moonschema JSON Schema · MoonBit

moonschema

moonbitstack/moonschema

One package. A schema is read with Schema::new, and the two questions are valid for a yes or a no and faults for the reasons. Everything else here is a type those answers are made of.

Values

letdraft

pub let draft : Draft

The version assumed when a document does not name one.

2020-12: the current release, and the one OpenAPI 3.1 is defined against. A document that says $schema is read at the version it says, whatever this is set to — the document is more specific than the setting.

letoutput

pub let output : Output

The output format a validation produces unless asked for another.

Flag, because the common case is a guard that wants a yes or a no and collecting errors nobody reads is work nobody asked for. A caller that wants to tell someone what went wrong asks for Basic.

letregistry

pub let registry : Registry

No vocabularies beyond the built-in ones.

Types

enumDraft

Which version of JSON Schema a document is written against.

The six released versions that are in use. draft-03 is not here: it predates the $ keywords and nothing has been written against it since 2010. The next release arrives as another value of this enum and a keyword table, not as a second validator.

pub(all) enum Draft {
  • Draft4draft-04 (2013). What OpenAPI 3.0 profiles, and what a great deal of Java and Python tooling still emits.
  • Draft6draft-06 (2017). $id replaces id, exclusiveMinimum becomes a number.
  • Draft7draft-07 (2018). The most widely deployed version.
  • Draft20192019-09. Vocabularies, $defs, $recursiveRef, and the annotation-aware unevaluatedProperties.
  • Draft20202020-12. $dynamicRef, prefixItems, and what OpenAPI 3.1 is.
  • V1v1 (2026). format asserts by default and propertyDependencies joins the applicators; the rest of the language is 2020-12's.
pub fn Draft::of(StringView) -> Self?

The version a $schema URI names.

It answers None for a URI it does not know rather than falling back to a version of its own choosing: a document written against something else is not a document to guess at. Both the http and https spellings are accepted, and a trailing # is optional, because both are found in the wild.

pub fn Draft::uri(Self) -> String

The $schema URI that names this version.

structFault

One reason an instance failed, in the shape the basic output gives it (2020-12 §12.4.2).

pub(all) struct Fault {
  • at : StringWhere in the instance, as a JSON Pointer.
  • schema_at : StringWhere in the schema, as a JSON Pointer.
  • keyword : StringThe keyword that refused it.
  • message : StringWhat that keyword had to say.

enumOutput

How much a validation says when it fails.

The four formats the specification itself defines (2020-12 §12.4), named as it names them.

pub(all) enum Output {
  • FlagWhether it validated, and nothing else. The cheapest, and what a guard on a request wants.
  • BasicA flat list of the errors, each with the location in the instance and in the schema.
  • DetailedThe errors as a tree, with the schema's own structure preserved.
  • VerboseEvery annotation as well as every error, which is what a tool that explains a schema to a person needs.

structOutputUnit

One result of one keyword: the output unit of §12.3.

keyword_at is the *evaluation path*, which is a JSON Pointer that goes through $ref; absolute_at is the *schema location*, which is the resource's own URI and a pointer into it and never goes through one. The two differ exactly when a reference was crossed, and a reader that has to fetch the keyword needs the second.

pub(all) struct OutputUnit {
  • valid : BoolWhether this result is a pass.
  • keyword_at : StringWhere in the schema, following the walk that arrived.
  • absolute_at : String?The resource's canonical URI and a pointer into it, when it can be said.
  • at : StringWhere in the instance, as a JSON Pointer.
  • error : String?What went wrong, in one clause. A branch that only has children under it has nothing of its own to say.
  • annotation : Json?What the keyword contributed, when it produced an annotation.
  • children : Array[OutputUnit]The results this one encloses.

structRegistry

The vocabularies a caller can offer a document, by URI.

A registry is not global: two schemas in one program may be read against different vocabularies, and a schema is read against what its own caller handed it. registry is the empty one, which is what a caller who adds nothing gets; a caller with vocabularies of their own starts from Registry::new.

pub struct Registry
pub fn Registry::add(Self, &Vocabulary) -> Unit raise Refused

Add a vocabulary.

It is refused when the vocabulary's URI is already held, when one of its keywords is a name a built-in keyword answers to, or when one of them is a name another vocabulary already took. A clash is refused rather than resolved in someone's favour: a keyword that quietly means one thing to the library and another to the document is worse than one that does not load.

pub fn Registry::new() -> Self

A registry that holds nothing, and answers for no name but the built-in ones.

structReport

What a validation came to, in the shape the caller asked for.

units is the whole of it: empty for flag, flat for basic, a pruned tree for detailed, and the tree the walk produced for verbose. Report::to_json writes it as §12.4's object, which is what a caller sends over a wire.

pub(all) struct Report {
  • valid : BoolWhether the instance conformed.
  • units : Array[OutputUnit]The results, nested as the format asks.

structSchema

A schema document, read and ready to validate instances against.

The version is the one the document names in $schema, whatever draft said; a document is more specific than a setting. remotes are the documents a $ref may reach outside this one, by the URI each is registered under: this library opens no sockets, so what is not fed to it does not exist.

pub struct Schema
pub fn Schema::faults(Self, Json) -> Array[Fault]

Every reason instance failed, or an empty array when it validated.

The reasons, and not the shape of the walk: each fault says where in the instance it was, which keyword refused it, and where that keyword was. Schema::report is the other reading of the same walk, in the four formats §12 names.

let schema = @moonschema.Schema::new(
  @json.parse("{ \"type\": \"integer\", \"minimum\": 1 }"),
)
for fault in schema.faults(@json.parse("0")) {
  println(fault.to_string())
}
// the instance: below the minimum (minimum at /minimum)
pub fn Schema::new(Json, draft? : Draft, remotes? : Map[String, Json], format? : Bool, registry? : Registry) -> Self raise Refused

Read a schema document.

draft is the version to read it at when it does not name one; a $schema it does name wins. remotes are the documents a $ref may reach, each under the URI it is referred to by.

registry holds the vocabularies a document may ask for beyond the built-in ones: a $vocabulary naming one of them puts its keywords in force, naming one nobody holds with true refuses the document, and naming it with false leaves its keywords as annotations.

format decides whether format asserts. Left out, the document answers for itself: an annotation before v1 unless its dialect's metaschema declares the assertion vocabulary, and an assertion throughout v1. Given, it is the answer, because a call is more specific than a document's dialect, which is more specific than a version's default.

let schema = @moonschema.Schema::new(
  @json.parse("{ \"type\": \"object\", \"required\": [\"id\"] }"),
)
schema.valid(@json.parse("{\"id\": 1}"))   // true
pub fn Schema::report(Self, Json, output? : Output) -> Report

Validate instance and answer in the format output names.

let schema = @moonschema.Schema::new(
  @json.parse("{ \"type\": \"integer\", \"minimum\": 1 }"),
)
let report = schema.report(@json.parse("0"), output=@moonschema.Output::Basic)
println(report.to_json().stringify(indent=2))
pub fn Schema::valid(Self, Json) -> Bool

Whether instance validates.

This is the flag output (§12.4.1): the cheapest answer, and the one a guard on a request wants. Schema::faults says why not.

let schema = @moonschema.Schema::new(@json.parse("{ \"type\": \"integer\" }"))
schema.valid(@json.parse("7"))          // true
schema.valid(@json.parse("\"seven\""))   // false

structSite

Where a keyword sits, for the benefit of a keyword that has to say where it looked.

pub(all) struct Site {
  • at : StringWhere in the instance, as a JSON Pointer; empty for the instance itself.
  • schema_at : StringWhere in the schema, as a JSON Pointer, following the walk that arrived.

enumVerdict

What a keyword says about one instance.

pub(all) enum Verdict {
  • AcceptIt conforms, and has nothing to add.
  • Annotate(annotation~ : Json)It conforms, and contributes annotation to the output — which is what a keyword that annotates rather than asserts always answers.
  • Refuse(why~ : String)It does not conform, and this says why in one clause.

Errors

suberrorRefused

Why a schema could not be read.

These are faults in the schema, not in the instance an instance that fails validation produces Faults instead.

pub(all) suberror Refused {
  • Unknown(String)
  • Broken(at~ : String, why~ : String)

Traits

traitKeyword

A keyword someone outside this library defines.

It is written in a schema as Keyword::name and read once per instance, with the keyword's own value, the instance, and where both sit. It asserts, annotates, or both; it does not apply subschemas — a keyword that has to walk a subschema is an applicator, and applicators are this library's own.

struct Even {}

pub impl Keyword for Even with fn name(_self) { "x-even" }

pub impl Keyword for Even with fn judge(_self, value, instance, site) { ignore(site) match (value, instance) { (True, Number(number, ..)) => if number % 2.0 == 0.0 { Accept } else { Refuse("odd") } _ => Accept() } }

pub extend Even with Keyword::{name, judge} ```

pub(open) trait Keyword {
fn name(Self) -> String

The name it is written as in a schema.

fn judge(Self, Json, Json, Site) -> Verdict

What it says about instance at site.

traitVocabulary

A set of keywords that a document puts in force by naming its URI in $vocabulary.

The URI is the one the schema's metaschema uses — a vocabulary is identified by an absolute URI, conventionally one ending in /vocab/<name>. A document whose metaschema names this URI with true gets the keywords; with false, they annotate and assert nothing.

pub(open) trait Vocabulary {
fn uri(Self) -> String

The URI a $vocabulary names it by.

fn keywords(Self) -> Array[&Keyword]

The keywords it defines.