json-schema

    Compile-once JSON Schema 2020-12 validator for MoonBit: exact decimal arithmetic, precise pattern semantics, no silent schema errors, no hidden I/O.

    json-schema
    validation
    jsonschema
    schema
    Download zip
    Author
    Version
    0.0.2
    License
    Apache-2.0
    Last updated
    12 hours ago
    Downloads
    4

    Dependencies

    #colmugx/json-schema

    A compile-once JSON Schema validator for MoonBit, built around precise 2020-12 validation scenarios rather than permissive approximations.

    Status: development milestone, not a complete 2020-12 implementation. Local static JSON Pointer references are supported, including recursive plans. Remote references, resource identifiers, dynamic scope and unevaluated semantics are not implemented; schemas requesting those semantics fail compilation explicitly. See the support contract before adopting the library.

    #Use it for

    • Closed tool/request argument objects (additionalProperties: false).
    • Structured result validation, tuples, array membership and dependencies.
    • Exact decimal multipleOf, integer checks and numeric bounds.
    • Unicode code-point string lengths and an explicitly supported pattern profile.
    • Reusing one owned compiled schema for many instances.

    The module has no dependency on the surrounding MCP project. Its local moon.work isolates it from an enclosing MoonBit workspace, so the directory can be moved into its own repository.

    #Quick start

    Import "colmugx/json-schema" in your package. Parse numbers through this library when their original decimal values matter:

    ///|
    let document = @json-schema.parse(
    "{\"type\":\"object\",\"required\":[\"amount\"],\"properties\":{\"amount\":{\"type\":\"number\",\"multipleOf\":0.1}},\"additionalProperties\":false}",
    )

    ///|
    let schema = @json-schema.Schema::compile(document)

    ///|
    let instance = @json-schema.parse("{\"amount\":0.3}")

    ///|
    let valid = schema.valid(instance)

    ///|
    let failures = schema.faults(instance)

    These operations can raise errors; propagate or catch them at your application's boundary. A malformed/unsupported schema is not a valid compiled schema. A malformed numeric node or exhausted traversal budget is not an ordinary false validation result.

    #Choose the result you need

    • schema.valid(instance) short-circuits assertion failures and does not build a diagnostic array.
    • schema.faults(instance) returns failures with instance_path, schema_path, keyword and message. Paths use JSON Pointer escaping.
    • Both methods check the instance's numeric representations and traversal budget. Failed speculative anyOf, oneOf, not and if branches do not leak errors into a successful result.

    #Preserve number precision

    A floating-point value cannot recover digits lost before validation. This library's parser keeps number lexemes in Json::Number's repr field; numeric predicates use integer coefficients and decimal scales, not an epsilon or a rounded decimal quotient. Core-parsed or programmatically constructed numbers without a lexeme use the available finite Double representation instead.

    Do not construct inconsistent Json::number(value, repr=...) nodes: numeric representations are checked, not silently discarded.

    #Supported now

    The compiler/evaluator covers boolean schemas, type, const, enum, numeric bounds and multipleOf, string lengths and patterns, object properties/names/ dependencies, 2020-12 prefixItems/items, array counts/contains/uniqueItems, allOf/anyOf/oneOf/not/if/then/else, and same-document static $ref (including $defs, forward references and recursive schemas).

    format and content keywords are annotations, not format/content assertions. Unknown extension keywords are annotations. Known malformed supported keywords are compile errors. An unimplemented known semantic keyword is an explicit unsupported error, not an ignored assertion.

    #Evidence and development

    The full pinned official draft2020-12 corpus currently reports 962/1301 standard cases matched: 0 mismatches, 337 unsupported, 2 rejected. Optional cases are reported separately (535/1036); format assertions are not enabled. This is visible progress toward conformance, not a complete-draft claim. Unsupported and rejected cases stay in the denominator.

    Run these commands from this independent module's root:

    moon check --target native --deny-warn --output-json moon test --target native --deny-warn --output-json moon check --target js --deny-warn --output-json moon test --target js --deny-warn --output-json moon check --target wasm --deny-warn --output-json moonx benchmarks/run-campaign.mbtx

    The campaign downloads the official corpus, checks and builds native release executables, runs every official case and measures 11 scenarios in five fresh processes each. It saves the corpus, provenance, detailed outcomes and logs in benchmark-results/. Move or remove the previous campaign's logs/ and runs/ before starting another; existing evidence is not overwritten silently.

    For a shorter infrastructure check, use moonx benchmarks/run-campaign.mbtx smoke. For case-level TDD, see the runner guide. Ordinary official-suite gaps do not fail CI; compilation, runtime/harness failures and invalid performance results do. Unit tests cover library-specific contracts rather than duplicating the full official corpus.

    The independent benchmark workflow runs on pushes to main and manual dispatch from this independent repository. Standalone .mbtx orchestration uses moonx; the native package runners can be invoked with moon run.

    #Performance policy

    Correctness comes first. Compilation hoists schema parsing and regular-expression compilation out of repeated validation; object checks share a property walk. The current implementation still performs a numeric/depth preflight and uses a quadratic exact-equality check for uniqueItems. It does not yet claim to be the fastest validator. Performance claims require reproducible benchmarks, representative valid/invalid cases and the same semantics on both sides.

    #License

    Apache-2.0. Vendored conformance data, when present, retains its upstream license and pinned-source attribution.

    CompileError

    pub(all) suberror CompileError {
    InvalidKeyword(path~ : String, reason~ : String)
    UnknownDialect(String)
    UnsupportedKeyword(path~ : String, keyword~ : String)
    UnsupportedPattern(path~ : String, pattern~ : String, reason~ : String)
    DepthLimit(Int)
    } derive(
    Debug
    )

    EvaluationError

    pub(all) suberror EvaluationError {
    DepthLimit(Int)
    InvalidNumber(path~ : String)
    } derive(
    Debug
    )

    NumericError

    pub(all) suberror NumericError {
    InvalidNumber
    } derive(
    Debug
    )

    ParseError

    pub(all) suberror ParseError {
    InvalidJson(position~ : Int, reason~ : String)
    DepthLimit(Int)
    } derive(
    Debug
    )

    PatternError

    pub(all) suberror PatternError {
    NotSupported(String)
    } derive(
    Debug
    )

    CompiledPattern

    pub struct CompiledPattern {
    // private fields
    }

    Opaque compiled pattern. No external regexp type leaks through this API.

    ExactNumber

    pub enum ExactNumber {
    Repr(coeff~ :
    BigInt
    , scale~ : Int)
    Double(Double)
    }

    A number taken from a JSON tree, kept exactly.

    Repr numbers carry the literal as written ("0.3", "1e10"); Double numbers were constructed in code and only the IEEE value remains. The representation stays private: literal validation is exact, while Double validation uses the finite double's shortest decimal rendering. Original digits discarded by another parser cannot be recovered.

    ExactNumber::compare

    fn ExactNumber::compare(self : ExactNumber, other : ExactNumber) -> Int raise NumericError

    ExactNumber::equal

    fn ExactNumber::equal(self : ExactNumber, other : ExactNumber) -> Bool raise NumericError

    Mathematical equality, including differently spelled decimal numbers.

    ExactNumber::from_double

    fn ExactNumber::from_double(v : Double) -> ExactNumber

    Interpret a Json number node's literal exactly when available.

    ExactNumber::from_literal

    fn ExactNumber::from_literal(text : String) -> ExactNumber?

    Interpret a decimal literal (JSON number grammar) exactly.

    ExactNumber::is_integer

    fn ExactNumber::is_integer(self : ExactNumber) -> Bool raise NumericError

    Decimal integer predicate; no rounding or floating-point tolerance.

    ExactNumber::multiple_of

    fn ExactNumber::multiple_of(self : ExactNumber, divisor : ExactNumber) -> Bool raise NumericError

    Exact divisibility of aligned integer coefficients, never rounded division.

    ExactNumber::of_json

    fn ExactNumber::of_json(v : Json) -> ExactNumber?

    A Json number node read as exactly as the source allows: parsed trees carry only the double (@json.parse does not keep literal text on this toolchain — verified by probe), constructed trees may carry a repr.

    ExactNumber::sign

    fn ExactNumber::sign(self : ExactNumber) -> Int raise NumericError

    Fault

    pub(all) struct Fault {
    instance_path : String
    schema_path : String
    keyword : String
    message : String
    } derive(Eq,
    Debug
    )

    Fault::equal

    fn Fault::equal(Fault, Fault) -> Bool

    Fault::not_equal

    fn Fault::not_equal(x : Fault, y : Fault) -> Bool

    Fault::to_repr

    PatternSupport

    pub(all) enum PatternSupport {
    Supported(CompiledPattern)
    Unsupported(reason~ : String)
    }

    Schema

    pub struct Schema {
    // private fields
    }

    An owned, precompiled 2020-12 validation plan. Compilation does not mutate the source document; constants and enumerations are copied into the plan.

    Schema::compile

    fn Schema::compile(document : Json, max_depth? : Int) -> Schema raise CompileError

    Compile a schema once. Unknown extension keywords are annotations; known malformed keywords fail compilation. Local static $ref targets compile into an indexed arena; unevaluated and cross-resource semantics stay explicitly refused until their evaluation semantics are implemented.

    Schema::faults

    fn Schema::faults(self : Schema, value : Json, max_depth? : Int) -> Array[Fault] raise

    Collect assertion failures. Paths are RFC 6901 JSON Pointers; failed speculative anyOf/oneOf/not/if branches do not leak their diagnostics.

    Schema::valid

    fn Schema::valid(self : Schema, value : Json, max_depth? : Int) -> Bool raise

    Validate without allocating a diagnostic array. Invalid JSON numbers and exhausted traversal budgets are errors, not ordinary validation failures.

    compile_pattern

    fn compile_pattern(pattern : String) -> PatternSupport

    Unanchored search semantics. Supports ordinary regexp.mbt patterns with ECMA digit/word/whitespace rewrites and a precise leading lookahead chain. Other lookarounds and ambiguous rewrites are explicit unsupported errors.

    parse

    fn parse(source : StringView, max_nesting_depth? : Int) -> Json raise ParseError

    Parse JSON while retaining every numeric literal in Json::Number's repr. Duplicate object keys use the last value. Offsets are UTF-16 code units.

    pattern_matches

    fn pattern_matches(support : PatternSupport, text : String) -> Bool raise PatternError

    Unsupported patterns cannot silently turn into a false validation result.