json-schema-engine 0.0.1__tar.gz → 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- json_schema_engine-0.0.2/CHANGELOG.md +47 -0
- json_schema_engine-0.0.2/PKG-INFO +314 -0
- json_schema_engine-0.0.2/README.md +287 -0
- json_schema_engine-0.0.2/pyproject.toml +175 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/__init__.py +113 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/emit.py +255 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/errors.py +16 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/plan.py +415 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/py.typed +0 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/runtime.py +114 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/runtime_compile.py +29 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/serialize/__init__.py +170 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/serialize/body.py +524 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/serialize/context.py +155 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/serialize/units.py +181 -0
- json_schema_engine-0.0.2/src/json_schema_engine/compiler/standalone.py +151 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/__init__.py +158 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/channel.py +172 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/cursor.py +78 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/dialect.py +494 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/engine.py +484 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/errors.py +191 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/evaluator.py +613 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/formats.py +50 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/json_model.py +245 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/__init__.py +1 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/_ids.py +54 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/applicator.py +353 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/applicator_array.py +312 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/applicator_object.py +323 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/content.py +58 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/core.py +180 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/dialect2019.py +163 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/dialect2020.py +88 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/dialect7.py +189 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/dialects.py +30 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/format.py +146 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/legacy.py +348 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/meta_data.py +20 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/unevaluated.py +266 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/keywords/validation.py +642 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/loader.py +96 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/lowering.py +463 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2019-09/applicator.json +53 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2019-09/content.json +14 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2019-09/core.json +54 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2019-09/format.json +11 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2019-09/meta-data.json +34 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2019-09/schema.json +42 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2019-09/validation.json +95 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/applicator.json +45 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/content.json +14 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/core.json +48 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/format-annotation.json +11 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/format-assertion.json +11 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/meta-data.json +34 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/schema.json +58 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/unevaluated.json +12 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/2020-12/validation.json +95 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/__init__.py +61 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/draft-06/schema.json +155 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/metaschemas/draft-07/schema.json +172 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/output.py +640 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/positions.py +287 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/py.typed +0 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/records.py +189 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/ref.py +35 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/regex.py +114 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/registry.py +484 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/result.py +258 -0
- json_schema_engine-0.0.2/src/json_schema_engine/core/uri.py +204 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/__init__.py +136 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/_abnf.py +70 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/datetime_.py +161 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/idna_.py +105 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/misc.py +62 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/net.py +165 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/pointer.py +57 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/py.typed +0 -0
- json_schema_engine-0.0.2/src/json_schema_engine/formats/uri.py +129 -0
- json_schema_engine-0.0.1/PKG-INFO +0 -48
- json_schema_engine-0.0.1/README.md +0 -26
- json_schema_engine-0.0.1/pyproject.toml +0 -50
- json_schema_engine-0.0.1/src/json_schema_engine/core/__init__.py +0 -7
- json_schema_engine-0.0.1/tests/test_import.py +0 -12
- {json_schema_engine-0.0.1 → json_schema_engine-0.0.2}/.gitignore +0 -0
- {json_schema_engine-0.0.1 → json_schema_engine-0.0.2}/LICENSE +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `json-schema-engine` (the Python engine). The
|
|
4
|
+
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
5
|
+
versions follow [SemVer](https://semver.org/) with the 0.x caveat that
|
|
6
|
+
minor versions may change public API.
|
|
7
|
+
|
|
8
|
+
## [0.0.2] - unreleased
|
|
9
|
+
|
|
10
|
+
The first functional release. Everything below is new relative to the
|
|
11
|
+
0.0.1 name reservation.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `json_schema_engine.core`: the interpreter tier. Draft 2020-12 (default),
|
|
16
|
+
2019-09, draft-07, and draft-06 with each draft's own semantics
|
|
17
|
+
(`$dynamicRef`, `$recursiveRef`, `$ref` sibling handling); `$vocabulary`
|
|
18
|
+
processing with the bundled metaschemas; loaders for remote references
|
|
19
|
+
with source-position reporting (`parse_json_with_ranges`,
|
|
20
|
+
`positions=True`, `Engine.locate`); metaschema validation on request
|
|
21
|
+
(`validate_schemas=True`); the `flag`, `basic`, `detailed`, `verbose`,
|
|
22
|
+
`list`, and `hierarchical` output formats with annotation selection,
|
|
23
|
+
error params, verbose levels, and the application trace; ECMA-262
|
|
24
|
+
regular expressions through `ecma-regex`, with a Python `re`
|
|
25
|
+
passthrough dialect and the optional `regex` backend; the
|
|
26
|
+
`reject_unsafe_regex` screen, `max_depth`, and O(n) `uniqueItems`.
|
|
27
|
+
- `json_schema_engine.compiler`: `compile_validator` (a verdict-only
|
|
28
|
+
Python function bound to a registry snapshot, trampolining into the
|
|
29
|
+
interpreter for whatever it cannot lower), `emit_standalone` (a module
|
|
30
|
+
importable without the compiler), and `explain_compilation`.
|
|
31
|
+
- `json_schema_engine.formats`: every format the four drafts define,
|
|
32
|
+
implemented from the RFCs and verified against the official
|
|
33
|
+
`optional/format` suites; the `idna` extra for IDNA2008
|
|
34
|
+
(`idn-hostname`, A-label checks in `hostname`); assertion opt-in through
|
|
35
|
+
`create_engine(formats=..., assert_formats=...)` and the 2020-12
|
|
36
|
+
format-assertion vocabulary.
|
|
37
|
+
- Every distribution portion ships a `py.typed` marker.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- The distribution depends on `ecma-regex>=0.1,<0.2`.
|
|
42
|
+
- The sdist no longer carries the test tree, which needs the repository's
|
|
43
|
+
test-kit and the suite submodule.
|
|
44
|
+
|
|
45
|
+
## [0.0.1] - 2026-09-20
|
|
46
|
+
|
|
47
|
+
Name reservation on PyPI; no functionality.
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: json-schema-engine
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: A spec-complete, annotation-first JSON Schema engine for Python: interpreter, compiler, formats, every standard output format.
|
|
5
|
+
Project-URL: Homepage, https://github.com/handrews/py-json-schema-engine
|
|
6
|
+
Project-URL: Repository, https://github.com/handrews/py-json-schema-engine
|
|
7
|
+
Project-URL: Issues, https://github.com/handrews/py-json-schema-engine/issues
|
|
8
|
+
Author: Henry Andrews
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: json,json-schema,validation
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: ecma-regex<0.2,>=0.1
|
|
22
|
+
Provides-Extra: idna
|
|
23
|
+
Requires-Dist: idna>=3.7; extra == 'idna'
|
|
24
|
+
Provides-Extra: regex
|
|
25
|
+
Requires-Dist: regex; extra == 'regex'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# json-schema-engine (Python)
|
|
29
|
+
|
|
30
|
+
A JSON Schema implementation for Python that aims to be both spec-complete
|
|
31
|
+
and built for speed: an interpreter that is the reference semantics, a
|
|
32
|
+
compiler tier for hot paths, full annotation collection, and every standard
|
|
33
|
+
output format. It is the Python counterpart of
|
|
34
|
+
[handrews/json-schema-engine](https://github.com/handrews/json-schema-engine)
|
|
35
|
+
and shares its architecture: two tiers, one keyword registry, a frame-scoped
|
|
36
|
+
record channel, and the new (2026)
|
|
37
|
+
[IETF working group draft-03](https://www.ietf.org/archive/id/draft-ietf-jsonschema-json-schema-03.html)
|
|
38
|
+
relevance model.
|
|
39
|
+
|
|
40
|
+
Produced by Henry Andrews via Claude Code.
|
|
41
|
+
|
|
42
|
+
**Status: `0.0.2` is the first functional release.**
|
|
43
|
+
|
|
44
|
+
All `0.0.x` releases will have AI-written documentation. Version
|
|
45
|
+
`0.1.0` will indicate that the documentation has been audited and revised
|
|
46
|
+
by a human.
|
|
47
|
+
|
|
48
|
+
As of `0.0.2`, this package carries the
|
|
49
|
+
interpreter core for 2020-12, 2019-09, draft-07, and draft-06 with every
|
|
50
|
+
standard output format, the compiler tier (a flag validator and standalone
|
|
51
|
+
modules, M6–M7), and the `json_schema_engine.formats` package — green on
|
|
52
|
+
every official test-suite file for those drafts through both tiers, on the
|
|
53
|
+
official output-tests, and on Bowtie. [DESIGN.md](DESIGN.md) is the design
|
|
54
|
+
contract and carries the milestone status.
|
|
55
|
+
|
|
56
|
+
The regular-expression translator lives in its own package,
|
|
57
|
+
[`ecma-regex`](packages/ecma-regex/README.md) (`0.1.0`): ECMA-262 patterns
|
|
58
|
+
for Python, with JavaScript semantics, no dependency on this engine.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
pip install json-schema-engine
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Python 3.12+. Extras: `[idna]` for IDNA2008 support (`idn-hostname`, and the
|
|
67
|
+
`xn--` label check inside `hostname`); `[regex]` to swap in the `regex`
|
|
68
|
+
package as the pattern backend.
|
|
69
|
+
|
|
70
|
+
## Use
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from json_schema_engine.core import create_engine
|
|
74
|
+
|
|
75
|
+
engine = create_engine()
|
|
76
|
+
uri = engine.register_schema(
|
|
77
|
+
{
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {"name": {"type": "string"}},
|
|
80
|
+
"required": ["name"],
|
|
81
|
+
},
|
|
82
|
+
"https://example.com/person",
|
|
83
|
+
)
|
|
84
|
+
assert engine.evaluate(uri, {"name": "Ada"}).valid
|
|
85
|
+
result = engine.evaluate(uri, {}, output="list")
|
|
86
|
+
assert result.errors is not None and result.errors[0]["evaluationPath"] == "/required"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Four drafts are built in: 2020-12 (the default), 2019-09, draft-07, and
|
|
90
|
+
draft-06. A document's `$schema` selects its dialect; `create_engine(
|
|
91
|
+
default_dialect=DIALECT_DRAFT_07)` sets the dialect for documents without
|
|
92
|
+
one. Each keeps its own semantics, so a draft-07 `$ref` ignores its
|
|
93
|
+
siblings while a 2019-09 one does not.
|
|
94
|
+
|
|
95
|
+
Every standard output format is available by name: `flag` (the default),
|
|
96
|
+
`basic`, `detailed`, and `verbose` from the IETF draft-03 output spec, and
|
|
97
|
+
`list` and `hierarchical` from the machines-oriented proposal. Annotations
|
|
98
|
+
are a separate control (`annotations=True`, or an `AnnotationSelection`),
|
|
99
|
+
`verbose=True` asks `list`/`hierarchical` for the verbose level with
|
|
100
|
+
irrelevant records marked as dropped, and `trace=True` adds the application
|
|
101
|
+
tree with error indexes into `result.errors`.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
result = engine.evaluate(uri, {"name": 3}, output="hierarchical")
|
|
105
|
+
assert result.output_document == {
|
|
106
|
+
"valid": False,
|
|
107
|
+
"evaluationPath": "",
|
|
108
|
+
"schemaLocation": "https://example.com/person#",
|
|
109
|
+
"instanceLocation": "",
|
|
110
|
+
"details": [
|
|
111
|
+
{
|
|
112
|
+
"valid": False,
|
|
113
|
+
"evaluationPath": "/properties/name",
|
|
114
|
+
"schemaLocation": "https://example.com/person#/properties/name",
|
|
115
|
+
"instanceLocation": "/name",
|
|
116
|
+
"errors": {"type": "expected string"},
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`create_engine(validate_schemas=True)` checks every registered document
|
|
123
|
+
against its metaschema and raises `SchemaValidationError` with the errors.
|
|
124
|
+
A loader that reports source positions (see `parse_json_with_ranges`,
|
|
125
|
+
exported from `json_schema_engine.core`) lets `evaluate(..., positions=True)`
|
|
126
|
+
attach a `source` location to every error and annotation, and
|
|
127
|
+
`engine.locate()` answers the same question for any schema location.
|
|
128
|
+
|
|
129
|
+
## Compile
|
|
130
|
+
|
|
131
|
+
The compiler tier turns a registered schema into a Python function. It is
|
|
132
|
+
not a second implementation: any subschema it cannot emit (a `$dynamicRef`,
|
|
133
|
+
an `unevaluated*` whose coverage is only known at runtime, an in-place
|
|
134
|
+
cycle) calls back into the interpreter, so a compiled validator is exactly
|
|
135
|
+
as correct as `Engine.evaluate` and never less complete. Tier choice is a
|
|
136
|
+
performance decision, not a semantic one.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from json_schema_engine.compiler import compile_validator, emit_standalone
|
|
140
|
+
|
|
141
|
+
compiled = compile_validator(engine, uri)
|
|
142
|
+
assert compiled.validate({"name": "Ada"}) is True
|
|
143
|
+
assert compiled.validate({}) is False
|
|
144
|
+
print(compiled.source) # the emitted module, for reading
|
|
145
|
+
module_source = emit_standalone(engine, uri) # importable without the compiler
|
|
146
|
+
assert "def validate" in module_source
|
|
147
|
+
assert "import json_schema_engine.compiler" not in module_source
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`compile_validator` returns a verdict-only validator (the flag level) that
|
|
151
|
+
binds a snapshot of the registries at compile time, so register everything
|
|
152
|
+
first. `emit_standalone` writes the same code as a module that imports
|
|
153
|
+
only the standard library and this package's pure helpers; it refuses,
|
|
154
|
+
with `StandaloneUnsupportedError`, a schema that would need the
|
|
155
|
+
interpreter at evaluation time. Compiled code assumes plain data as
|
|
156
|
+
`json.loads` produces it (`dict`, `list`, `str`, `int`, `float`, `bool`,
|
|
157
|
+
`None`); subclasses of those types belong to the interpreter. Errors,
|
|
158
|
+
annotations, and the output formats are interpreter features today;
|
|
159
|
+
compiled output beyond the verdict is a later milestone.
|
|
160
|
+
|
|
161
|
+
## Formats
|
|
162
|
+
|
|
163
|
+
`format` annotates by default in every dialect (the specs' default, and
|
|
164
|
+
what the official `format.json` legs require). Assertion is opt-in, from a
|
|
165
|
+
format table implemented from each format's RFC and verified against the
|
|
166
|
+
official `optional/format` suite:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from json_schema_engine.core import create_engine
|
|
170
|
+
from json_schema_engine.formats import FORMATS_2020_12, format_table_for
|
|
171
|
+
|
|
172
|
+
# The 2020-12 format-assertion vocabulary: a metaschema declaring it makes
|
|
173
|
+
# `format` assert; names the table lacks are refused at registration.
|
|
174
|
+
engine = create_engine(formats=FORMATS_2020_12)
|
|
175
|
+
# Best effort in every standard dialect: known names assert, unknown names
|
|
176
|
+
# annotate only.
|
|
177
|
+
engine = create_engine(formats=FORMATS_2020_12, assert_formats=True)
|
|
178
|
+
uri = engine.register_schema({"format": "date-time"}, "https://example.com/dt")
|
|
179
|
+
assert engine.evaluate(uri, "1998-12-31T23:59:60Z").valid
|
|
180
|
+
assert not engine.evaluate(uri, "1998-12-31T22:59:60Z").valid
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`FORMATS_2020_12` (also 2019-09) carries the nineteen defined formats;
|
|
184
|
+
`FORMATS_DRAFT_07` and `FORMATS_DRAFT_06` carry each draft's list, and
|
|
185
|
+
`format_table_for(dialect_uri)` picks one. A metaschema that declares the
|
|
186
|
+
format-assertion vocabulary on an engine without a table raises
|
|
187
|
+
`FormatsRequiredError`, and `assert_formats=True` without a table does too.
|
|
188
|
+
A custom table is any mapping of names to `FormatDefinition(test, types)`;
|
|
189
|
+
`types` scopes a format to instance types other than strings.
|
|
190
|
+
|
|
191
|
+
`idn-hostname` and the A-label checks inside `hostname` need IDNA2008,
|
|
192
|
+
provided by the `idna` extra:
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
pip install 'json-schema-engine[idna]'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Without it, asserting `idn-hostname` raises `FormatUnavailableError` at
|
|
199
|
+
registration, and `hostname` accepts a well-formed `xn--` label without
|
|
200
|
+
decoding it. Compiled validators assert formats too; a standalone module
|
|
201
|
+
imports the predicates it needs from `json_schema_engine.formats`, so the
|
|
202
|
+
extra must be installed wherever such a module runs.
|
|
203
|
+
|
|
204
|
+
## Documentation
|
|
205
|
+
|
|
206
|
+
- [User guide](docs/guide/index.md) — validation, output formats,
|
|
207
|
+
annotations, dialects, loaders, source positions, metaschemas, custom
|
|
208
|
+
keywords, security, compiling schemas, and formats, topic by topic.
|
|
209
|
+
- [API reference](docs/reference.md) — every public name, by package.
|
|
210
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — setup, gates, and conventions.
|
|
211
|
+
- [CHANGELOG.md](CHANGELOG.md) — what changed, release by release.
|
|
212
|
+
- [DESIGN.md](DESIGN.md) — the engineering design contract and milestone
|
|
213
|
+
status.
|
|
214
|
+
|
|
215
|
+
## Security
|
|
216
|
+
|
|
217
|
+
Schemas and instances are both often untrusted input. The interpreter
|
|
218
|
+
generates no code — there is no `compile()` or `exec()` on its path — so
|
|
219
|
+
code-injection concerns do not apply to it; a denial-of-service bound is
|
|
220
|
+
best effort, not a guarantee, so treat wildly untrusted schemas with the
|
|
221
|
+
same care as any other untrusted program input. Three specific vectors have
|
|
222
|
+
a bound or an opt-out.
|
|
223
|
+
|
|
224
|
+
**Regular expressions (ReDoS).** `pattern` and `patternProperties` compile
|
|
225
|
+
untrusted regexes and run them against untrusted strings; Python's `re` can
|
|
226
|
+
backtrack catastrophically on a pattern like `(a+)+$`. `reject_unsafe_regex`
|
|
227
|
+
screens for nested unbounded quantifiers at registration:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from json_schema_engine.core import create_engine, UnsafeRegexError
|
|
231
|
+
|
|
232
|
+
engine = create_engine(reject_unsafe_regex=True)
|
|
233
|
+
try:
|
|
234
|
+
engine.register_schema({"pattern": "(a+)+$"}, "https://ex/redos")
|
|
235
|
+
except UnsafeRegexError:
|
|
236
|
+
pass # rejected before it ever runs
|
|
237
|
+
else:
|
|
238
|
+
raise AssertionError("expected UnsafeRegexError")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Patterns are ECMA-262 by default, translated by the `ecma-regex` package to
|
|
242
|
+
the `re` backend (a `regex` backend is available via the `regex` extra);
|
|
243
|
+
neither backend is linear-time, so the screen is a heuristic, not a proof.
|
|
244
|
+
`regex_dialect="python"` hands patterns to `re` untouched, for schemas
|
|
245
|
+
written for Python only.
|
|
246
|
+
|
|
247
|
+
**Recursion depth.** `max_depth` (default 512) bounds both registration
|
|
248
|
+
nesting and evaluation nesting, raising the typed `MaxDepthExceededError`
|
|
249
|
+
before CPython's own stack limit can produce an untyped `RecursionError`; a
|
|
250
|
+
stray `RecursionError` that does slip through is still converted to the
|
|
251
|
+
same typed error. The engine stays usable afterward — each `evaluate` or
|
|
252
|
+
`register_schema` call runs in fresh state, so a rejected document does not
|
|
253
|
+
poison later calls.
|
|
254
|
+
|
|
255
|
+
**Array uniqueness.** `uniqueItems` compares elements in O(n) by bucketing
|
|
256
|
+
on a canonical key and confirming collisions with full JSON equality, so
|
|
257
|
+
large arrays of distinct values do not incur quadratic cost, while genuine
|
|
258
|
+
duplicates — including numbers equal across `int`/`float` and objects that
|
|
259
|
+
differ only in member order — are still reported.
|
|
260
|
+
|
|
261
|
+
**No prototype hazard.** Python dicts have no prototype chain, so there is
|
|
262
|
+
nothing for a hostile property name to pollute: `__proto__`, `constructor`,
|
|
263
|
+
and similar reserved-looking names evaluate as ordinary properties.
|
|
264
|
+
|
|
265
|
+
## Development
|
|
266
|
+
|
|
267
|
+
```sh
|
|
268
|
+
uv sync --all-packages --all-groups
|
|
269
|
+
uv run pytest -q
|
|
270
|
+
uv run ruff check . && uv run ruff format --check .
|
|
271
|
+
uv run pyright
|
|
272
|
+
uv run lint-imports
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The official test suite is a git submodule at `test-suite/`; clone with
|
|
276
|
+
`--recurse-submodules` or run `git submodule update --init`.
|
|
277
|
+
|
|
278
|
+
The [Bowtie](https://bowtie.report) conformance leg builds a container
|
|
279
|
+
image and runs the suite through Bowtie's harness protocol. It needs a
|
|
280
|
+
reachable container engine (Docker, or `podman machine start`) and fetches
|
|
281
|
+
Bowtie through `uvx`:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
uv run python scripts/bowtie_check.py
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Benchmarks
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
uv run python scripts/bench.py --budget-ms 250 --filter user
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
`scripts/bench.py` times the compiler tier's flag and standalone artifacts
|
|
294
|
+
against the interpreter and two competitors (fastjsonschema, jsonschema)
|
|
295
|
+
over seven corpora in `packages/bench`: three small hand-authored schemas,
|
|
296
|
+
the official OpenAPI 3.1 schema against a real document, a generated
|
|
297
|
+
API-payload corpus, and two 2000-record corpora. It is report-only (it
|
|
298
|
+
enforces no performance threshold) and, per the IP policy below, runs the
|
|
299
|
+
competitors only, never reading or porting their source. `--filter` takes
|
|
300
|
+
a regex over corpus/subject/partition names; omit `--out` to skip writing
|
|
301
|
+
JSON; `--compare BEFORE AFTER` prints a before/after comparison of two
|
|
302
|
+
results files. The committed run lives at
|
|
303
|
+
`packages/bench/results/results.json` (`--budget-ms 250`). The interpreter
|
|
304
|
+
is the reference semantics, so ratios are informational, not a
|
|
305
|
+
compatibility claim. [`packages/bench/README.md`](packages/bench/README.md)
|
|
306
|
+
covers corpus provenance and licensing, methodology, and the recorded
|
|
307
|
+
exclusions.
|
|
308
|
+
|
|
309
|
+
## IP policy
|
|
310
|
+
|
|
311
|
+
The implementation is written from the JSON Schema specifications and the
|
|
312
|
+
official test suite only. Other validators are executed as benchmark subjects
|
|
313
|
+
and correctness oracles; their source is never used as an implementation
|
|
314
|
+
reference. See [DESIGN.md](DESIGN.md) §0.
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# json-schema-engine (Python)
|
|
2
|
+
|
|
3
|
+
A JSON Schema implementation for Python that aims to be both spec-complete
|
|
4
|
+
and built for speed: an interpreter that is the reference semantics, a
|
|
5
|
+
compiler tier for hot paths, full annotation collection, and every standard
|
|
6
|
+
output format. It is the Python counterpart of
|
|
7
|
+
[handrews/json-schema-engine](https://github.com/handrews/json-schema-engine)
|
|
8
|
+
and shares its architecture: two tiers, one keyword registry, a frame-scoped
|
|
9
|
+
record channel, and the new (2026)
|
|
10
|
+
[IETF working group draft-03](https://www.ietf.org/archive/id/draft-ietf-jsonschema-json-schema-03.html)
|
|
11
|
+
relevance model.
|
|
12
|
+
|
|
13
|
+
Produced by Henry Andrews via Claude Code.
|
|
14
|
+
|
|
15
|
+
**Status: `0.0.2` is the first functional release.**
|
|
16
|
+
|
|
17
|
+
All `0.0.x` releases will have AI-written documentation. Version
|
|
18
|
+
`0.1.0` will indicate that the documentation has been audited and revised
|
|
19
|
+
by a human.
|
|
20
|
+
|
|
21
|
+
As of `0.0.2`, this package carries the
|
|
22
|
+
interpreter core for 2020-12, 2019-09, draft-07, and draft-06 with every
|
|
23
|
+
standard output format, the compiler tier (a flag validator and standalone
|
|
24
|
+
modules, M6–M7), and the `json_schema_engine.formats` package — green on
|
|
25
|
+
every official test-suite file for those drafts through both tiers, on the
|
|
26
|
+
official output-tests, and on Bowtie. [DESIGN.md](DESIGN.md) is the design
|
|
27
|
+
contract and carries the milestone status.
|
|
28
|
+
|
|
29
|
+
The regular-expression translator lives in its own package,
|
|
30
|
+
[`ecma-regex`](packages/ecma-regex/README.md) (`0.1.0`): ECMA-262 patterns
|
|
31
|
+
for Python, with JavaScript semantics, no dependency on this engine.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
pip install json-schema-engine
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Python 3.12+. Extras: `[idna]` for IDNA2008 support (`idn-hostname`, and the
|
|
40
|
+
`xn--` label check inside `hostname`); `[regex]` to swap in the `regex`
|
|
41
|
+
package as the pattern backend.
|
|
42
|
+
|
|
43
|
+
## Use
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from json_schema_engine.core import create_engine
|
|
47
|
+
|
|
48
|
+
engine = create_engine()
|
|
49
|
+
uri = engine.register_schema(
|
|
50
|
+
{
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {"name": {"type": "string"}},
|
|
53
|
+
"required": ["name"],
|
|
54
|
+
},
|
|
55
|
+
"https://example.com/person",
|
|
56
|
+
)
|
|
57
|
+
assert engine.evaluate(uri, {"name": "Ada"}).valid
|
|
58
|
+
result = engine.evaluate(uri, {}, output="list")
|
|
59
|
+
assert result.errors is not None and result.errors[0]["evaluationPath"] == "/required"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Four drafts are built in: 2020-12 (the default), 2019-09, draft-07, and
|
|
63
|
+
draft-06. A document's `$schema` selects its dialect; `create_engine(
|
|
64
|
+
default_dialect=DIALECT_DRAFT_07)` sets the dialect for documents without
|
|
65
|
+
one. Each keeps its own semantics, so a draft-07 `$ref` ignores its
|
|
66
|
+
siblings while a 2019-09 one does not.
|
|
67
|
+
|
|
68
|
+
Every standard output format is available by name: `flag` (the default),
|
|
69
|
+
`basic`, `detailed`, and `verbose` from the IETF draft-03 output spec, and
|
|
70
|
+
`list` and `hierarchical` from the machines-oriented proposal. Annotations
|
|
71
|
+
are a separate control (`annotations=True`, or an `AnnotationSelection`),
|
|
72
|
+
`verbose=True` asks `list`/`hierarchical` for the verbose level with
|
|
73
|
+
irrelevant records marked as dropped, and `trace=True` adds the application
|
|
74
|
+
tree with error indexes into `result.errors`.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
result = engine.evaluate(uri, {"name": 3}, output="hierarchical")
|
|
78
|
+
assert result.output_document == {
|
|
79
|
+
"valid": False,
|
|
80
|
+
"evaluationPath": "",
|
|
81
|
+
"schemaLocation": "https://example.com/person#",
|
|
82
|
+
"instanceLocation": "",
|
|
83
|
+
"details": [
|
|
84
|
+
{
|
|
85
|
+
"valid": False,
|
|
86
|
+
"evaluationPath": "/properties/name",
|
|
87
|
+
"schemaLocation": "https://example.com/person#/properties/name",
|
|
88
|
+
"instanceLocation": "/name",
|
|
89
|
+
"errors": {"type": "expected string"},
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`create_engine(validate_schemas=True)` checks every registered document
|
|
96
|
+
against its metaschema and raises `SchemaValidationError` with the errors.
|
|
97
|
+
A loader that reports source positions (see `parse_json_with_ranges`,
|
|
98
|
+
exported from `json_schema_engine.core`) lets `evaluate(..., positions=True)`
|
|
99
|
+
attach a `source` location to every error and annotation, and
|
|
100
|
+
`engine.locate()` answers the same question for any schema location.
|
|
101
|
+
|
|
102
|
+
## Compile
|
|
103
|
+
|
|
104
|
+
The compiler tier turns a registered schema into a Python function. It is
|
|
105
|
+
not a second implementation: any subschema it cannot emit (a `$dynamicRef`,
|
|
106
|
+
an `unevaluated*` whose coverage is only known at runtime, an in-place
|
|
107
|
+
cycle) calls back into the interpreter, so a compiled validator is exactly
|
|
108
|
+
as correct as `Engine.evaluate` and never less complete. Tier choice is a
|
|
109
|
+
performance decision, not a semantic one.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from json_schema_engine.compiler import compile_validator, emit_standalone
|
|
113
|
+
|
|
114
|
+
compiled = compile_validator(engine, uri)
|
|
115
|
+
assert compiled.validate({"name": "Ada"}) is True
|
|
116
|
+
assert compiled.validate({}) is False
|
|
117
|
+
print(compiled.source) # the emitted module, for reading
|
|
118
|
+
module_source = emit_standalone(engine, uri) # importable without the compiler
|
|
119
|
+
assert "def validate" in module_source
|
|
120
|
+
assert "import json_schema_engine.compiler" not in module_source
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`compile_validator` returns a verdict-only validator (the flag level) that
|
|
124
|
+
binds a snapshot of the registries at compile time, so register everything
|
|
125
|
+
first. `emit_standalone` writes the same code as a module that imports
|
|
126
|
+
only the standard library and this package's pure helpers; it refuses,
|
|
127
|
+
with `StandaloneUnsupportedError`, a schema that would need the
|
|
128
|
+
interpreter at evaluation time. Compiled code assumes plain data as
|
|
129
|
+
`json.loads` produces it (`dict`, `list`, `str`, `int`, `float`, `bool`,
|
|
130
|
+
`None`); subclasses of those types belong to the interpreter. Errors,
|
|
131
|
+
annotations, and the output formats are interpreter features today;
|
|
132
|
+
compiled output beyond the verdict is a later milestone.
|
|
133
|
+
|
|
134
|
+
## Formats
|
|
135
|
+
|
|
136
|
+
`format` annotates by default in every dialect (the specs' default, and
|
|
137
|
+
what the official `format.json` legs require). Assertion is opt-in, from a
|
|
138
|
+
format table implemented from each format's RFC and verified against the
|
|
139
|
+
official `optional/format` suite:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from json_schema_engine.core import create_engine
|
|
143
|
+
from json_schema_engine.formats import FORMATS_2020_12, format_table_for
|
|
144
|
+
|
|
145
|
+
# The 2020-12 format-assertion vocabulary: a metaschema declaring it makes
|
|
146
|
+
# `format` assert; names the table lacks are refused at registration.
|
|
147
|
+
engine = create_engine(formats=FORMATS_2020_12)
|
|
148
|
+
# Best effort in every standard dialect: known names assert, unknown names
|
|
149
|
+
# annotate only.
|
|
150
|
+
engine = create_engine(formats=FORMATS_2020_12, assert_formats=True)
|
|
151
|
+
uri = engine.register_schema({"format": "date-time"}, "https://example.com/dt")
|
|
152
|
+
assert engine.evaluate(uri, "1998-12-31T23:59:60Z").valid
|
|
153
|
+
assert not engine.evaluate(uri, "1998-12-31T22:59:60Z").valid
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`FORMATS_2020_12` (also 2019-09) carries the nineteen defined formats;
|
|
157
|
+
`FORMATS_DRAFT_07` and `FORMATS_DRAFT_06` carry each draft's list, and
|
|
158
|
+
`format_table_for(dialect_uri)` picks one. A metaschema that declares the
|
|
159
|
+
format-assertion vocabulary on an engine without a table raises
|
|
160
|
+
`FormatsRequiredError`, and `assert_formats=True` without a table does too.
|
|
161
|
+
A custom table is any mapping of names to `FormatDefinition(test, types)`;
|
|
162
|
+
`types` scopes a format to instance types other than strings.
|
|
163
|
+
|
|
164
|
+
`idn-hostname` and the A-label checks inside `hostname` need IDNA2008,
|
|
165
|
+
provided by the `idna` extra:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
pip install 'json-schema-engine[idna]'
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Without it, asserting `idn-hostname` raises `FormatUnavailableError` at
|
|
172
|
+
registration, and `hostname` accepts a well-formed `xn--` label without
|
|
173
|
+
decoding it. Compiled validators assert formats too; a standalone module
|
|
174
|
+
imports the predicates it needs from `json_schema_engine.formats`, so the
|
|
175
|
+
extra must be installed wherever such a module runs.
|
|
176
|
+
|
|
177
|
+
## Documentation
|
|
178
|
+
|
|
179
|
+
- [User guide](docs/guide/index.md) — validation, output formats,
|
|
180
|
+
annotations, dialects, loaders, source positions, metaschemas, custom
|
|
181
|
+
keywords, security, compiling schemas, and formats, topic by topic.
|
|
182
|
+
- [API reference](docs/reference.md) — every public name, by package.
|
|
183
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — setup, gates, and conventions.
|
|
184
|
+
- [CHANGELOG.md](CHANGELOG.md) — what changed, release by release.
|
|
185
|
+
- [DESIGN.md](DESIGN.md) — the engineering design contract and milestone
|
|
186
|
+
status.
|
|
187
|
+
|
|
188
|
+
## Security
|
|
189
|
+
|
|
190
|
+
Schemas and instances are both often untrusted input. The interpreter
|
|
191
|
+
generates no code — there is no `compile()` or `exec()` on its path — so
|
|
192
|
+
code-injection concerns do not apply to it; a denial-of-service bound is
|
|
193
|
+
best effort, not a guarantee, so treat wildly untrusted schemas with the
|
|
194
|
+
same care as any other untrusted program input. Three specific vectors have
|
|
195
|
+
a bound or an opt-out.
|
|
196
|
+
|
|
197
|
+
**Regular expressions (ReDoS).** `pattern` and `patternProperties` compile
|
|
198
|
+
untrusted regexes and run them against untrusted strings; Python's `re` can
|
|
199
|
+
backtrack catastrophically on a pattern like `(a+)+$`. `reject_unsafe_regex`
|
|
200
|
+
screens for nested unbounded quantifiers at registration:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from json_schema_engine.core import create_engine, UnsafeRegexError
|
|
204
|
+
|
|
205
|
+
engine = create_engine(reject_unsafe_regex=True)
|
|
206
|
+
try:
|
|
207
|
+
engine.register_schema({"pattern": "(a+)+$"}, "https://ex/redos")
|
|
208
|
+
except UnsafeRegexError:
|
|
209
|
+
pass # rejected before it ever runs
|
|
210
|
+
else:
|
|
211
|
+
raise AssertionError("expected UnsafeRegexError")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Patterns are ECMA-262 by default, translated by the `ecma-regex` package to
|
|
215
|
+
the `re` backend (a `regex` backend is available via the `regex` extra);
|
|
216
|
+
neither backend is linear-time, so the screen is a heuristic, not a proof.
|
|
217
|
+
`regex_dialect="python"` hands patterns to `re` untouched, for schemas
|
|
218
|
+
written for Python only.
|
|
219
|
+
|
|
220
|
+
**Recursion depth.** `max_depth` (default 512) bounds both registration
|
|
221
|
+
nesting and evaluation nesting, raising the typed `MaxDepthExceededError`
|
|
222
|
+
before CPython's own stack limit can produce an untyped `RecursionError`; a
|
|
223
|
+
stray `RecursionError` that does slip through is still converted to the
|
|
224
|
+
same typed error. The engine stays usable afterward — each `evaluate` or
|
|
225
|
+
`register_schema` call runs in fresh state, so a rejected document does not
|
|
226
|
+
poison later calls.
|
|
227
|
+
|
|
228
|
+
**Array uniqueness.** `uniqueItems` compares elements in O(n) by bucketing
|
|
229
|
+
on a canonical key and confirming collisions with full JSON equality, so
|
|
230
|
+
large arrays of distinct values do not incur quadratic cost, while genuine
|
|
231
|
+
duplicates — including numbers equal across `int`/`float` and objects that
|
|
232
|
+
differ only in member order — are still reported.
|
|
233
|
+
|
|
234
|
+
**No prototype hazard.** Python dicts have no prototype chain, so there is
|
|
235
|
+
nothing for a hostile property name to pollute: `__proto__`, `constructor`,
|
|
236
|
+
and similar reserved-looking names evaluate as ordinary properties.
|
|
237
|
+
|
|
238
|
+
## Development
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
uv sync --all-packages --all-groups
|
|
242
|
+
uv run pytest -q
|
|
243
|
+
uv run ruff check . && uv run ruff format --check .
|
|
244
|
+
uv run pyright
|
|
245
|
+
uv run lint-imports
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The official test suite is a git submodule at `test-suite/`; clone with
|
|
249
|
+
`--recurse-submodules` or run `git submodule update --init`.
|
|
250
|
+
|
|
251
|
+
The [Bowtie](https://bowtie.report) conformance leg builds a container
|
|
252
|
+
image and runs the suite through Bowtie's harness protocol. It needs a
|
|
253
|
+
reachable container engine (Docker, or `podman machine start`) and fetches
|
|
254
|
+
Bowtie through `uvx`:
|
|
255
|
+
|
|
256
|
+
```sh
|
|
257
|
+
uv run python scripts/bowtie_check.py
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Benchmarks
|
|
261
|
+
|
|
262
|
+
```sh
|
|
263
|
+
uv run python scripts/bench.py --budget-ms 250 --filter user
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`scripts/bench.py` times the compiler tier's flag and standalone artifacts
|
|
267
|
+
against the interpreter and two competitors (fastjsonschema, jsonschema)
|
|
268
|
+
over seven corpora in `packages/bench`: three small hand-authored schemas,
|
|
269
|
+
the official OpenAPI 3.1 schema against a real document, a generated
|
|
270
|
+
API-payload corpus, and two 2000-record corpora. It is report-only (it
|
|
271
|
+
enforces no performance threshold) and, per the IP policy below, runs the
|
|
272
|
+
competitors only, never reading or porting their source. `--filter` takes
|
|
273
|
+
a regex over corpus/subject/partition names; omit `--out` to skip writing
|
|
274
|
+
JSON; `--compare BEFORE AFTER` prints a before/after comparison of two
|
|
275
|
+
results files. The committed run lives at
|
|
276
|
+
`packages/bench/results/results.json` (`--budget-ms 250`). The interpreter
|
|
277
|
+
is the reference semantics, so ratios are informational, not a
|
|
278
|
+
compatibility claim. [`packages/bench/README.md`](packages/bench/README.md)
|
|
279
|
+
covers corpus provenance and licensing, methodology, and the recorded
|
|
280
|
+
exclusions.
|
|
281
|
+
|
|
282
|
+
## IP policy
|
|
283
|
+
|
|
284
|
+
The implementation is written from the JSON Schema specifications and the
|
|
285
|
+
official test suite only. Other validators are executed as benchmark subjects
|
|
286
|
+
and correctness oracles; their source is never used as an implementation
|
|
287
|
+
reference. See [DESIGN.md](DESIGN.md) §0.
|