formats: JSON Schema validation of the 2020-12 in-place keywords in std/json-schema (step 1 of #80) #434

Closed
opened 2026-10-23 14:55:46 +00:00 by orion · 2 comments
Collaborator

Context

Step 1 of #80 (std/json-schema). The package, the schema model and a validator for the in-place keywords of draft 2020-12: read a schema from JSON, check a JSON document against it, get the list of failures with the places they were found. No references ($ref, $dynamicRef), no unevaluated*, no earlier drafts, no writing and no derivation from a type yet; those are the later steps of the epic.

Source

  • ROADMAP.md, format versions: JSON Schema 2020-12, 2019-09, draft 07 (06 read as 07), draft 04.
  • ENCODING.md section 9: formats are packages on Encode/Decode; the versions of one format share one model.
  • std/toml and std/xml are the layout model (format value, tree, parser, vendored public suite, docs page); std/json provides JsonValue, the document the validator reads.

Design record point

ROADMAP "Which versions a format supports" (the JSON Schema row: one model, an earlier draft is read into the current one) and ENCODING.md section 9. Decided by the loop:

  • Package std/json-schema (std/json-schema/project.trb, src/lib.trb re-exporting from schema.trb, keywords.trb, validate.trb, error.trb), written in TorbScript, no native, not in the prelude. Hyphenated like the planned std/language-model.
  • public type JsonSchema is the model of the current draft (2020-12 vocabulary), built from a JsonValue or from text: JsonSchema.parse(value: JsonValue): Result<JsonSchema, JsonSchemaError> and JsonSchema.read(text: String): Result<JsonSchema, JsonSchemaError>. A boolean schema is a schema. Methods, not free functions (#146). Exact names beyond these follow the conventions of std/toml and std/xml.
  • JsonSchema.validate(instance: JsonValue): Validation, where Validation has valid: Bool and errors: List<ValidationError>; a ValidationError has the keyword, the schemaLocation and the instanceLocation (both JSON Pointer text) and a message. Output is the "basic" shape of the specification, flattened. Validation implements Show.
  • Keywords validated: type, enum, const; multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum; maxLength, minLength, pattern; maxItems, minItems, uniqueItems, maxContains, minContains, prefixItems, items, contains; maxProperties, minProperties, required, dependentRequired, dependentSchemas, properties, patternProperties, additionalProperties, propertyNames; allOf, anyOf, oneOf, not, if/then/else. The annotation keywords (title, description, default, examples, deprecated, readOnly, writeOnly, $comment) are read and kept, not evaluated; an unknown keyword is ignored, as the specification says.
  • $schema: absent or the 2020-12 URI is accepted; any other value is a JsonSchemaError that names it, until its step adds the draft (never a silent misread). A schema using $ref, $dynamicRef or unevaluated* is likewise refused with a clear error, so step 1 never answers "valid" for a keyword it does not check.
  • pattern and patternProperties run on std/regex (RE2 syntax, linear time). Where ECMA 262 syntax outside RE2 (lookahead, backreferences) is used, the pattern is a JsonSchemaError at read time; the cases of the suite that need it are listed as exclusions in the test with the reason.
  • JsonValue.Number is a Float64: integer-ness (type: "integer", 1.0 counts as an integer), multipleOf and the comparison keywords follow the specification on that representation; arbitrary-precision cases are the suite's optional bignum file and are excluded with that reason.

Acceptance criteria

  • use JsonSchema, Validation, ValidationError, JsonSchemaError from "std/json-schema" works; JsonSchema.read(text) reads a 2020-12 schema and validate(instance) answers valid or the list of failures, one ValidationError per failed keyword, with instanceLocation as a JSON Pointer (for example /items/2/name).
  • Every keyword listed above is validated per draft 2020-12; the three refusals (other $schema, a keyword of a later step, a pattern outside RE2) are JsonSchemaErrors that name what was refused.
  • The public JSON-Schema-Test-Suite (MIT License) is vendored as a generated std/json-schema/tests/suite-cases.trb with its notice and the pinned release tag in the header (the model is std/yaml/tests/suite-cases.trb), for the files of tests/draft2020-12/ that are about the keywords above; each case is its own test and passes. Files and cases about $ref, $dynamicRef, unevaluated*, format, content, vocabulary, optional/ and the bignum file are excluded by name, and every other exclusion (a case that needs a reference, a lookahead pattern) is listed in the test with its reason, so the exclusions shrink with the later steps.
  • A page docs/standard-library/json-schema.md (front matter as json.md, a trb check example that validates a small document and prints the failures), listed in docs/standard-library/index.md; torb docs check, docs index --check, docs skill --check and doc --check --no-run std are green; ROADMAP's Formats item notes the package as started (JSON Schema validation, 2020-12 in-place keywords) without claiming the epic done.
  • Gates: tier A green in forge CI (sh tools/gates.sh a); every public declaration has its doc comment; touched files pass torb format --check.

Touches the seed?

No. std/json-schema is a new package that the compiler does not import, and no std name is looked up by string, so no two-commit rule applies. One or two Conventional Commits: feat(std/json-schema): validate JSON against the in-place keywords of 2020-12 and, if the diff is large, docs(std/json-schema): ... (page, index, regenerated skills/).

Dependencies

  • None blocking: std/json (JsonValue), std/regex and std/test exist.
  • Step 1 of the epic #80; unblocks its steps 2 to 9 and, through #80, #76 (OpenAPI).
  • Independent of #81 (JSON Patch).
## Context Step 1 of #80 (`std/json-schema`). The package, the schema model and a validator for the in-place keywords of draft 2020-12: read a schema from JSON, check a JSON document against it, get the list of failures with the places they were found. No references (`$ref`, `$dynamicRef`), no `unevaluated*`, no earlier drafts, no writing and no derivation from a type yet; those are the later steps of the epic. ## Source - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L185): JSON Schema 2020-12, 2019-09, draft 07 (06 read as 07), draft 04. - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/ENCODING.md#L709): formats are packages on `Encode`/`Decode`; the versions of one format share one model. - `std/toml` and `std/xml` are the layout model (format value, tree, parser, vendored public suite, docs page); `std/json` provides `JsonValue`, the document the validator reads. ## Design record point ROADMAP "Which versions a format supports" (the JSON Schema row: one model, an earlier draft is read into the current one) and ENCODING.md section 9. Decided by the loop: - Package `std/json-schema` (`std/json-schema/project.trb`, `src/lib.trb` re-exporting from `schema.trb`, `keywords.trb`, `validate.trb`, `error.trb`), written in TorbScript, no `native`, not in the prelude. Hyphenated like the planned `std/language-model`. - `public type JsonSchema` is the model of the current draft (2020-12 vocabulary), built from a `JsonValue` or from text: `JsonSchema.parse(value: JsonValue): Result<JsonSchema, JsonSchemaError>` and `JsonSchema.read(text: String): Result<JsonSchema, JsonSchemaError>`. A boolean schema is a schema. Methods, not free functions (#146). Exact names beyond these follow the conventions of `std/toml` and `std/xml`. - `JsonSchema.validate(instance: JsonValue): Validation`, where `Validation` has `valid: Bool` and `errors: List<ValidationError>`; a `ValidationError` has the `keyword`, the `schemaLocation` and the `instanceLocation` (both JSON Pointer text) and a `message`. Output is the "basic" shape of the specification, flattened. `Validation` implements `Show`. - Keywords validated: `type`, `enum`, `const`; `multipleOf`, `maximum`, `exclusiveMaximum`, `minimum`, `exclusiveMinimum`; `maxLength`, `minLength`, `pattern`; `maxItems`, `minItems`, `uniqueItems`, `maxContains`, `minContains`, `prefixItems`, `items`, `contains`; `maxProperties`, `minProperties`, `required`, `dependentRequired`, `dependentSchemas`, `properties`, `patternProperties`, `additionalProperties`, `propertyNames`; `allOf`, `anyOf`, `oneOf`, `not`, `if`/`then`/`else`. The annotation keywords (`title`, `description`, `default`, `examples`, `deprecated`, `readOnly`, `writeOnly`, `$comment`) are read and kept, not evaluated; an unknown keyword is ignored, as the specification says. - `$schema`: absent or the 2020-12 URI is accepted; any other value is a `JsonSchemaError` that names it, until its step adds the draft (never a silent misread). A schema using `$ref`, `$dynamicRef` or `unevaluated*` is likewise refused with a clear error, so step 1 never answers "valid" for a keyword it does not check. - `pattern` and `patternProperties` run on `std/regex` (RE2 syntax, linear time). Where ECMA 262 syntax outside RE2 (lookahead, backreferences) is used, the pattern is a `JsonSchemaError` at read time; the cases of the suite that need it are listed as exclusions in the test with the reason. - `JsonValue.Number` is a `Float64`: integer-ness (`type: "integer"`, `1.0` counts as an integer), `multipleOf` and the comparison keywords follow the specification on that representation; arbitrary-precision cases are the suite's optional `bignum` file and are excluded with that reason. ## Acceptance criteria - [ ] `use JsonSchema, Validation, ValidationError, JsonSchemaError from "std/json-schema"` works; `JsonSchema.read(text)` reads a 2020-12 schema and `validate(instance)` answers `valid` or the list of failures, one `ValidationError` per failed keyword, with `instanceLocation` as a JSON Pointer (for example `/items/2/name`). - [ ] Every keyword listed above is validated per draft 2020-12; the three refusals (other `$schema`, a keyword of a later step, a pattern outside RE2) are `JsonSchemaError`s that name what was refused. - [ ] The public [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) (MIT License) is vendored as a generated `std/json-schema/tests/suite-cases.trb` with its notice and the pinned release tag in the header (the model is `std/yaml/tests/suite-cases.trb`), for the files of `tests/draft2020-12/` that are about the keywords above; each case is its own test and passes. Files and cases about `$ref`, `$dynamicRef`, `unevaluated*`, `format`, `content`, `vocabulary`, `optional/` and the `bignum` file are excluded by name, and every other exclusion (a case that needs a reference, a lookahead pattern) is listed in the test with its reason, so the exclusions shrink with the later steps. - [ ] A page `docs/standard-library/json-schema.md` (front matter as `json.md`, a `trb check` example that validates a small document and prints the failures), listed in `docs/standard-library/index.md`; `torb docs check`, `docs index --check`, `docs skill --check` and `doc --check --no-run std` are green; ROADMAP's Formats item notes the package as started (JSON Schema validation, 2020-12 in-place keywords) without claiming the epic done. - [ ] Gates: tier A green in forge CI (`sh tools/gates.sh a`); every public declaration has its doc comment; touched files pass `torb format --check`. ## Touches the seed? No. `std/json-schema` is a new package that the compiler does not import, and no std name is looked up by string, so no two-commit rule applies. One or two Conventional Commits: `feat(std/json-schema): validate JSON against the in-place keywords of 2020-12` and, if the diff is large, `docs(std/json-schema): ...` (page, index, regenerated `skills/`). ## Dependencies - None blocking: `std/json` (`JsonValue`), `std/regex` and `std/test` exist. - Step 1 of the epic #80; unblocks its steps 2 to 9 and, through #80, #76 (OpenAPI). - Independent of #81 (JSON Patch).
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T21:02Z

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T21:02Z
Author
Collaborator

[loop:torbscript] implement: std/json-schema reads 2020-12 schemas and validates the in-place keywords (694 suite cases from JSON-Schema-Test-Suite 23.1.0, two groups mixing in later-step keywords listed as refused), page + index + skills + ROADMAP note added, one docs-declarations fix after the first CI round, PR #436, CI green (tier A, docs gates, pdisc), next: review. Tokens: about 0.3M for this item across two context windows (estimate).

[loop:torbscript] implement: std/json-schema reads 2020-12 schemas and validates the in-place keywords (694 suite cases from JSON-Schema-Test-Suite 23.1.0, two groups mixing in later-step keywords listed as refused), page + index + skills + ROADMAP note added, one docs-declarations fix after the first CI round, PR https://git.torb.dev/torbscript/language/pulls/436, CI green (tier A, docs gates, pdisc), next: review. Tokens: about 0.3M for this item across two context windows (estimate).
argus closed this issue 2026-10-23 16:07:23 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#434
No description provided.