Skip to content

schema-calc v0.3.0

Choose a tag to compare

@fMindstormer fMindstormer released this 07 Nov 19:03
· 17 commits to main since this release

Lists. A contract can now sum a list of amounts or of line items, so an ordinary validator can check that the lines of an invoice add up to its subtotal, however many lines there are up to a declared maximum. Contracts without lists compile exactly as in 0.2.0, and the four earlier examples are unchanged. Try it in any browser; nothing is sent to a server.

What's new

  • Lists of amounts. Declare "lists": {"lines": 10} and write sum(lines) anywhere a field can appear, with a coefficient if you like. A list may hold up to 20 amounts and be any length up to its maximum, including empty, which sums to zero. Each item must be an amount in the declared format. Up to two lists per contract.
  • Lists of objects. "lines": {"maxItems": 10, "fields": ["net", "tax"]} declares line items with up to four amount fields, summed separately with sum(lines.net) and sum(lines.tax). Every line must carry each declared field; other properties, such as a description or SKU, pass through unchecked. Writing sum(lines) for a list of objects, or sum(lines.amount) for a list of amounts, is refused with a message naming the right form.
  • A fifth example, line items. sum(lines.amount) = subtotal, for up to four lines with a description each: Notebook 19.99, Pens 4.95 and Stamp 0.06 add up to 25.00, and Add one cent turns the subtotal into 25.01, which the schema rejects. It is 77,917 B and has its own tab in the playground, which shows each line on one row.
  • Size grows roughly with the square of the length, because every position is another digit to add in each column: sum(lines) = subtotal, with four integer and two fractional digits, is 9 kB for one line, 72 kB for 4, 386 kB for 10 and 1.4 MB for 20. Lines that are objects cost 3–17% more.
  • The tally encoding refuses lists, with a message pointing to the flat encoding: its columns have a fixed set of blocks, and a list's length varies.

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.3.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

The line-items schema and records work the same way with lines.schema.json, lines.valid.json and lines.invalid.json.

Validator compatibility

The flat test corpus grew from 32,000 to 34,732 records, adding lists of every length up to their maximum, empty and malformed lists, lines with other properties and lines with separate net and tax fields. 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, as before. @exodus/schemasafe times out loading some schemas from about 170 kB up, and which ones varies between runs: four of 75 locally, six on the slower Linux CI runner. jsonschema-rs runs out of memory on the larger flat schemas, including all five shipped ones.

For library users

A list position is read through a prefixItems tuple that stops at that position. Ajv's default strict mode logs a warning for every such partial tuple, and strict: true refuses it; new Ajv2020({ strictTuples: false }) keeps the other strict checks. No other tested validator objects. normalizeSpec returns the declared lists, and parseConstraint names each position as a term of its own, lines[0] or lines[0].amount. fields may be empty when the contract declares a list.

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.3.0.zip is the source, with a MANIFEST.sha256 inside; schema-calc-0.3.0.zip.sha256 is its checksum and SHA256SUMS covers every file.

Scope

Sums and differences of nonnegative decimal strings, their whole-number multiples and sums over lists, related by =, <=, >=, < or >: up to 8 fields, 2 lists of 20 items, 8 constraints and 12 digits. No products of fields, division, !=, indexing into a list, constraints on each line, other nested objects, or lists in the tally encoding yet. A research prototype; it is not published to npm.