json/blueprint/contract

Schema contracts: a JSON Schema accepted at runtime, values validated against it, and decoding of validated values.

Build a Contract from a codec (from_codec), from a codec.Schema (from_schema), or from a Draft 2020-12 schema document (load for a parsed Value, parse for text). A document must stay inside the finite profile that codecs describe: closed objects, pairs, lists, nullable values, bounded integers and numbers, string enums, tagged unions and any value ({}, or the boolean schema true). Objects require explicit properties, required, and additionalProperties: false. Profile restrictions return UnsupportedDocument, including default-open objects and one-sided numeric bounds. Wrong keyword types return MalformedDocument. These are first error diagnostics, not general JSON Schema meta-schema validation.

validate checks a Value and returns a ValidatedValue or a ValidationError with the path of the first failure; validation uses the same codec.Reason vocabulary as decoding. decode decodes a validated value with a codec whose schema matches the contract, and fails with ContractMismatch otherwise. value_codec is a Codec(Value) that validates while encoding and decoding, for passing schema-checked JSON through without changing its representation.

Use this module when a schema arrives at runtime, such as a tool schema from a remote server. To decode JSON text with a known codec, codec.decode_json is enough. Validation walks the supplied value and imposes no independent resource limits. Parse with explicit limits before validating untrusted input; manually constructed values have no parse bound.

import json/blueprint/codec
import json/blueprint/contract
import json/blueprint/value

pub fn example() {
  let names = codec.list(codec.string())
  let schema_text =
    "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\","
    <> "\"type\":\"array\",\"items\":{\"type\":\"string\"}}"
  let assert Ok(remote) = contract.parse(schema_text, value.default_limits())
  let assert Ok(parsed) = value.parse("[\"a\"]", value.default_limits())
  let assert Ok(validated) = contract.validate(remote, parsed)
  let assert Ok(["a"]) = contract.decode(names, validated)
}

Types

A schema accepted for validation, normalized so that member and label order does not matter.

pub opaque type Contract

Why load refused a schema document, at path within the document. The strings name keywords and dialects from the document.

pub type DocumentError {
  MalformedDocument(
    path: List(codec.PathSegment),
    reason: MalformedReason,
  )
  UnsupportedDocument(
    path: List(codec.PathSegment),
    reason: UnsupportedReason,
  )
  UnsupportedDialect(
    path: List(codec.PathSegment),
    dialect: String,
  )
  InvalidDefinition(
    path: List(codec.PathSegment),
    error: codec.DefinitionError,
  )
}

Constructors

Why parse refused schema text.

pub type LoadError {
  InvalidJson(value.ParseError)
  InvalidDocument(DocumentError)
}

Constructors

An observed schema violation, such as a wrong keyword type or duplicate name. Historical profile-shape reasons remain available for compatibility.

pub type MalformedReason {
  ExpectedObject
  ExpectedText
  ExpectedInteger
  ExpectedNumber
  ExpectedBoolean
  ExpectedArray
  MissingKeyword(name: String)
  DuplicateKey(name: String)
  DuplicateRequiredName(name: String)
  UndeclaredRequiredName(name: String)
  InvalidNullableAlternatives
  InvalidTaggedAlternatives
  InvalidTupleShape
  IncompleteIntegerRange
  IncompleteNumberRange
  ExpectedObjectSchema
  ExpectedClosedTag
}

Constructors

  • ExpectedObject
  • ExpectedText
  • ExpectedInteger
  • ExpectedNumber
  • ExpectedBoolean
  • ExpectedArray
  • MissingKeyword(name: String)
  • DuplicateKey(name: String)
  • DuplicateRequiredName(name: String)
  • UndeclaredRequiredName(name: String)
  • InvalidNullableAlternatives
  • InvalidTaggedAlternatives
  • InvalidTupleShape
  • IncompleteIntegerRange
  • IncompleteNumberRange
  • ExpectedObjectSchema
  • ExpectedClosedTag

An encountered keyword or form outside the codec profile. This is not certification that the whole document is a valid JSON Schema: loading stops at the first error and does not run a general meta-schema validator.

pub type UnsupportedReason {
  UnsupportedKeyword(name: String)
  NestedDialect
  UnsupportedSchemaForm
  OpenObject
  ArbitraryUnion
  UnboundedArray
  UnsupportedTupleForm
  UnsupportedTaggedShape
}

Constructors

  • UnsupportedKeyword(name: String)
  • NestedDialect
  • UnsupportedSchemaForm
  • OpenObject
  • ArbitraryUnion
  • UnboundedArray
  • UnsupportedTupleForm
  • UnsupportedTaggedShape

A value that passed validate, with the schema it passed.

pub opaque type ValidatedValue

The first failure of validate, at path. Read fields by label.

pub type ValidationError {
  ValidationError(
    path: List(codec.PathSegment),
    reason: codec.Reason,
  )
}

Constructors

Values

pub fn decode(
  codec: codec.Codec(a),
  validated: ValidatedValue,
) -> Result(a, codec.DecodeError)

Decode a validated value with a codec whose normalized schema structure matches the validating contract. A codec with another schema, or none, fails with DecodeError([], ContractMismatch).

pub fn describe_document_error(error: DocumentError) -> String

Render a document error as text such as $["properties"]["age"]["format"]: unsupported keyword "format".

pub fn describe_load_error(error: LoadError) -> String

Render a load error: a parse error or a document error.

pub fn describe_validation_error(
  error: ValidationError,
) -> String

Render a validation error like codec.describe_decode_error, such as $["limit"]: integer outside range 1 to 10.

pub fn from_codec(
  codec: codec.Codec(a),
) -> Result(Contract, codec.SchemaError)

The contract of a codec’s schema. Panics on a definition mistake, like other uses of the codec.

pub fn from_schema(schema: codec.Schema) -> Contract

The contract of a schema, such as one that a tool declaration holds. Every Schema comes from a codec or a contract and is already checked, so this cannot fail.

pub fn load(
  document: value.Value,
) -> Result(Contract, DocumentError)

Load a Draft 2020-12 schema document. Its $schema must be the Draft 2020-12 URI, and it must stay inside the codec profile; description keywords are kept. The root must be an object with that dialect declaration. An otherwise empty object schema, or nested boolean true, accepts any value, like codec.value(). Missing profile-required keywords and otherwise valid unsupported forms return UnsupportedDocument; no constraints are silently discarded.

pub fn parse(
  text: String,
  limits: value.Limits,
) -> Result(Contract, LoadError)

Parse a schema document within limits and load it.

pub fn same_schema(left: Contract, right: Contract) -> Bool

Whether two contracts have the same normalized schema structure. Descriptions and property, label, and variant order do not affect matching.

pub fn schema(contract: Contract) -> codec.Schema

The contract’s schema, normalized.

pub fn validate(
  contract: Contract,
  item: value.Value,
) -> Result(ValidatedValue, ValidationError)

Check a value against the contract.

pub fn value(validated: ValidatedValue) -> value.Value

The value that passed validation.

pub fn value_codec(
  contract: Contract,
) -> codec.Codec(value.Value)

A Codec(Value) with the contract’s schema. Encoding and decoding validate against that contract. Successful conversion returns the value unchanged; failure retains the first validation path and reason in EncodeError or DecodeError. Enclosing fields and lists prepend their paths as usual.

Validation checks structure and supported value constraints on each call, including exact numbers beyond native Int/Float representation. It adds no independent resource limits. Typed and generated codecs keep their own encoding checks; this adapter does not add a validation pass to them.

✨ Search Document