Add JSON Schema draft 2019-09 and 2020-12 support - #144
Open
zmstone wants to merge 5 commits into
Open
Conversation
Add a new dialect module jesse_validator_draft2019_09 (forked from draft6), dispatched from the schema's $schema URI (https://json-schema.org/draft/2019-09/schema, with or without a trailing #). Implemented keywords: - dependentRequired / dependentSchemas (the split of draft-06 dependencies) - if / then / else - minContains / maxContains (extending contains) - $defs, local $anchor resolution (in jesse_state) - $ref evaluated alongside sibling keywords (2019-09 no longer replaces the whole schema with the ref target) - format is annotation-only (non-asserting), per the 2019-09 default Safety: keywords the dialect defines but jesse does not yet implement (unevaluatedProperties, unevaluatedItems, $recursiveRef) raise a keyword_not_supported schema error instead of being silently ignored, so a schema relying on them can never quietly accept data those keywords reject. Wire a new CT suite jesse_tests_draft2019_09_SUITE against the official JSON-Schema-Test-Suite submodule with an explicit, documented skip-list for the not-yet-supported groups. 833/1003 individual draft 2019-09 tests pass; draft3/4/6 suites and eunit are unaffected.
Milestone J2. Thread an evaluated-annotation accumulator through the draft 2019-09 recursion so unevaluated* apply only to properties/items not evaluated by an adjacent keyword or a successful in-place applicator. - jesse_state: add an opaque 'evaluated' field with get/set (unused by draft 3/4/6). - draft2019_09: split check_value/3 into a per-schema-object entry (resets the evaluated set, walks keywords, then applies unevaluated*) and the keyword walk. properties/patternProperties/additionalProperties/items/contains record what they evaluate; allOf/anyOf/oneOf/if-then-else/$ref/dependentSchemas merge the evaluated sets of their *passing* subschemas upward; 'not' never contributes; child-instance recursion restores the parent's set (so cousins and uncles cannot see each other's annotations). anyOf/oneOf now collect annotations from all/one passing branch respectively. Fix a latent jesse_json_path KVC pitfall: looking up a keyword on the empty object (unwrapped to []) returned [] rather than not_found; unevaluated* lookups now use a plain proplist lookup (schema_keyword/2). draft 2019-09 JSON-Schema-Test-Suite coverage rises to 927/1003; draft3/4/6 suites and eunit unaffected.
Milestone J3. New dialect module jesse_validator_draft2020_12, forked from the 2019-09 validator and dispatched from the 2020-12 $schema URI (with or without a trailing #). Differences from 2019-09: - The tuple form of "items" is now "prefixItems"; "items" is the single-schema applicator for the items after any "prefixItems" (replacing 2019-09 "additionalItems"). Evaluated-item accounting for unevaluatedItems is adjusted accordingly. - "$dynamicRef" hard-errors (keyword_not_supported) instead of being silently ignored, so it can never false-accept; "$dynamicAnchor" is an inert identifier. A dynamic scope stack is left for a follow-up. Everything else (dependentRequired/dependentSchemas, if/then/else, min/maxContains, $defs/$anchor, $ref alongside siblings, annotation-only format, and the unevaluated* annotation model) is inherited from 2019-09. New CT suite jesse_tests_draft2020_12_SUITE against the official JSON-Schema-Test-Suite: 935/997 individual draft 2020-12 tests pass. All five draft suites (3/4/6/2019-09/2020-12) and eunit pass.
Predicate that reports whether a schema's $schema dialect URI is one jesse can validate against (drafts 3/4/6 and, partially, 2019-09/2020-12; the latter with or without a trailing #). Lets callers reject a schema declaring an unsupported dialect at registration time instead of accepting it and failing every validation at run time.
Author
|
the last commit was made because the pr was originally preped for the emqx fork. |
seriyps
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds JSON Schema draft 2019-09 and draft 2020-12 support to jesse (for
emqx/emqx#17978), validated against
the official JSON-Schema-Test-Suite. Drafts 3/4/6 are left untouched.
Landed in three milestones (one commit each):
unevaluatedProperties/unevaluatedItems(the annotation model)prefixItems,items-rest, dispatch)Changes
jesse_schema_validator): a schema whose$schemais the2019-09 or 2020-12 metaschema URI (with or without a trailing
#, http orhttps) routes to a new dialect module. Previously any non-3/4/6
$schemareturned
schema_unsupported.jesse_validator_draft2019_09(forked from draft6):dependentRequired/dependentSchemas,if/then/else,minContains/maxContains,$defs, local$anchorresolution,$refevaluated alongside siblings (2019-09 no longer replaces the whole
schema), and
unevaluatedProperties/unevaluatedItems.formatisannotation-only per the 2019-09 default.
jesse_validator_draft2020_12(forked from 2019-09): theitemstuple-form →
prefixItemsrename,itemsas the after-prefixItemsapplicator, with the matching evaluated-item accounting for
unevaluatedItems.jesse_state:$id(not legacyid) drives scope resolution under2019-09/2020-12; plain-name
$reffragments resolve via a new$anchorsearch (skipping
$anchors buried inenum/const); an opaqueevaluatedannotation accumulator (unused by draft 3/4/6).
unevaluated* annotation model
Each schema-object evaluation carries an evaluated-set (
{PropNames, ItemIdxs}).check_value/3resets it on entry (so cousins in separate subschemas can'tsee each other's annotations); adjacent keywords and successful in-place
applicators (
allOf/anyOf/oneOf/if-then-else/$ref/dependentSchemas)contribute;
notnever does; child-instance recursion restores the parent set(the uncle rule).
anyOf/oneOfcollect annotations from all/one passingbranch. This passes the suite's cousin/uncle/nested/in-place-sibling cases.
Safety: no silent false-acceptance
Keywords a dialect defines but jesse doesn't implement yet —
$recursiveRef(2019-09) and
$dynamicRef(2020-12) — raisekeyword_not_supportedratherthan being silently ignored, so a schema relying on them fails loudly instead
of quietly accepting data those keywords would reject.
Test coverage (official JSON-Schema-Test-Suite)
All non-skipped assertions are green. New CT suites keep an explicit,
documented skip-list (in the suite) rather than silently dropping cases. The
skipped groups are:
$recursiveRef/$dynamicRef, remote-schema fetching(
refRemote, remote-metaschemaid/defs/refcases), and in-document$idbase-URI scoping (some
anchor/unknownKeywordcases) — all follow-ups.Validation
make ct— all five draft suites pass (3/4/6: 105/105; 2019-09: 47/47 cases;2020-12: 47/47 cases; 199 CT cases total)
make eunit— 36/36make elvis,make xref— cleanmake dialyzer— no new warnings (only the pre-existingct:palnoise injesse_tests_util)Follow-ups:
$recursiveRef/$dynamicRef(dynamic scope stack), remote-schemafetching, in-document
$idscoping. The1.9.0tag / EMQX dep bump happenafter this merges.