Liking cljdoc? Tell your friends :D

com.blockether.skjema.core

JSON Schema 2020-12 evaluation.

The evaluator answers ANNOTATIONS as well as errors, from the first line and not as a later addition: unevaluatedProperties and unevaluatedItems are defined in terms of what the ADJACENT keywords and the in-place applicators already evaluated, so a validator that answers only true/false cannot grow them without being rewritten. Every evaluation therefore returns {:valid? :props :items :errors} - the property names and item indices that were evaluated AT THIS INSTANCE LOCATION, plus every fault found underneath.

Two kinds of applicator, and the difference is the whole design:

  • IN PLACE ($ref, $dynamicRef, allOf, anyOf, oneOf, if/then/ else, dependentSchemas) apply another schema to the SAME instance location, so their annotations belong to this location and bubble up.
  • CHILD (properties, patternProperties, additionalProperties, items, prefixItems, contains, propertyNames) apply to a location BELOW, so their annotations stay there and this location records only which member was covered.

Identifiers are resolved once, when the schema is compiled: $id moves the base URI, $anchor and $dynamicAnchor name a place inside it, and the index that comes out is what $ref and $dynamicRef read. Nothing is fetched over the network, ever - a schema that references a document the caller did not supply is a compile error, not a silent pass.

JSON Schema 2020-12 evaluation.

The evaluator answers ANNOTATIONS as well as errors, from the first line and
not as a later addition: `unevaluatedProperties` and `unevaluatedItems` are
defined in terms of what the ADJACENT keywords and the in-place applicators
already evaluated, so a validator that answers only true/false cannot grow
them without being rewritten. Every evaluation therefore returns
`{:valid? :props :items :errors}` - the property names and item indices that
were evaluated AT THIS INSTANCE LOCATION, plus every fault found underneath.

Two kinds of applicator, and the difference is the whole design:

- IN PLACE (`$ref`, `$dynamicRef`, `allOf`, `anyOf`, `oneOf`, `if`/`then`/
  `else`, `dependentSchemas`) apply another schema to the SAME instance
  location, so their annotations belong to this location and bubble up.
- CHILD (`properties`, `patternProperties`, `additionalProperties`, `items`,
  `prefixItems`, `contains`, `propertyNames`) apply to a location BELOW, so
  their annotations stay there and this location records only which member
  was covered.

Identifiers are resolved once, when the schema is compiled: `$id` moves the
base URI, `$anchor` and `$dynamicAnchor` name a place inside it, and the
index that comes out is what `$ref` and `$dynamicRef` read. Nothing is
fetched over the network, ever - a schema that references a document the
caller did not supply is a compile error, not a silent pass.
raw docstring

com.blockether.skjema.format

What every format the 2020-12 specification names actually asserts.

format is an ANNOTATION by default and this namespace is only consulted when a schema asks for the assertion - by declaring the format-assertion vocabulary, or because the caller compiled with :format-assertion true. An unknown format is not an error: it annotates and asserts nothing, which is why known? is a question worth asking before valid? answers one.

Every check is the grammar of the specification that owns it, written out rather than delegated: java.net.URI is RFC 2396 and accepts what RFC 3986 refuses, InetAddress resolves names, and DateTimeFormatter has no opinion about a leap second, which RFC 3339 permits at exactly one instant of the day.

What every `format` the 2020-12 specification names actually asserts.

`format` is an ANNOTATION by default and this namespace is only consulted
when a schema asks for the assertion - by declaring the format-assertion
vocabulary, or because the caller compiled with `:format-assertion true`.
An unknown format is not an error: it annotates and asserts nothing, which
is why `known?` is a question worth asking before `valid?` answers one.

Every check is the grammar of the specification that owns it, written out
rather than delegated: `java.net.URI` is RFC 2396 and accepts what RFC 3986
refuses, `InetAddress` resolves names, and `DateTimeFormatter` has no
opinion about a leap second, which RFC 3339 permits at exactly one instant
of the day.
raw docstring

com.blockether.skjema.idn

