Skip to main content

corvus_json_schema/
dialect.rs

1//! Dialects, vocabularies and which keywords hold subschemas (a port of `SchemaKeywords`).
2
3/// The JSON Schema dialects the evaluator understands, in specification order.
4#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub enum Dialect {
6    Draft4,
7    Draft6,
8    Draft7,
9    Draft201909,
10    Draft202012,
11}
12
13impl Dialect {
14    /// Draft 7 and earlier, where `$ref` replaces its siblings and there are no vocabularies.
15    pub(crate) fn is_legacy(self) -> bool {
16        self <= Dialect::Draft7
17    }
18}
19
20/// The vocabularies in effect for a schema resource (2019-09 and later), as bits.
21pub(crate) mod vocab {
22    pub const NONE: u32 = 0;
23    pub const CORE: u32 = 1 << 0;
24    pub const APPLICATOR: u32 = 1 << 1;
25    pub const VALIDATION: u32 = 1 << 2;
26    pub const META_DATA: u32 = 1 << 3;
27    pub const FORMAT_ANNOTATION: u32 = 1 << 4;
28    pub const FORMAT_ASSERTION: u32 = 1 << 5;
29    pub const CONTENT: u32 = 1 << 6;
30    pub const UNEVALUATED: u32 = 1 << 7;
31    pub const ALL_ANNOTATING_FORMAT: u32 =
32        CORE | APPLICATOR | VALIDATION | META_DATA | FORMAT_ANNOTATION | CONTENT | UNEVALUATED;
33}
34
35/// Maps a well-known metaschema URI (normalised, no fragment) to its dialect.
36pub(crate) fn known_dialect(uri: &str) -> Option<Dialect> {
37    match uri {
38        "http://json-schema.org/draft-04/schema" => Some(Dialect::Draft4),
39        "http://json-schema.org/draft-06/schema" => Some(Dialect::Draft6),
40        "http://json-schema.org/draft-07/schema" => Some(Dialect::Draft7),
41        "https://json-schema.org/draft/2019-09/schema" => Some(Dialect::Draft201909),
42        "https://json-schema.org/draft/2020-12/schema" => Some(Dialect::Draft202012),
43        _ => None,
44    }
45}
46
47/// Maps a vocabulary URI to its bit.
48pub(crate) fn vocabulary_flag(uri: &str) -> u32 {
49    match uri {
50        "https://json-schema.org/draft/2020-12/vocab/core" | "https://json-schema.org/draft/2019-09/vocab/core" => {
51            vocab::CORE
52        }
53        "https://json-schema.org/draft/2020-12/vocab/applicator"
54        | "https://json-schema.org/draft/2019-09/vocab/applicator" => vocab::APPLICATOR,
55        "https://json-schema.org/draft/2020-12/vocab/validation"
56        | "https://json-schema.org/draft/2019-09/vocab/validation" => vocab::VALIDATION,
57        "https://json-schema.org/draft/2020-12/vocab/meta-data"
58        | "https://json-schema.org/draft/2019-09/vocab/meta-data" => vocab::META_DATA,
59        "https://json-schema.org/draft/2020-12/vocab/format-annotation"
60        | "https://json-schema.org/draft/2019-09/vocab/format" => vocab::FORMAT_ANNOTATION,
61        "https://json-schema.org/draft/2020-12/vocab/format-assertion" => vocab::FORMAT_ASSERTION,
62        "https://json-schema.org/draft/2020-12/vocab/content"
63        | "https://json-schema.org/draft/2019-09/vocab/content" => vocab::CONTENT,
64        "https://json-schema.org/draft/2020-12/vocab/unevaluated" => vocab::UNEVALUATED,
65        _ => vocab::NONE,
66    }
67}
68
69/// How a keyword holds subschemas.
70#[derive(Clone, Copy, PartialEq, Eq)]
71pub(crate) enum SubschemaKind {
72    None,
73    Single,
74    SingleOrArray,
75    Array,
76    Map,
77}
78
79/// Which keywords hold subschemas, by dialect (`SchemaKeywords.GetSubschemaKind`).
80pub(crate) fn subschema_kind(keyword: &str, dialect: Dialect, legacy_ref_overrides_siblings: bool) -> SubschemaKind {
81    if keyword == "definitions" || keyword == "$defs" {
82        return SubschemaKind::Map;
83    }
84    if legacy_ref_overrides_siblings {
85        return SubschemaKind::None;
86    }
87    match keyword {
88        "properties" | "patternProperties" | "dependencies" => SubschemaKind::Map,
89        "additionalProperties" | "not" => SubschemaKind::Single,
90        "allOf" | "anyOf" | "oneOf" => SubschemaKind::Array,
91        "items" => {
92            if dialect >= Dialect::Draft202012 {
93                SubschemaKind::Single
94            } else {
95                SubschemaKind::SingleOrArray
96            }
97        }
98        "additionalItems" if dialect <= Dialect::Draft201909 => SubschemaKind::Single,
99        "contains" | "propertyNames" if dialect >= Dialect::Draft6 => SubschemaKind::Single,
100        "if" | "then" | "else" if dialect >= Dialect::Draft7 => SubschemaKind::Single,
101        "unevaluatedProperties" | "unevaluatedItems" | "contentSchema" if dialect >= Dialect::Draft201909 => {
102            SubschemaKind::Single
103        }
104        "dependentSchemas" if dialect >= Dialect::Draft201909 => SubschemaKind::Map,
105        "prefixItems" if dialect >= Dialect::Draft202012 => SubschemaKind::Array,
106        _ => SubschemaKind::None,
107    }
108}