formats: JSON Schema format assertion for the formats std can parse (step 4a of #80) #697

Closed
opened 2026-10-30 15:39:37 +00:00 by orion · 2 comments
Collaborator

Context

Step 4a of the epic #80 (step 4 is split just in time: 4a here, 4b the hand-written formats and the content* keywords). Steps 1, 2a, 2b and 3 have landed. std/json-schema validates every in-place keyword and reference of 2020-12, but format is neither stored in the model nor checked, and tests/suite.test.trb lists format and content under fileExclusions. The specification lets a validator treat format as an annotation or as an assertion; the epic decided annotation by default, assertion by option.

Goal

format becomes a keyword of the model, and a caller can ask validate to assert it for the formats whose syntax std already parses.

Source

  • JSON Schema 2020-12 Validation, section 7 (format) and its section 7.3 (defined formats); ROADMAP.md, format versions.
  • Epic #80, step 4. Layout from #434 to #690: src/schema.trb (model, reader), src/keywords.trb, src/validate.trb, tests/suite.test.trb (exclusions, fileExclusions), tests/suite-cases.trb (generated).

Scope

  • src/schema.trb: field format: String?, read from format (a string, else a JsonSchemaError at the keyword); kept as an annotation as before when not asserted.
  • src/validate.trb: validate(instance, registry = SchemaRegistry(), assertFormat: Bool = false); the flag travels in the Frame. Where it is true, a string document that does not satisfy the named format fails with keyword format (location /format, message names the format). Non-strings and unknown format names always pass, as the specification says.
  • New src/formats.trb: fn formatFailure(name: String, text: String): String?, one function per format, each built on the parser std has:
    date-time, date, time (std/time: OffsetDateTime, Date, TimeOfDay+UtcOffset, RFC 3339 strict: upper-case T/Z, leap second 23:59:60 accepted only at UTC 23:59:60),
    ipv4, ipv6 (std/ip, Ipv4Address, Ipv6Address),
    uri, uri-reference, iri, iri-reference (std/uri, Uri, UriReference, the IRI forms of std/uri's iri),
    uri-template (UriTemplate), uuid (Uuid), regex (std/regex, Regex),
    json-pointer (JsonPointer.parse) and relative-json-pointer (non-negative integer without leading zeros, optional #, or a JSON Pointer).
  • Formats not in this list (email, idn-email, hostname, idn-hostname, duration) pass until step 4b; they are named in a documented list on the page, not silently ignored.

Acceptance criteria

  • JsonSchema.read keeps format; a non-string format is refused with the place in the schema.
  • Default validate never fails on format (annotation); with assertFormat: true each listed format accepts its valid examples and refuses its invalid ones; non-strings and unknown formats pass.
  • Suite: tests/draft2020-12/optional/format/ files for the listed formats (date-time, date, time, ipv4, ipv6, uri, uri-reference, iri, iri-reference, uri-template, uuid, regex, json-pointer, relative-json-pointer) and tests/draft2020-12/format.json are generated into suite-cases.trb (header note updated) with the valid/invalid verdict as the suite says under assertFormat: true; format drops from fileExclusions. Suite cases the std parser decides differently stay as named exclusions with the reason (a documented difference, to be fixed in the std package by its own issue, not here); the unlisted formats' files stay out.
  • Unit tests in tests/json-schema.test.trb: default is annotation-only, assertFormat fails one case per format family, error location and message, unknown format passes.
  • docs/standard-library/json-schema.md: the format keyword, the option, the table of asserted formats and the list that waits on 4b; skills/ regenerated; sh tools/gates.sh a green in forge CI.

Design record point

ROADMAP "Which versions a format supports"; ENCODING.md section 9. Decided by the loop:

  • The option is a defaulted parameter of validate, not a field of the schema: assertion is a decision of the caller who validates, not a property of the schema text (the specification names it an implementation choice), and it keeps validate(instance) call sites unchanged. The vocabulary route (format-assertion in a meta-schema's $vocabulary) is step 4b with its meta-schema.
  • Checkers reuse the parsers of std/time, std/ip, std/uri, std/identifier, std/regex and std/json; no second grammar is written where one exists. A difference between a std parser and the suite is recorded as an exclusion and fixed in that package, not worked around here.
  • Strings only; the checkers see the document's characters, no normalization.

Seed impact

None: the compiler does not import std/json-schema and no std name is looked up by string. No new native, no syntax change.

Size

One PR, two Conventional Commits: (1) feat(std/json-schema): assert format on request (model field, option, formats.trb, unit tests); (2) test(std/json-schema): run the format suite files (regenerated cases, exclusions, docs, skill regeneration).

Dependencies

Needs #690 (landed). Part of #80; step 4b follows (email, hostname, duration, format-assertion vocabulary, content*).

## Context Step 4a of the epic #80 (step 4 is split just in time: 4a here, 4b the hand-written formats and the `content*` keywords). Steps 1, 2a, 2b and 3 have landed. `std/json-schema` validates every in-place keyword and reference of 2020-12, but `format` is neither stored in the model nor checked, and `tests/suite.test.trb` lists `format` and `content` under `fileExclusions`. The specification lets a validator treat `format` as an annotation or as an assertion; the epic decided annotation by default, assertion by option. ## Goal `format` becomes a keyword of the model, and a caller can ask `validate` to assert it for the formats whose syntax `std` already parses. ## Source - JSON Schema 2020-12 Validation, section 7 (format) and its section 7.3 (defined formats); [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/branch/main/docs/ROADMAP.md). - Epic #80, step 4. Layout from #434 to #690: `src/schema.trb` (model, reader), `src/keywords.trb`, `src/validate.trb`, `tests/suite.test.trb` (`exclusions`, `fileExclusions`), `tests/suite-cases.trb` (generated). ## Scope - `src/schema.trb`: field `format: String?`, read from `format` (a string, else a `JsonSchemaError` at the keyword); kept as an annotation as before when not asserted. - `src/validate.trb`: `validate(instance, registry = SchemaRegistry(), assertFormat: Bool = false)`; the flag travels in the `Frame`. Where it is `true`, a string document that does not satisfy the named format fails with keyword `format` (location `/format`, message names the format). Non-strings and unknown format names always pass, as the specification says. - New `src/formats.trb`: `fn formatFailure(name: String, text: String): String?`, one function per format, each built on the parser `std` has: `date-time`, `date`, `time` (`std/time`: `OffsetDateTime`, `Date`, `TimeOfDay`+`UtcOffset`, RFC 3339 strict: upper-case `T`/`Z`, leap second 23:59:60 accepted only at UTC 23:59:60), `ipv4`, `ipv6` (`std/ip`, `Ipv4Address`, `Ipv6Address`), `uri`, `uri-reference`, `iri`, `iri-reference` (`std/uri`, `Uri`, `UriReference`, the IRI forms of `std/uri`'s `iri`), `uri-template` (`UriTemplate`), `uuid` (`Uuid`), `regex` (`std/regex`, `Regex`), `json-pointer` (`JsonPointer.parse`) and `relative-json-pointer` (non-negative integer without leading zeros, optional `#`, or a JSON Pointer). - Formats not in this list (`email`, `idn-email`, `hostname`, `idn-hostname`, `duration`) pass until step 4b; they are named in a documented list on the page, not silently ignored. ## Acceptance criteria - [ ] `JsonSchema.read` keeps `format`; a non-string `format` is refused with the place in the schema. - [ ] Default `validate` never fails on `format` (annotation); with `assertFormat: true` each listed format accepts its valid examples and refuses its invalid ones; non-strings and unknown formats pass. - [ ] Suite: `tests/draft2020-12/optional/format/` files for the listed formats (`date-time`, `date`, `time`, `ipv4`, `ipv6`, `uri`, `uri-reference`, `iri`, `iri-reference`, `uri-template`, `uuid`, `regex`, `json-pointer`, `relative-json-pointer`) and `tests/draft2020-12/format.json` are generated into `suite-cases.trb` (header note updated) with the valid/invalid verdict as the suite says under `assertFormat: true`; `format` drops from `fileExclusions`. Suite cases the std parser decides differently stay as named `exclusions` with the reason (a documented difference, to be fixed in the std package by its own issue, not here); the unlisted formats' files stay out. - [ ] Unit tests in `tests/json-schema.test.trb`: default is annotation-only, `assertFormat` fails one case per format family, error location and message, unknown format passes. - [ ] `docs/standard-library/json-schema.md`: the `format` keyword, the option, the table of asserted formats and the list that waits on 4b; `skills/` regenerated; `sh tools/gates.sh a` green in forge CI. ## Design record point ROADMAP "Which versions a format supports"; ENCODING.md section 9. Decided by the loop: - The option is a defaulted parameter of `validate`, not a field of the schema: assertion is a decision of the caller who validates, not a property of the schema text (the specification names it an implementation choice), and it keeps `validate(instance)` call sites unchanged. The vocabulary route (`format-assertion` in a meta-schema's `$vocabulary`) is step 4b with its meta-schema. - Checkers reuse the parsers of `std/time`, `std/ip`, `std/uri`, `std/identifier`, `std/regex` and `std/json`; no second grammar is written where one exists. A difference between a `std` parser and the suite is recorded as an exclusion and fixed in that package, not worked around here. - Strings only; the checkers see the document's characters, no normalization. ## Seed impact None: the compiler does not import `std/json-schema` and no std name is looked up by string. No new native, no syntax change. ## Size One PR, two Conventional Commits: (1) `feat(std/json-schema): assert format on request` (model field, option, `formats.trb`, unit tests); (2) `test(std/json-schema): run the format suite files` (regenerated cases, exclusions, docs, skill regeneration). ## Dependencies Needs #690 (landed). Part of #80; step 4b follows (email, hostname, duration, `format-assertion` vocabulary, `content*`).
Author
Collaborator

[loop:torbscript] claim implementation by session:loop-torbscript-20261030 @anthropic:tkoehn until 2026-10-30T22:42Z

[loop:torbscript] claim implementation by session:loop-torbscript-20261030 @anthropic:tkoehn until 2026-10-30T22:42Z
Author
Collaborator

[loop:torbscript] release implementation by session:loop-torbscript-20261030 @anthropic:tkoehn: worker interrupted by a gateway restart after opening PR #700 (head 4bc6eb4b, mergeable, argus requested, not WIP). CI is still pending (gates, seed pack, archives, tier A); if it ends red, a fix worker takes it.
next: review

[loop:torbscript] release implementation by session:loop-torbscript-20261030 @anthropic:tkoehn: worker interrupted by a gateway restart after opening PR #700 (head 4bc6eb4b, mergeable, argus requested, not WIP). CI is still pending (gates, seed pack, archives, tier A); if it ends red, a fix worker takes it. next: review
argus closed this issue 2026-10-30 19:38:24 +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#697
No description provided.