Internationalized domain names, as IDNA2008 and UTS 46 define them - what format: idn-hostname asserts and what format: idn-email asks about the part after the at sign.

A name is read the way a resolver reads it. First the whole string is MAPPED: the characters Unicode discards in a domain name are dropped, the compatibility forms are folded (so a fullwidth digit becomes an ASCII one) and everything is lower-cased. Then it is split on the four characters that separate labels - . and its ideographic, fullwidth and halfwidth twins.

Each label is then either an A-label, xn-- followed by Punycode, which is decoded and must re-encode to exactly what arrived, or a U-label, whose every code point must be PVALID by the derived property of RFC 5892 - and the handful that are only CONTEXTUALLY valid must satisfy their rule: a MIDDLE DOT between two ls, a Greek KERAIA before a Greek letter, a KATAKANA MIDDLE DOT in a label that has Japanese in it, a ZERO WIDTH JOINER after a virama, and Arabic-Indic digits that do not mix with their extended cousins. Finally, a name with any right-to-left label answers to the Bidi rule of RFC 5893, which is what makes 0a.<hebrew> invalid while <arabic><extended-indic-digit> stays valid.

Two of the tables Unicode publishes have no Java API - the derived property NFKC_CaseFold and Joining_Type - so the first is computed as NFC(lower-case(NFKC(x))) and the second from the script and category of the character, with the right-joining letters of the Arabic block written out. Both agree with the published data on everything a domain name can hold; neither is a substitute for the tables themselves.

Internationalized domain names, as IDNA2008 and UTS 46 define them - what
`format: idn-hostname` asserts and what `format: idn-email` asks about the
part after the at sign.

A name is read the way a resolver reads it. First the whole string is
MAPPED: the characters Unicode discards in a domain name are dropped, the
compatibility forms are folded (so a fullwidth digit becomes an ASCII one)
and everything is lower-cased. Then it is split on the four characters that
separate labels - `.` and its ideographic, fullwidth and halfwidth twins.

Each label is then either an A-label, `xn--` followed by Punycode, which is
decoded and must re-encode to exactly what arrived, or a U-label, whose
every code point must be PVALID by the derived property of RFC 5892 - and
the handful that are only CONTEXTUALLY valid must satisfy their rule: a
MIDDLE DOT between two `l`s, a Greek KERAIA before a Greek letter, a
KATAKANA MIDDLE DOT in a label that has Japanese in it, a ZERO WIDTH JOINER
after a virama, and Arabic-Indic digits that do not mix with their extended
cousins. Finally, a name with any right-to-left label answers to the Bidi
rule of RFC 5893, which is what makes `0a.<hebrew>` invalid while
`<arabic><extended-indic-digit>` stays valid.

Two of the tables Unicode publishes have no Java API - the derived property
NFKC_CaseFold and Joining_Type - so the first is computed as
`NFC(lower-case(NFKC(x)))` and the second from the script and category of
the character, with the right-joining letters of the Arabic block written
out. Both agree with the published data on everything a domain name can
hold; neither is a substitute for the tables themselves.
raw docstring

com.blockether.skjema.json

JSON reader and writer - RFC 8259, no dependencies, no reflection.

read-str / read-bytes answer plain Clojure data: an object is a map with STRING keys, an array a vector, a number a Long / BigInteger / Double, a string a String, true / false a Boolean and null nil. That is the only shape skjema validates: JSON Schema property names ARE strings, so keeping the keys strings means a schema and its instance are compared without a second spelling and without a round trip.

Reading is TOTAL: every rejection is an ex-info carrying :skjema/error :json/parse with :offset, :line and :column, never a partial value and never a raw Java exception. Depth is BOUNDED (:max-depth, default 1024) because recursive descent over hostile input is otherwise a StackOverflowError - an Error is not something a caller can handle, so a document that is too deep is REJECTED like any other malformed input.

This reader exists so the library depends on nothing. It is deliberately plain: schemas are read once, at compile time, and an instance normally arrives already parsed by whatever the host uses.

