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.
verify retains accepted JSON and its definition without decoding. A boundary
decodes once and passes the decoded value and original JSON to its handler.
Converters are trusted application code: exceptions are operational failures.
JavaScript and Erlang targets are supported on the
tested runtimes.
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 definition and a private handler invoked only after successful decoding
against that definition. Construct with boundary, inspect the contract with
boundary_definition, 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)
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 JSON value obtained through parse or schema_json.
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 definition and a private handler receiving verified JSON without decoding.
Construct with json_boundary, inspect with json_boundary_definition, and
submit admitted JSON with forward_json.
pub opaque type JsonBoundary(a, response)
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 definition, created by verify without decoding.
Access the JSON and contract with verified_json and verified_definition.
No decoded value is retained. The parameter a identifies the definition’s
output type, not a particular definition or its constraints.
Use a Boundary to enforce which definition checks a handler’s input.
pub opaque type VerifiedJson(a)
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 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(
definition: Plan(a, Nil),
handler: fn(a, Json) -> response,
) -> Boundary(a, response)
Bind a handler to its input definition. Construction does not validate input or invoke callbacks. The handler receives decoded data and original JSON.
pub fn boundary_definition(
boundary: Boundary(a, response),
) -> Plan(a, Nil)
Return the exact definition used by forward, for schema emission or host
review. This does not expose the handler or permit replacing its definition.
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 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 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, List(DataError))
Decode once against the boundary’s definition, then invoke its handler once.
Invalid input returns the same errors as decode and invokes no conversion
callbacks or handler. Operational failures and callback exceptions propagate.
A handler returning Result produces a nested Result; its errors remain
distinct from input validation errors. Output is not checked by this function.
pub fn forward_json(
boundary: JsonBoundary(a, response),
json: Json,
) -> Result(response, List(DataError))
Validate once against the receiving boundary’s definition, then invoke its handler once with a certificate. This never decodes or invokes converters. “Once” concerns Gargamelle validation; admission and host policy are separate. Invalid input invokes no handler. Operational failures and handler exceptions propagate. A handler’s Result response remains nested inside the outer Result. Input must be admitted JSON, not a certificate checked under another contract.
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_boundary(
definition: Plan(a, Nil),
handler: fn(VerifiedJson(a)) -> response,
) -> JsonBoundary(a, response)
Bind a JSON-only receiving handler to its authoritative definition. Construction invokes neither validation nor callbacks.
pub fn json_boundary_definition(
boundary: JsonBoundary(a, response),
) -> Plan(a, Nil)
Return the exact definition used by forward_json; the handler stays private.
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 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_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 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_definition(
verified: VerifiedJson(a),
) -> Plan(a, Nil)
Return the exact definition that accepted the stored JSON, for contract inspection or later decoding. Retrieving it invokes no conversion callbacks.
pub fn verified_json(verified: VerifiedJson(a)) -> Json
Return the original admitted JSON. This does not validate, decode or serialize.
pub fn verify(
definition: Plan(a, Nil),
json: Json,
) -> Result(VerifiedJson(a), List(DataError))
Check JSON against a definition and retain the JSON and checked definition.
This invokes no conversion callbacks and constructs no decoded value.
Rejection and operational failures have the same semantics as validate.