gargamelle
Typed JSON decoding and JSON Schema 2020-12 emission from one definition.
Parse JSON with parse, then call validate or decode. Validation never
invokes user converters; decoding invokes them only after structural success.
Contract adds semantic requirements above a structural definition. Boundaries
enforce their stored contract before invoking handlers. verify retains
original JSON and the accepting contract; semantic requirements decode once.
Converters are trusted application code: exceptions are operational failures.
JavaScript and Erlang targets are supported on the
tested runtimes.
Explicit typed encoders are provided by gargamelle/encoding.
The Erlang target requires a separately compiled C regex helper.
Types
JSON syntax, nonfinite-number or non-scalar-string admission failure.
pub type AdmissionError {
AdmissionError(String)
}
Constructors
-
AdmissionError(String)
Boundary
opaqueA contract and a private handler invoked only after successful decoding and
semantic checks. Construct with boundary, inspect with boundary_contract,
and submit JSON with forward.
pub opaque type Boundary(a, response)
An invalid definition, constraint, default, or reference graph.
pub type ConstructionError {
ConstructionError(String)
}
Constructors
-
ConstructionError(String)
Contract
opaqueA completed structural definition and additional checks of decoded values. Checks must be deterministic, total and free of side effects, with immutable bound context. They cannot transform the decoded value or the original JSON.
pub opaque type Contract(a)
Structural decoding rejection or rejection by semantic requirements. Exceptions and resource failures are operational failures, not these values.
pub type ContractError {
Structural(List(DataError))
Semantic(List(RequirementFailure))
}
Constructors
-
Structural(List(DataError)) -
Semantic(List(RequirementFailure))
A structural validation failure. path is a JSON Pointer (empty at the root).
branches contains indexed union errors and matches identifies matching
branches. Categories and diagnostic wording may evolve before version 1.0.
pub type DataError {
DataError(
category: String,
path: String,
missing_key: option.Option(String),
branches: List(#(Int, List(DataError))),
matches: List(Int),
)
}
Constructors
-
DataError( category: String, path: String, missing_key: option.Option(String), branches: List(#(Int, List(DataError))), matches: List(Int), )
A completed definition, usable for validation, decoding and emission.
pub type Definition(a) =
Plan(a, Nil)
Supported asserted formats. Assertions are emitted as ordinary schema constraints as well as format metadata; optional format checking is unnecessary.
pub type FormatAssertion {
Uuid
}
Constructors
-
Uuid
Json
opaqueAn opaque admitted JSON value obtained through parsing, public JSON
constructors, schema generation or encoding.
Numbers are finite binary64 values; strings and keys contain Unicode scalars.
This type is distinct from gleam/json.Json; arbitrary runtime objects cannot
enter the validation boundary.
pub opaque type Json
JsonBoundary
opaqueA contract and a private handler receiving verified JSON. Semantic checks
require decoding once; contracts without checks validate without conversion.
Construct with json_boundary, inspect with json_boundary_contract, and
submit admitted JSON with forward_json.
pub opaque type JsonBoundary(a, response)
A rejected direct JSON construction. Values remain opaque and admitted. Object constructors reject duplicate keys instead of silently replacing them.
pub type JsonError {
NonFiniteNumber
UnsafeInteger
NonScalarString
NonScalarKey(key: String)
DuplicateKey(key: String)
}
Constructors
-
NonFiniteNumber -
UnsafeInteger -
NonScalarString -
NonScalarKey(key: String) -
DuplicateKey(key: String)
JSON text layout. Compact uses canonical JSON; Pretty adds two-space
indentation without changing numbers, escaping or key order. Neither adds
a trailing newline. Empty arrays and objects stay on one line.
pub type JsonFormat {
Compact
Pretty
}
Constructors
-
Compact -
Pretty
A finite numeric endpoint. Inclusive endpoints accept equality.
pub type NumberBound {
Inclusive(Float)
Exclusive(Float)
}
Constructors
-
Inclusive(Float) -
Exclusive(Float)
Plan
opaqueA composable acceptance contract and typed conversion under recursion r.
Only completed definitions, Plan(a, Nil), can be validated, decoded or emitted.
Recursive builder references remain construction plans after the builder returns.
pub opaque type Plan(a, r)
Record
opaqueAn unfinished record consuming a curried constructor one field at a time.
pub opaque type Record(a, r)
Treatment of undeclared record keys. Strict rejects them; Open accepts
them but omits them from the constructed output.
pub type RecordPolicy {
Strict
Open
}
Constructors
-
Strict -
Open
Recursive
opaqueThe opaque recursion environment supplied to a self-recursive plan. User code cannot construct or execute this environment.
pub opaque type Recursive(a)
Tuple
opaqueAn unfinished exact-length tuple consuming a curried constructor by position.
pub opaque type Tuple(a, r)
Variant
opaqueA record branch for tagged, with its discriminator supplied automatically.
pub opaque type Variant(a, r)
VerifiedJson
opaqueJSON accepted by the stored full contract, created by verify.
Semantic requirements cause one decoding pass; no decoded value is retained.
Access the JSON and contract with verified_json and verified_contract.
The parameter a identifies the definition’s output type, not a particular
contract or its constraints.
Use a receiving boundary to enforce which contract checks a handler’s input.
pub opaque type VerifiedJson(a)
A semantic violation. path is a JSON Pointer, empty for the whole input.
Locations and messages are supplied by trusted semantic checks.
pub type Violation {
Violation(path: String, message: String)
}
Constructors
-
Violation(path: String, message: String)
Values
pub fn any_of(
defs: List(Plan(a, r)),
) -> Result(Plan(a, r), ConstructionError)
Accept one or more matching branches and decode with the first matching branch in list order. Empty unions fail construction.
pub fn anything() -> Plan(Json, r)
Accept any admitted JSON and return the original opaque Json unchanged. No schema constraints or conversions are applied. Admission still excludes nonfinite numbers and non-scalar strings/keys; this is not arbitrary runtime data. The schema is unconstrained within the documented admitted JSON domain.
pub fn array(def: Plan(a, r)) -> Plan(List(a), r)
Accept homogeneous arrays and decode every element in input order.
pub fn assert_format(
def: Plan(a, r),
assertion: FormatAssertion,
) -> Plan(a, r)
Assert a supported format on string inputs, preserving the base converter. UUID accepts the 36-character, case-insensitive hexadecimal/dash syntax, including nil UUIDs, without a URN prefix or whitespace. Version/variant bits are not restricted. Nonstrings remain governed by the base definition.
pub fn boundary(
contract: Contract(a),
handler: fn(a, Json) -> response,
) -> Boundary(a, response)
Bind a handler to its authoritative contract. Construction runs no callbacks.
pub fn boundary_contract(
boundary: Boundary(a, response),
) -> Contract(a)
Return the receiving contract without exposing its handler.
pub fn boundary_definition(
boundary: Boundary(a, response),
) -> Plan(a, Nil)
Return the receiving contract’s structural definition for schema generation.
pub fn bounds(
def: Plan(a, r),
lower: option.Option(Int),
upper: option.Option(Int),
) -> Result(Plan(a, r), ConstructionError)
Intersect inclusive integer bounds, Unicode code-point string lengths,
homogeneous array lengths or dictionary key counts. None leaves a side
unbounded. Unsupported definitions and invalid intervals fail construction.
pub fn contains(
def: Plan(a, r),
matching: Plan(b, r),
minimum: Int,
maximum: option.Option(Int),
) -> Result(Plan(a, r), ConstructionError)
Require between minimum and maximum structurally matching array items. Preserve the original decoder; matching-definition converters never run.
pub fn contract(definition: Plan(a, Nil)) -> Contract(a)
Wrap a completed definition without adding semantic requirements.
pub fn contract_definition(contract: Contract(a)) -> Plan(a, Nil)
Return the structural definition. Its schema does not describe arbitrary semantic requirements and is not sufficient to establish contract acceptance.
pub fn contract_requirements(
contract: Contract(a),
) -> List(String)
Return every requirement description in declaration order, without callbacks.
pub fn decode(
def: Plan(a, Nil),
value: Json,
) -> Result(a, List(DataError))
Validate the entire input before running typed conversion. Invalid inputs run no converters; exceptions from trusted callbacks propagate.
pub fn decode_contract(
contract: Contract(a),
json: Json,
) -> Result(a, ContractError)
Decode once, then run each semantic requirement once in declaration order. Structural rejection skips all checks. Semantic failures are collected; exceptions propagate and may prevent later checks from running.
pub fn default(def: Plan(a, r), value: Json) -> Plan(a, r)
Attach a JSON Schema default annotation without inserting values during
decoding or checking this sample against the definition. defaulted is the
separate record-field operation for validated defaults applied on absence.
A defaulted field’s declared default takes precedence over child annotations.
pub fn defaulted(
r: Record(fn(a) -> b, r),
name: String,
child: Plan(a, r),
value: Json,
) -> Result(Record(b, r), ConstructionError)
Add a field with an admitted JSON default validated during construction. When absent, decode the default with the child converter. A present invalid value still fails; defaults do not mutate the input or bypass validation.
pub fn dependent_required(
def: Plan(a, r),
trigger: String,
required: List(String),
) -> Result(Plan(a, r), ConstructionError)
When trigger is present in the original object, require every listed key.
Null counts as present; decoded defaults do not create input presence.
Repeated rules for the same trigger merge their keys. Duplicate keys are
normalized, and empty lists and self-dependencies are allowed.
pub fn description(def: Plan(a, r), s: String) -> Plan(a, r)
Add a schema description without changing validation or decoding.
pub fn dictionary(def: Plan(a, r)) -> Plan(List(#(String, a)), r)
Accept objects whose values satisfy the child definition. Decode to a Gleam list of key/value pairs, preserving original keys rather than a JS object.
pub fn element(
t: Tuple(fn(a) -> b, r),
child: Plan(a, r),
) -> Tuple(b, r)
Append one required position, consuming one constructor argument.
pub fn examples(
def: Plan(a, r),
values: List(Json),
) -> Plan(a, r)
Attach the JSON Schema examples annotation without affecting acceptance or
conversion. Examples are admitted JSON, but need not satisfy the definition.
A later call replaces an earlier examples annotation at the same schema node.
pub fn exclude(
def: Plan(a, r),
forbidden: Plan(b, r),
) -> Plan(a, r)
Reject input accepted by a forbidden structural definition. Preserve the base output; forbidden-definition converters never run.
pub fn finish(
r: Record(a, r),
policy: RecordPolicy,
) -> Plan(a, r)
Finish a typed record with the chosen undeclared-key policy.
pub fn finish_tuple(t: Tuple(a, r)) -> Plan(a, r)
Close a tuple. Short arrays and extra positions are rejected.
pub fn format(def: Plan(a, r), name: String) -> Plan(a, r)
Attach a JSON Schema format annotation, such as “email” or “uuid”.
This does not check the format, change acceptance or invoke converters.
Custom names are allowed. Names must contain only Unicode scalar values.
pub fn forward(
boundary: Boundary(a, response),
json: Json,
) -> Result(response, ContractError)
Decode and check the stored contract once, then invoke the handler once. Rejection invokes no handler. Callback exceptions propagate operationally. A Result response remains nested; output is not checked.
pub fn forward_json(
boundary: JsonBoundary(a, response),
json: Json,
) -> Result(response, ContractError)
Verify the stored contract once, then invoke the handler once. Semantic requirements cause one decoding pass; without them no converter runs. Admission and application policy are separate. Certificates from other contracts cannot bypass this check. Exceptions propagate operationally.
pub fn if_then(
def: Plan(a, r),
condition: Plan(b, r),
consequence: Plan(c, r),
) -> Plan(a, r)
Validate the consequence only when the structural condition accepts input. Retain the base output; condition/consequence converters never run.
pub fn if_then_else(
def: Plan(a, r),
condition: Plan(b, r),
consequence: Plan(c, r),
alternative: Plan(d, r),
) -> Plan(a, r)
Validate exactly the selected structural branch, retaining the base decoder.
pub fn integer_constant(
v: Int,
) -> Result(Plan(Int, r), ConstructionError)
Accept exactly this safe integer; reject constants outside the safe range.
pub fn integer_multiple_of(
def: Plan(a, r),
divisor: Int,
) -> Result(Plan(a, r), ConstructionError)
Require safe-integer inputs divisible by a positive safe-integer divisor. This exact integer policy does not accept fractional divisors or number() definitions. Apply before naming. Repeated constraints intersect.
pub fn json_array(values: List(Json)) -> Json
Construct a JSON array from already admitted children, preserving list order. Child values are not serialized, parsed or recursively rechecked.
pub fn json_boundary(
contract: Contract(a),
handler: fn(VerifiedJson(a)) -> response,
) -> JsonBoundary(a, response)
Bind a JSON receiving handler to its authoritative contract.
pub fn json_boundary_contract(
boundary: JsonBoundary(a, response),
) -> Contract(a)
Return the receiving contract without exposing its handler.
pub fn json_boundary_definition(
boundary: JsonBoundary(a, response),
) -> Plan(a, Nil)
Return the receiving contract’s structural definition for schema generation.
pub fn json_int(value: Int) -> Result(Json, JsonError)
Construct a JSON number from an Int in the common safe-integer range. Reject larger integers rather than silently rounding them to binary64.
pub fn json_number(value: Float) -> Result(Json, JsonError)
Construct a finite binary64 JSON number, preserving its numeric value.
pub fn json_object(
fields: List(#(String, Json)),
) -> Result(Json, JsonError)
Construct a JSON object from already admitted children. Keys must contain Unicode scalars and be unique. Declaration order is retained; serialization uses the usual canonical key ordering. Keys such as proto are ordinary keys. The first invalid or repeated key identifies the rejected declaration.
pub fn json_string(value: String) -> Result(Json, JsonError)
Construct a JSON string containing only Unicode scalar values.
pub fn lift(def: Plan(a, Nil)) -> Plan(a, r)
Compose a completed definition inside a recursive builder. Its converter ignores the builder environment and retains its own recursion.
pub fn map(def: Plan(a, r), f: fn(a) -> b) -> Plan(b, r)
Transform decoded output without changing schema acceptance. The callback runs only during successful decoding and must return the declared output type.
pub fn named(
name: String,
def: Plan(a, r),
) -> Result(Plan(a, r), ConstructionError)
Name a definition for local $defs/$ref reuse. Names must start with an
ASCII letter and contain only ASCII letters, digits, underscores or hyphens.
Conflicting names fail when the composed graph is validated or emitted.
pub fn nullable(def: Plan(a, r)) -> Plan(option.Option(a), r)
Accept null as None, or decode the child as Some. This does not make
a record field optional.
pub fn number() -> Plan(Float, r)
Decode any admitted finite IEEE-754 number to Float, including integers. JSON parsing may round or underflow; decoding preserves the parsed value.
pub fn number_bounds(
def: Plan(Float, r),
lower: option.Option(NumberBound),
upper: option.Option(NumberBound),
) -> Result(Plan(Float, r), ConstructionError)
Intersect a number definition’s interval with finite inclusive/exclusive bounds. Reject inverted intervals and exclusive equal endpoints.
pub fn one_of(
defs: List(Plan(a, r)),
) -> Result(Plan(a, r), ConstructionError)
Accept exactly one structurally matching branch and decode with it. Overlapping matches are rejected. Empty unions fail construction.
pub fn optional(
r: Record(fn(option.Option(a)) -> b, r),
name: String,
child: Plan(a, r),
) -> Result(Record(b, r), ConstructionError)
Add an optional field, consuming an Option(a) constructor argument.
Absence is None; present values must satisfy the child, including null.
pub fn parse(text: String) -> Result(Json, AdmissionError)
Admit JSON text without invoking a definition or converter. Duplicate object keys use the parser’s last value; numeric values use IEEE-754 binary64. String values and object keys must contain only Unicode scalar values.
pub fn pattern(
def: Plan(String, r),
p: String,
) -> Result(Plan(String, r), ConstructionError)
Intersect an unanchored ECMAScript Unicode regular expression with a string definition. Anchor explicitly for whole-string matching. Invalid expressions and unsupported underlying definitions fail construction.
pub fn property_names(
def: Plan(a, r),
keys: Plan(String, r),
) -> Result(Plan(a, r), ConstructionError)
Constrain every own object key without changing decoded values or keys. The key definition is used for validation only; its converter never runs.
pub fn record(constructor: a) -> Record(a, r)
Start a record with a curried constructor. Each field consumes one argument;
call finish after supplying all fields. Multi-argument constructors need currying.
pub fn recursive(
name: String,
build: fn(Plan(a, Recursive(a))) -> Result(
Plan(a, Recursive(a)),
ConstructionError,
),
) -> Result(Plan(a, Nil), ConstructionError)
Build a guarded self-recursive definition, running the builder once. The supplied plan can compose inside the builder but cannot be decoded, validated or emitted independently, including after construction completes.
pub fn recursive_pair(
first_name: String,
second_name: String,
build: fn(
Plan(a, #(Recursive(a), Recursive(b))),
Plan(b, #(Recursive(a), Recursive(b))),
) -> Result(
#(
Plan(a, #(Recursive(a), Recursive(b))),
Plan(b, #(Recursive(a), Recursive(b))),
),
ConstructionError,
),
) -> Result(#(Plan(a, Nil), Plan(b, Nil)), ConstructionError)
Build two guarded recursive definitions with independent output types. Both builder plans share a typed pair environment; return both bodies once.
pub fn require(
contract: Contract(a),
description: String,
check: fn(a) -> Result(Nil, List(Violation)),
) -> Contract(a)
Add a requirement without running it. Checks inspect decoded values, so projected extra fields and distinctions removed by maps are unavailable. Description strings must contain Unicode scalar values.
pub fn required(
r: Record(fn(a) -> b, r),
name: String,
child: Plan(a, r),
) -> Result(Record(b, r), ConstructionError)
Add a required field. Duplicate field names fail construction.
pub fn safe_integer() -> Plan(Int, r)
Accept integral numbers between -9007199254740991 and 9007199254740991.
pub fn schema_json(def: Plan(a, Nil)) -> Json
Return a JSON Schema 2020-12 document as Gargamelle’s opaque Json, not
gleam/json.Json. Unfinished or conflicting reference graphs fail operationally.
pub fn schema_string(
def: Plan(a, Nil),
format: JsonFormat,
) -> String
Return a JSON Schema 2020-12 string in the chosen layout, including local references.
pub fn string_enum(
values: List(String),
) -> Result(Plan(String, r), ConstructionError)
Accept one of the supplied strings. Empty or duplicate alternatives fail construction.
pub fn tagged(
discriminator: String,
variants: List(Variant(a, r)),
) -> Result(Plan(a, r), ConstructionError)
Construct a discriminated union of record variants. Empty lists, duplicate tags and fields redeclaring the discriminator are construction errors.
pub fn title(def: Plan(a, r), s: String) -> Plan(a, r)
Add a schema title without changing validation or decoding.
pub fn to_gleam_json(value: Json) -> json.Json
Convert already admitted JSON to the standard gleam/json representation. This is a one-way representation conversion, not validation or certification. Standard JSON serialization rules apply, including normalization of zero signs.
pub fn to_string(value: Json, format: JsonFormat) -> String
Serialize any admitted JSON value in the chosen layout. Negative zero
becomes 0. Compact is canonical; Pretty changes only whitespace.
pub fn tuple(constructor: a) -> Tuple(a, r)
Start an exact-length positional array with a curried typed constructor.
pub fn unique_items(
def: Plan(List(a), r),
) -> Result(Plan(List(a), r), ConstructionError)
Require structurally distinct JSON items in a direct homogeneous array definition. Uniqueness is checked before mapping, not on decoded outputs.
pub fn validate(
def: Plan(a, Nil),
value: Json,
) -> Result(Nil, List(DataError))
Validate an admitted value without invoking constructors or map callbacks. Unfinished/conflicting reference graphs and resource exhaustion are operational failures rather than data errors.
pub fn validate_contract(
contract: Contract(a),
json: Json,
) -> Result(Nil, ContractError)
Check contract acceptance. A contract without semantic requirements validates without conversion; otherwise decoding occurs once to supply the checks.
pub fn variant(
tag: String,
r: Record(a, r),
policy: RecordPolicy,
) -> Variant(a, r)
Finish a tagged record branch. Do not declare the discriminator as a field.
pub fn variant_description(
v: Variant(a, r),
s: String,
) -> Variant(a, r)
Describe a tagged branch without changing its acceptance contract.
pub fn variant_map(
v: Variant(a, r),
f: fn(a) -> b,
) -> Variant(b, r)
Transform a tagged branch’s decoded output without changing its schema.
pub fn verified_contract(
verified: VerifiedJson(a),
) -> Contract(a)
Return the exact contract, including checks and captured context, that accepted the stored JSON. The output type parameter alone does not identify guarantees.
pub fn verified_definition(
verified: VerifiedJson(a),
) -> Plan(a, Nil)
Return the checked contract’s underlying structural definition.
pub fn verified_json(verified: VerifiedJson(a)) -> Json
Return original admitted JSON without validation, decoding or serialization.
pub fn verify(
contract: Contract(a),
json: Json,
) -> Result(VerifiedJson(a), ContractError)
Verify a full contract and retain original JSON and that exact contract. No decoded value is retained. Semantic checks require decoding once; contracts without them validate without conversion.