JSON reader and writer - RFC 8259, no dependencies, no reflection.

`read-str` / `read-bytes` answer plain Clojure data: an object is a map with
STRING keys, an array a vector, a number a Long / BigInteger / Double, a
string a String, `true` / `false` a Boolean and `null` nil. That is the only
shape skjema validates: JSON Schema property names ARE strings, so keeping
the keys strings means a schema and its instance are compared without a
second spelling and without a round trip.

Reading is TOTAL: every rejection is an `ex-info` carrying
`:skjema/error :json/parse` with `:offset`, `:line` and `:column`, never a
partial value and never a raw Java exception. Depth is BOUNDED
(`:max-depth`, default 1024) because recursive descent over hostile input is
otherwise a StackOverflowError - an Error is not something a caller can
handle, so a document that is too deep is REJECTED like any other malformed
input.

This reader exists so the library depends on nothing. It is deliberately
plain: schemas are read once, at compile time, and an instance normally
arrives already parsed by whatever the host uses.
raw docstring

com.blockether.skjema.regex

ECMAScript regular expressions in the dialect java.util.regex speaks.

pattern and patternProperties hold ECMA-262 regular expressions, and Java's are ALMOST the same language. Every difference between the two is silent - the pattern compiles on both sides and matches different strings:

  • \s is six ASCII characters in Java; in ECMAScript it is every space separator, both line terminators and the byte-order mark,
  • \v is one vertical tab in ECMAScript and a whole class of vertical whitespace in Java,
  • \ca and \cA are the same control character in ECMAScript, which takes the letter modulo 32; Java exclusive-ors with 64 and answers !,
  • \b inside a character class is a backspace in ECMAScript,
  • \0 is NUL in ECMAScript and the start of an octal escape in Java,
  • \p{Letter} and \p{Script=Greek} are \p{L} and \p{IsGreek}.

One scan answers both questions this library asks of a pattern: how Java spells it, and whether it was ECMAScript in the first place - which is what format: regex asserts, and why (?P<name>...), (?#comment) and the inline flags (?i) are refused even though Java understands two of them.

ECMAScript regular expressions in the dialect java.util.regex speaks.

`pattern` and `patternProperties` hold ECMA-262 regular expressions, and
Java's are ALMOST the same language. Every difference between the two is
silent - the pattern compiles on both sides and matches different strings:

- `\s` is six ASCII characters in Java; in ECMAScript it is every space
  separator, both line terminators and the byte-order mark,
- `\v` is one vertical tab in ECMAScript and a whole class of vertical
  whitespace in Java,
- `\ca` and `\cA` are the same control character in ECMAScript, which
  takes the letter modulo 32; Java exclusive-ors with 64 and answers `!`,
- `\b` inside a character class is a backspace in ECMAScript,
- `\0` is NUL in ECMAScript and the start of an octal escape in Java,
- `\p{Letter}` and `\p{Script=Greek}` are `\p{L}` and `\p{IsGreek}`.

One scan answers both questions this library asks of a pattern: how Java
spells it, and whether it was ECMAScript in the first place - which is what
`format: regex` asserts, and why `(?P<name>...)`, `(?#comment)` and the
inline flags `(?i)` are refused even though Java understands two of them.
raw docstring

com.blockether.skjema.uri

URI reference resolution and JSON Pointer tokens - the addressing half of JSON Schema.

Identifiers in a schema are URI REFERENCES: $id moves the base, $ref resolves against whatever base encloses it, and a fragment is either an anchor name or a JSON Pointer. Everything here is string work; nothing fetches anything, because a validator that resolves a reference over the network is a validator that fails differently on every machine.

URI reference resolution and JSON Pointer tokens - the addressing half of
JSON Schema.

Identifiers in a schema are URI REFERENCES: `$id` moves the base, `$ref`
resolves against whatever base encloses it, and a fragment is either an
anchor name or a JSON Pointer. Everything here is string work; nothing
fetches anything, because a validator that resolves a reference over the
network is a validator that fails differently on every machine.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close