pi-mono/packages/schema/test/conformance.test.ts
Mario Zechner fab2c5353e feat(schema): add pi-schema, JSON Schema builders and validation without eval
@earendil-works/pi-schema compiles JSON Schema (drafts 2020-12 and 7) once into
plain closures: no eval or new Function, so it runs under a strict CSP. It
passes 2,222 of the 2,223 core tests of the official JSON-Schema-Test-Suite,
checks Chord-style argument schemas within 1.2x of TypeBox's JIT, and keeps
3,200 compiled schemas in 7.9 MB (TypeBox: 54.7 MB).

- compile(schema, options) returns a Validator: check() is the boolean check
  (a type guard for builder schemas), errors() reports each failure with a
  JSON Pointer path, missing and unknown fields at the field.
- Builders (str, int, obj, opt, union, ...) return plain JSON Schema typed with
  the values it accepts (Static<typeof schema>).
- Formats are an optional entry point (./formats), adapted from TypeBox (MIT).
- Tests run the vendored suite (drafts 2020-12 and 7, with optional and format
  tests); npm run bench compares conformance, speed, setup cost, memory and
  bundle size with TypeBox, @cfworker/json-schema and omptype.
2026-10-31 21:39:34 +00:00

51 lines
2.8 KiB
TypeScript

// The official JSON-Schema-Test-Suite, drafts 2020-12 and 7: every test, including optional and format tests.
// A test listed in KNOWN_FAILURES must fail; when one starts passing, remove it from the list.
import { describe, expect, it } from "vitest";
import { formats } from "../src/formats/index.ts";
import { compile } from "../src/index.ts";
import { suiteFiles, suiteSchemas } from "./suite.ts";
const KNOWN_FAILURES = new Set([
// Vocabularies switched off by a custom meta-schema are not supported.
"draft2020-12/vocabulary > schema that uses custom metaschema with with no validation vocabulary > no validation: invalid number, but it still validates",
"draft2020-12/optional/format-assertion > schema that uses custom metaschema with format-assertion: false > format-assertion: false: invalid string",
"draft2020-12/optional/format-assertion > schema that uses custom metaschema with format-assertion: true > format-assertion: true: invalid string",
// A $ref to a document of another draft is evaluated with the compile's draft.
"draft2020-12/optional/cross-draft > refs to historic drafts are processed as historic drafts > first item not a string is valid",
"draft7/optional/cross-draft > refs to future drafts are processed as future drafts > missing bar is invalid",
// contentMediaType and contentEncoding are annotations.
"draft7/optional/content > validation of string-encoded content based on media type > an invalid JSON document",
"draft7/optional/content > validation of binary string-encoding > an invalid base64 string (% is not a valid character)",
"draft7/optional/content > validation of binary-encoded media type documents > a validly-encoded invalid JSON document",
"draft7/optional/content > validation of binary-encoded media type documents > an invalid base64 string that is valid JSON",
]);
const schemas = suiteSchemas();
for (const file of suiteFiles()) {
// Format tests assert formats; everywhere else `format` is an annotation, as draft 2020-12 specifies.
const options = {
draft: file.draft,
schemas,
formats: file.name.includes("/optional/format/") ? formats : undefined,
};
describe(file.name, () => {
for (const group of file.groups) {
describe(group.description, () => {
for (const test of group.tests) {
const known = KNOWN_FAILURES.has(`${file.name} > ${group.description} > ${test.description}`);
(known ? it.fails : it)(test.description, () => {
const validator = compile(group.schema, options);
expect(validator.check(test.data)).toBe(test.valid);
});
it(`${test.description} (errors agree with check)`, () => {
const validator = compile(group.schema, options);
const errors = validator.errors(test.data);
expect(errors.length === 0).toBe(validator.check(test.data));
});
}
});
}
});
}