///|
priv struct Dialect {
uri : String
active : Set[String]
meta_slot : Int32?
}
///|
fn standard_dialect() -> Dialect {
{
uri: "https://json-schema.org/draft/2020-12/schema",
active: Set([
"applicator", "validation", "unevaluated", "meta-data", "format-annotation",
"content",
]),
meta_slot: None,
}
}
///|
fn vocabulary_family(uri : String) -> String? {
let prefix = "https://json-schema.org/draft/2020-12/vocab/"
if uri.has_prefix(prefix) {
let family = uri[prefix.length():].to_owned()
if [
"core", "applicator", "validation", "unevaluated", "meta-data", "format-annotation",
"content",
].contains(family) {
return Some(family)
}
}
None
}
///|
fn keyword_family(keyword : String) -> String? {
match keyword {
"properties"
| "patternProperties"
| "additionalProperties"
| "propertyNames"
| "dependentSchemas"
| "prefixItems"
| "items"
| "contains"
| "allOf"
| "anyOf"
| "oneOf"
| "not"
| "if"
| "then"
| "else" => Some("applicator")
"type"
| "const"
| "enum"
| "minimum"
| "maximum"
| "exclusiveMinimum"
| "exclusiveMaximum"
| "multipleOf"
| "minLength"
| "maxLength"
| "pattern"
| "required"
| "dependentRequired"
| "minProperties"
| "maxProperties"
| "minItems"
| "maxItems"
| "uniqueItems"
| "minContains"
| "maxContains" => Some("validation")
"unevaluatedProperties" | "unevaluatedItems" => Some("unevaluated")
"title"
| "description"
| "default"
| "deprecated"
| "readOnly"
| "writeOnly"
| "examples" => Some("meta-data")
"format" => Some("format-annotation")
"contentEncoding" | "contentMediaType" | "contentSchema" => Some("content")
_ => None
}
}
///|
fn dialect_fields(
fields : Map[String, Json],
dialect : Dialect,
) -> Map[String, Json] {
if dialect.active.length() == 6 {
return fields
}
let active : Map[String, Json] = Map([])
for key, value in fields {
match keyword_family(key) {
Some(family) => if dialect.active.contains(family) { active[key] = value }
None => active[key] = value
}
}
active
}
///|
fn load_resource(
index : RefIndex,
identity : String,
path : String,
limit : Int32,
as_dialect~ : Bool = false,
) -> Int32 raise CompileError {
if index.registry.get(identity) is Some(resource) {
return resource
}
let mut candidate : Int32? = None
for doc, source in index.docs {
if source.identity == Some(identity) ||
document_has_identity(
index,
source.root,
source.identity,
identity,
standard_dialect(),
) {
if candidate is Some(_) {
invalid_keyword(
path,
"ambiguous supplied resource identity: " + identity,
)
}
candidate = Some(doc)
}
}
let doc = match candidate {
Some(doc) => doc
None =>
match builtin_metaschema(identity) {
Some(document) => add_document(index, document, Some(identity), limit)
None => {
if as_dialect {
raise UnknownDialect(identity)
}
invalid_keyword(path, "no supplied schema resource: " + identity)
0
}
}
}
scan_resource(index, doc, index.docs[doc].root, "", 0, true, 0, limit)
match index.registry.get(identity) {
Some(resource) => resource
None => {
invalid_keyword(path, "resource identity was not registered: " + identity)
0
}
}
}
///|
fn dialect_identifier(text : String) -> String {
let uri = Uri::parse(text).normalize()
match uri.fragment {
Some(fragment) if fragment != "" => uri.identity() + "#" + fragment
_ => uri.identity()
}
}
///|
fn validate_meta_locations(
index : RefIndex,
doc : Int32,
value : Json,
path : String,
inherited : Dialect,
is_root : Bool,
depth : Int32,
limit : Int32,
) -> Unit raise CompileError {
if depth > limit {
raise CompileError::DepthLimit(limit)
}
guard value is Object(fields) else { return }
if !is_root && !fields.contains("$id") && fields.contains("$vocabulary") {
invalid_keyword(
pointer_step(path, "$vocabulary"),
"$vocabulary is only allowed at a metaschema resource root",
)
}
let dialect = match index.docs[doc].slots.get(path) {
Some(slot) if index.resource_of[slot] >= 0 =>
index.resources[index.resource_of[slot]].dialect
_ => inherited
}
for entry in schema_children(dialect_fields(fields, dialect), path) {
let (child_path, child) = entry
validate_meta_locations(
index,
doc,
child,
child_path,
dialect,
false,
depth + 1,
limit,
)
}
}
///|
fn resolve_dialect(
index : RefIndex,
identifier : String,
path : String,
limit : Int32,
) -> Dialect raise CompileError {
validate_uri_reference(identifier, path)
if Uri::parse(identifier).scheme is None {
invalid_keyword(path, "dialect URI must be absolute")
}
let uri = dialect_identifier(identifier)
if uri == standard_dialect().uri {
return standard_dialect()
}
if index.dialects.get(uri) is Some(dialect) {
return dialect
}
if index.resolving_dialects.contains(uri) {
invalid_keyword(path, "cyclic dialect declaration: " + uri)
}
if index.resolving_dialects.length() >= limit {
raise CompileError::DepthLimit(limit)
}
index.resolving_dialects.add(uri)
defer ignore(index.resolving_dialects.remove(uri))
let resource = load_resource(
index,
canonical_document_uri(uri),
path,
limit,
as_dialect=true,
)
let (doc, pointer) = index.sites[resource]
let slot = match Uri::parse(uri).fragment {
Some(fragment) => resolve_ref(index, resource, "#" + fragment, path, limit)
None => resource_root_slot(index, doc, pointer, path)
}
let (_, root_pointer) = index.sites[index.resource_of[slot]]
if root_pointer != index.paths[slot] {
invalid_keyword(path, "$schema must identify a metaschema resource root")
}
validate_meta_locations(
index,
index.doc_of[slot],
index.targets[slot],
index.paths[slot],
index.resources[index.resource_of[slot]].dialect,
true,
0,
limit,
)
let mut active = standard_dialect().active
if index.targets[slot] is Object(fields) &&
fields.get("$vocabulary") is Some(declaration) {
guard declaration is Object(vocabularies) else {
invalid_keyword(path, "metaschema $vocabulary must be an object: " + uri)
return standard_dialect()
}
active = Set([])
let mut core = false
let seen : Set[String] = Set([])
for identifier, required in vocabularies {
validate_uri_reference(identifier, path)
if Uri::parse(identifier).scheme is None {
invalid_keyword(path, "vocabulary URI must be absolute")
}
let vocabulary = dialect_identifier(identifier)
if seen.contains(vocabulary) {
invalid_keyword(
path,
"duplicate normalized vocabulary URI: " + vocabulary,
)
}
seen.add(vocabulary)
guard required is (True | False) else {
invalid_keyword(
path,
"vocabulary requirement must be boolean: " + vocabulary,
)
continue
}
match vocabulary_family(vocabulary) {
Some("core") => {
if required is False {
invalid_keyword(path, "core vocabulary must be required: " + uri)
}
core = true
}
Some(family) => active.add(family)
None =>
if required is True {
raise UnsupportedVocabulary(dialect=uri, vocabulary~)
}
}
}
if !core {
invalid_keyword(path, "metaschema $vocabulary must require core: " + uri)
}
}
let dialect : Dialect = { uri, active, meta_slot: Some(slot), }
index.dialects[uri] = dialect
dialect
}
///|
// Discovery only selects candidate namespaces. Validation is deferred until a
// source is actually loaded; unrelated legacy dialects must not affect a call.
fn discovery_dialect(
index : RefIndex,
fields : Map[String, Json],
inherited : Dialect,
) -> Dialect {
guard fields.get("$schema") is Some(String(identifier)) else {
return inherited
}
let uri = dialect_identifier(identifier)
if uri == standard_dialect().uri {
return standard_dialect()
}
if index.dialects.get(uri) is Some(dialect) {
return dialect
}
for source in index.docs {
let base = canonical_document_uri(uri)
let identified = source.identity == Some(base) ||
(match source.root {
Object(meta) =>
match meta.get("$id") {
Some(String(id)) => canonical_uri(source.identity, id) == base
_ => false
}
_ => false
})
if identified {
if source.root is Object(meta) &&
meta.get("$vocabulary") is Some(Object(vocabularies)) {
let active : Set[String] = Set([])
for id, value in vocabularies {
if value is (True | False) &&
vocabulary_family(dialect_identifier(id)) is Some(family) &&
family != "core" {
active.add(family)
}
}
return { uri, active, meta_slot: None, }
}
return standard_dialect()
}
}
{ uri, active: Set([]), meta_slot: None, }
}