Skip to content

Repository files navigation

json-schema-dev

A REPRO capability module that bundles two JSON Schema validators behind one command-line contract, so the same (schema, instance) pair can be put to both and their verdicts compared.

The gallery of runnable JSON Schema demonstrations that grew up alongside these wrappers now lives in json-schema-demos, which requires this module. The numbered demos cited below are demos of that gallery.

Validators

Both pinned, both driven at the JSON Schema 2020-12 dialect:

Language Library Command
Python jsonschema 4.23.0 (Draft202012Validator) jsonschema-validate --schema FILE --instance FILE [--ref FILE]...
JavaScript ajv 8.17.1 (ajv/dist/2020) ajv-validate --schema FILE --instance FILE [--ref FILE]...

Arguments are named (--schema/--instance, or -s/-i), not positional, so the schema and instance can't be transposed by accident — a swap that would otherwise pass silently, since any JSON object is itself a valid, permissive schema.

Each --ref FILE (repeatable) loads an additional schema and registers it under its $id, making it available to $ref in the main schema — this is how a schema spread across several files is validated. A --ref schema must carry a $id; nothing is ever fetched from the $id's URI, which serves purely as a name to resolve against (demo 16).

Neither wrapper checks format: it is the annotation the 2020-12 specification makes it by default, on both legs alike and in silence (Ajv's "unknown format ignored" warning is switched off, since ignoring is all the wrapper ever does with a format). Format assertion, if ever added, will be added to both wrappers together as an explicit option, with the unknown-format case handled identically on both.

Both wrappers honor the nonstandard errorMessage keyword, reading the messages out of the schema JSON themselves — a plain string covering its subschema, an object keyed by keyword, by member under required, properties or items, with _ for the rest — and interpolating ${pointer} references to instance values, absolute from the instance root or relative to where the message is written. Neither uses the ajv-errors plugin. Failures no message covers keep each library's own message (demo 19). The forms and their precedence are stated in CONTRACT.md.

Each command writes its verdict to stdout — VALID, or one INVALID: <error> line per validation error — and exits 0 (valid), 1 (invalid), or 2 (error: bad arguments, an unreadable file, a schema or --ref file that is not valid JSON, an invalid schema, or an unresolvable $ref); diagnostics and -h/--help text go to stderr/stdout respectively. An instance file that reads but does not parse as JSON is a verdict, not an error — INVALID: instance is not valid JSON: …, exit 1 — because everything about the submitted document is a verdict and everything about the setup is an error; a document failing the JSON grammar itself has failed validation's lowest tier (demo 20). In the same spirit, --reject-duplicate-members makes a duplicate object member name anywhere in the instance a verdict (INVALID: instance contains duplicate member name "…", all names sorted, word-for-word identical across the two wrappers): parsers silently keep the last value, so no schema can ever see a duplicate — parse time is the only tier that can check (demo 21). On the schema side, a schema (or --ref file) whose $schema entry declares any version other than 2020-12 is refused (exit 2) rather than silently reinterpreted; --ignore-declared-version discards the declaration and validates as 2020-12 anyway, making the reinterpretation an explicit choice (demo 22). The dialect is forced in the wrapper rather than read from the schema's $schema, so an old validator cannot fall back to a weaker dialect.

Both commands are exported as artifacts of this module, so they are on PATH inside this REPRO and inside any REPRO that composes it with repro.require json-schema-dev ….

The contract, and the tests

The two wrappers are two implementations of one command-line contract, stated in CONTRACT.md: the exit-status rule that everything about the instance is a verdict and everything else is an error, what the two implementations must agree about and what is left to the underlying library, the --ref, errorMessage, and parse-tier behaviors, and a table of the defects currently known.

tests/ holds the conformance corpus that puts the contract to both implementations: language-neutral cases, as JSON, run through each wrapper by a Mocha suite and checked both against their recorded behavior and against each other. Behavior known to be wrong is pinned by a case whose defect summary becomes a test name, so the defect inventory appears in the test report.

make test-code       # start a session and run the suite in the image

Inside a started REPRO it is plain Mocha (npm test, npm test -- --grep <case>). Not on the host: the wrappers live in the image, so that is where anything exercising them runs.

(Makefile-tests is separate and older: it exercises the REPRO lifecycle, not the validator contract.)

Limitations

What this pair cannot be used for, as against what it does not yet do well: see CONTRACT.md. The one that constrains schema authors is that the Python leg does not implement the ECMA-262 regular expression dialect pattern is defined in, so a schema meant for both wrappers should not lean on $.

Build

The parent image adds Python (via apt) and a pinned Node (official prebuilt binary) to the published framework base, so it builds from nothing local.

make build-parent     # framework base + Python + Node (one-time, slow layer)
make build-image      # install the two libraries and export the wrappers
make test-code        # run the conformance suite

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages