Skip to content

schema-calc v0.2.0

Choose a tag to compare

@fMindstormer fMindstormer released this 31 Oct 18:26
· 22 commits to main since this release

Coefficients and inequalities. A contract can now multiply a field by a whole number and compare two sides with <=, >=, < or >, so an ordinary validator can check discount <= subtotal, or that each share is a third of the total rounded down to the cent. The shipped examples compile to byte-identical schemas; a 0.1.1 contract changes only if part of its schema held more than 2,000 subschemas without a reference. Try it in any browser; nothing is sent to a server.

What's new

  • Whole-number coefficients, written 3 * price or price * 3, from 1 to 1,000. Products of fields and decimal coefficients are refused with a clear message. The schema grows with the coefficient: total = k * price + shipping with four integer and two fractional digits is 21 kB at k = 1, 80 kB at 10, 401 kB at 100 and 751 kB at 1,000.
  • Inequalities. >= and > negate both sides, and a strict comparison adds one minor unit, so every inequality becomes sum <= 0. Where an equation rejects a column that leaves a remainder, an inequality carries the remainder rounded up instead (proof). Adding discount <= subtotal to the invoice takes it from 41,364 to 46,422 B; a bound such as shipping < 100 is about 1 kB.
  • A fourth example, the even split. 3 * share <= total and total < 3 * share + 0.03 accept exactly the share rounded down to the cent: 33.33 of 100.00, but neither 33.32 nor 33.34. It is 26,728 B, or 12,278 B in the tally encoding, and has its own tab in the playground.
  • At most 2,000 subschemas between references. With a coefficient of 1,000, Ajv's call stack overflowed while validating some records; Ajv and schemasafe compile each such region into one function. Larger regions now move into $defs, as the 32-level nesting limit already did. compile(spec, { maxSubschemas }) sets another limit. No existing example changes.
  • The tally encoding refuses coefficients above 16, with a message pointing to the flat encoding. Its schema grows roughly with the cube of the coefficient: total = 16 * price + shipping is 87 kB, and a coefficient of 100 would take megabytes.

Check it without Schema Calc

Python 3 and jsonschema only, in an empty directory:

python3 -m pip install jsonschema==4.25.1
base=https://github.com/fMindstormer/schema-calc/releases/download/v0.2.0
for f in validate.py invoice.schema.json invoice.valid.json invoice.invalid.json; do curl -fLO "$base/$f"; done
python3 validate.py invoice.schema.json invoice.valid.json invoice.invalid.json
ACCEPT  invoice.valid.json
REJECT  invoice.invalid.json

Validator compatibility

The test corpus grew to 32,000 flat and 31,521 tally records, adding coefficients from 2 to 1,000, exhaustive one-digit comparisons, signed inequalities one unit either side of the boundary, and the even split. All of them pass Ajv and Python jsonschema, and twelve validators in five languages ran them (table). None gave a wrong answer. Nine load every schema. @cfworker/json-schema needs --defs. @exodus/schemasafe times out loading the two largest test schemas, compiled from coefficients of 100 and 1,000 (333 kB and 911 kB); timed separately, it takes 159 s and 11 minutes. jsonschema-rs runs out of memory on the larger flat schemas, as before.

For library users

parseConstraint parses every relation. parseEquation still accepts only equations, because code written against 0.1 reads its result as one.

Files

invoice.schema.json is the compiled invoice contract, unchanged since 0.1.1; invoice.valid.json and invoice.invalid.json are the correct record and the one a cent off. validate.py is the standalone validator: it needs only jsonschema and never imports the compiler. schema-calc-0.2.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.2.0.zip.sha256 is its checksum and SHA256SUMS covers every file.

Scope

Sums and differences of nonnegative decimal strings and their whole-number multiples, related by =, <=, >=, < or >: up to 8 fields, 8 constraints and 12 digits. No products of fields, division, !=, or nested objects and arrays yet. A research prototype; it is not published to npm.