Sign in

    MoonCheck

    Batch / CI JSON Schema validation CLI with schema linting and JSON reports, built on mizchi/jsonschema.

    json
    jsonschema
    validation
    cli
    ci
    Download zip
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    28 days ago
    Downloads
    20

    #MoonCheck

    CI mooncakes

    A batch / CI command line front end for JSON Schema validation in MoonBit, built on mizchi/jsonschema.

    Attribution. MoonCheck does not implement a JSON Schema validator. All schema compilation and data validation is done by mizchi/jsonschema (MIT), declared as a dependency in moon.mod. MoonCheck is an extension on top of it that adds what you need to run schema checks over many files in CI. Versions up to 0.1.0 shipped their own small validation engine; from 0.2.0 that engine was removed and replaced by mizchi/jsonschema.

    #What MoonCheck adds on top of mizchi/jsonschema

    mizchi/jsonschemaMoonCheck adds
    Validate a value against a JSON Schema✅ (the engine)uses it as is
    Command line tool–mooncheck validate schema.json data/*.json
    Many documents in one run, one bad file does not stop the run–✅
    Machine-readable report for CI–--report json, stable error kinds
    Stable exit codes–0 valid / 1 invalid data / 2 usage, I/O or schema error
    Schema lintinglenient: unknown nodes become "accept anything"check-schema rejects typos such as "type": "int", "min", "required": true with a location and a hint
    Error formatJsonPointer + message treeflat errors with $.user.tags[0] paths and a kind (missing_required, type_mismatch, above_max, …)

    The schema linter matters in CI: a schema with a typo would otherwise compile to an accept-all validator, and every document would silently pass.

    #Installation / Build

    Requires the MoonBit toolchain.

    moon update # refresh the registry index moon check # type-check moon test # run the test suite moon run examples/demo # run the demo (any backend) moon build cmd/main --target native # build the `mooncheck` executable

    The library runs on any backend. The cmd/main CLI targets native builds (MSVC on Windows, a C toolchain on Linux/macOS).

    #CLI Usage

    Schemas are standard JSON Schema documents:

    { "type": "object", "required": ["host", "port"], "properties": { "host": { "type": "string", "minLength": 1 }, "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, "debug": { "type": "boolean" }, "timeout": { "type": "integer", "minimum": 0, "maximum": 600 } } }

    Validate many documents at once (the shell expands the glob):

    mooncheck validate examples/cli/config.schema.json examples/cli/configs/*.json

    ok: examples/cli/configs/service_a.json ok: examples/cli/configs/service_b.json examples/cli/configs/service_c_broken.json: $.host: String length 0 is less than minimum 1 examples/cli/configs/service_c_broken.json: $.port: Value is greater than maximum 65535 examples/cli/configs/service_c_broken.json: $.timeout: Value is not a number checked 3 document(s): 2 ok, 1 failed, 3 error(s) error: 1 of 3 document(s) failed validation

    The messages come from mizchi/jsonschema; the paths, grouping, summary and exit code come from MoonCheck.

    Add --report json for a report a CI job or script can consume:

    { "schema": "examples/cli/config.schema.json", "ok": false, "summary": { "checked": 3, "failed": 1, "errors": 3 }, "documents": [ { "path": "examples/cli/configs/service_a.json", "ok": true, "errors": [] }, { "path": "examples/cli/configs/service_b.json", "ok": true, "errors": [] }, { "path": "examples/cli/configs/service_c_broken.json", "ok": false, "errors": [ { "path": "$.host", "kind": "too_short", "message": "String length 0 is less than minimum 1" }, { "path": "$.port", "kind": "above_max", "message": "Value is greater than maximum 65535" }, { "path": "$.timeout", "kind": "type_mismatch", "message": "Value is not a number" } ] } ] }

    Lint a schema before using it:

    mooncheck check-schema bad.schema.json

    error: invalid schema "bad.schema.json": #/required: must be an array of property names, e.g. ["name"]; #/properties/age/type: unknown type "int" (did you mean "integer"?); #/properties/age: unknown keyword "min" (did you mean "minimum"?)

    Other options: -q/--quiet (only failures), -h/--help, -V/--version.

    Exit codes: 0 every document is valid, 1 at least one document is invalid, 2 usage, I/O or schema error.

    #Error kinds

    kindproduced by
    missing_requiredrequired
    type_mismatchtype
    below_min / above_maxminimum, exclusiveMinimum / maximum, exclusiveMaximum
    too_short / too_longminLength, minItems / maxLength, maxItems
    not_in_enumenum
    constraintany other keyword (const, anyOf, additionalProperties, …)
    invalid_jsonthe data file is not JSON

    #Library Usage

    match @MoonCheck.validate_strings(schema_text, data_text) {
    Ok(errors) if errors.is_empty() => println("valid")
    Ok(errors) => for error in errors { println(@MoonCheck.to_string(error)) }
    Err(reason) => println("could not validate: \{reason}")
    }

    Batch reports, the same path the CLI takes:

    let schema = match @MoonCheck.parse_schema_text(schema_text) {
    Ok(schema) => schema
    Err(reason) => panic()
    }
    let report = @MoonCheck.RunReport::new("schema.json", [
    @MoonCheck.FileResult::new("a.json", @MoonCheck.validate_text(schema, text_a)),
    @MoonCheck.FileResult::new("b.json", @MoonCheck.validate_text(schema, text_b)),
    ])
    println(@MoonCheck.render(report, @MoonCheck.ReportFormat::json(), false))

    @MoonCheck.lint_schema(json) returns the linter's findings as a list.

    #Scope

    Which JSON Schema keywords are supported is decided by mizchi/jsonschema; MoonCheck does not re-implement or extend the validation rules. The linter rejects "type": [..] unions (use anyOf) because the underlying validator would ignore them.

    Related MoonBit projects: mizchi/jsonschema (the engine MoonCheck builds on) and Betterlol/moon_zod (Zod-style in-code schemas for LLM tool calling). MoonCheck does not compete with either: it is a CLI/CI layer, not a validator.

    #Testing

    moon test

    41 tests: the schema linter, the error adapter (paths and kinds for every keyword), CLI argument parsing, the batch flow and both report formats.

    #Project Structure

    MoonCheck/ ├── moon.mod # module metadata, depends on mizchi/jsonschema ├── schema.mbt # schema linter + compile via mizchi/jsonschema ├── validate.mbt # adapter: mizchi/jsonschema errors -> MoonCheck errors ├── error.mbt # stable ValidationError / ErrorKind ├── report.mbt # text and JSON reports ├── cli.mbt # command line parsing + usage text ├── *_test.mbt, *_wbtest.mbt # tests ├── cmd/main/ # CLI shell: reads files, prints, sets exit status └── examples/ ├── demo/ # runnable demo (moon run examples/demo) └── cli/ # sample schemas + data (configs/ for batch runs)

    #License

    Apache-2.0. Depends on mizchi/jsonschema (MIT).

    SchemaError

    pub(all) suberror SchemaError {
    InvalidSchema(String)
    } derive(Eq,
    Debug
    )

    Raised when a schema document is malformed.

    CheckSchemaOptions

    pub struct CheckSchemaOptions {
    path : String
    } derive(Eq,
    Debug
    )

    Options of the check-schema subcommand.

    CliCommand

    pub enum CliCommand {
    Help
    Version
    Validate(ValidateOptions)
    CheckSchema(CheckSchemaOptions)
    Invalid(String)
    } derive(Eq,
    Debug
    )

    A parsed command line.

    ErrorKind

    pub enum ErrorKind {
    MissingRequired
    Type
    BelowMinimum
    AboveMaximum
    TooShort
    TooLong
    NotInEnum
    Constraint
    InvalidJson
    } derive(Eq,
    Debug
    )

    Classification of a single validation problem.

    FileResult

    pub struct FileResult {
    name : String
    errors : Array[ValidationError]
    } derive(Eq,
    Debug
    )

    The validation outcome for one data document.

    FileResult::new

    fn FileResult::new(name : String, errors : Array[ValidationError]) -> FileResult

    Create a FileResult.

    ReportFormat

    pub enum ReportFormat {
    Text
    Json
    } derive(Eq,
    Debug
    )

    Output format of a validation report.

    ReportFormat::json

    The JSON report format.

    ReportFormat::text

    The text report format.

    A constructor function, so callers in other packages do not have to spell out the variant.

    RunReport

    pub struct RunReport {
    schema : String
    results : Array[FileResult]
    } derive(Eq,
    Debug
    )

    The result of validating one schema against one or more data documents.

    RunReport::failed

    fn RunReport::failed(report : RunReport) -> Int

    Number of documents that failed validation.

    RunReport::is_ok

    fn RunReport::is_ok(report : RunReport) -> Bool

    true when every document is valid.

    RunReport::new

    fn RunReport::new(schema : String, results : Array[FileResult]) -> RunReport

    Create a RunReport.

    RunReport::total_errors

    fn RunReport::total_errors(report : RunReport) -> Int

    Total number of errors across all documents.

    Schema

    pub struct Schema {
    // private fields
    }

    A compiled schema, ready to validate documents.

    ValidateOptions

    pub struct ValidateOptions {
    format : ReportFormat
    quiet : Bool
    schema : String
    data : Array[String]
    } derive(Eq,
    Debug
    )

    Options of the validate subcommand.

    ValidationError

    pub struct ValidationError {
    path : String
    kind : ErrorKind
    message : String
    } derive(Eq,
    Debug
    )

    A single validation error.

    error_kind_name

    fn error_kind_name(kind : ErrorKind) -> String

    Stable, machine-readable name of an error kind.

    These strings are part of the JSON report format, so they are kept short, lowercase and stable rather than being derived from the constructor names.

    is_valid

    fn is_valid(schema : Schema, value : Json) -> Bool

    Convenience: true when validate finds no error.

    lint_schema

    fn lint_schema(schema : Json) -> Array[String]

    Lint a schema document.

    Returns every problem found, each prefixed with the JSON pointer of the offending schema node (#/properties/age). An empty array means the schema is safe to compile.

    parse_args

    fn parse_args(argv : ArrayView[String]) -> CliCommand

    Parse a command line (without the program name).

    validate is optional, so mooncheck validate s.json d.json and mooncheck s.json d.json are equivalent. Data paths may be given in any number; shells expand globs, so mooncheck s.json cfg/*.json works without any glob support in the CLI itself.

    parse_schema

    fn parse_schema(json : Json) -> Schema raise SchemaError

    Lint and compile a schema document.

    parse_schema_string

    fn parse_schema_string(input : String) -> Schema raise SchemaError

    Lint and compile a schema from JSON text.

    parse_schema_text

    fn parse_schema_text(text : String) -> Result[Schema, String]

    Parse a schema document, returning the reason as a string on failure.

    Batch and CLI callers parse the schema once and reuse it for every data document, so a broken schema is reported separately from data errors.

    render

    fn render(report : RunReport, format : ReportFormat, quiet : Bool) -> String

    Render the report in the requested format.

    quiet only affects the text format; the JSON format is already machine-oriented and always contains everything.

    render_json

    fn render_json(report : RunReport) -> String

    Render the report as a JSON document.

    Shape:
    { "schema": "schema.json", "ok": false, "summary": { "checked": 2, "failed": 1, "errors": 2 }, "documents": [ { "path": "a.json", "ok": true, "errors": [] }, { "path": "b.json", "ok": false, "errors": [ { "path": "$.age", "kind": "type_mismatch", "message": "..." } ] } ] }

    render_text

    fn render_text(report : RunReport, quiet : Bool) -> String

    Render the report as text.

    With a single data document the output is just the error lines, which is the friendly case for one-off checks. With several documents each line is prefixed by the document name and a summary line is appended, which is the useful case for CI.

    to_string

    fn to_string(error : ValidationError) -> String

    Render one error as a single line: $.user.age: Value is not a number.

    usage_text

    fn usage_text() -> String

    Usage text, printed by --help and after a usage error.

    validate

    fn validate(schema : Schema, value : Json) -> Array[ValidationError]

    Validate a JSON value against a schema.

    Returns all validation errors. An empty array means the value is valid.

    validate_strings

    fn validate_strings(schema_text : String, data_text : String) -> Result[Array[ValidationError], String]

    Validate in one step from raw text.

    Takes a schema document and a data document, both as JSON text. Returns Ok(errors) where an empty list means the data is valid, or Err(reason) when either document could not be parsed.

    validate_text

    fn validate_text(schema : Schema, data_text : String) -> Array[ValidationError]

    Validate a data document against an already parsed schema.

    Unlike validate_strings, a data document that is not valid JSON is reported as a single InvalidJson error instead of aborting the call, so callers that process many documents can keep going and report everything.

    version_text

    fn version_text() -> String

    Version text, printed by --version.