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
-
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, )The document is well formed but contradicts itself, such as an enum label listed twice or a reversed range.
Why parse refused schema text.
pub type LoadError {
InvalidJson(value.ParseError)
InvalidDocument(DocumentError)
}
Constructors
-
InvalidJson(value.ParseError) -
InvalidDocument(DocumentError)
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
-
ValidationError( path: List(codec.PathSegment), reason: codec.Reason, )
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 validate(
contract: Contract,
item: value.Value,
) -> Result(ValidatedValue, ValidationError)
Check a value against the contract.
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.