Gargamelle

Package Version Hex Docs

Gargamelle builds a typed JSON decoder and a JSON Schema 2020-12 document from a single Gleam definition. It can also generate formatted, navigable HTML documentation with checked examples.

The technical contract defines how schema validation and decoding agree, what decoding produces, and the assumptions and limits of those guarantees.

Gargamelle grew from an attempt to use glon for a project into an independent implementation. No glon source code was copied.

Developed with Codex Astra.

Getting started

Requires Gleam 1.18.1 or later.

For Erlang, follow Native Erlang setup for target selection and helper installation.

JavaScript

Install Gargamelle:

gleam add gargamelle@0.2.1

Set target = "javascript" in your project’s gleam.toml:

target = "javascript"
import gargamelle

pub fn read_names(source: String) {
  let assert Ok(value) = gargamelle.parse(source)
  gargamelle.decode(gargamelle.array(gargamelle.text()), value)
}

pub fn names_schema() {
  gargamelle.emit_text(gargamelle.array(gargamelle.text()))
}

For input ["Ada", "Grace"], read_names returns Ok(["Ada", "Grace"]). names_schema() returns the following schema text:

{"$schema":"https://json-schema.org/draft/2020-12/schema","items":{"type":"string"},"type":"array"}

Handle admission errors separately from validation errors in application code; these assertions only keep the introductory example short. validate checks acceptance without invoking converters. decode validates before constructing output. See the typed record example below, the recursive examples, and the API reference.

Typed records

import gargamelle as s

pub type User { User(name: String, active: Bool) }

pub fn user_definition() {
  let assert Ok(fields) =
    s.record(fn(name) { fn(active) { User(name, active) } })
    |> s.required("name", s.text())
  let assert Ok(fields) = s.required(fields, "active", s.boolean())
  s.finish(fields, s.Closed)
}

s.emit_text(definition) emits canonical schema text. s.parse(text) admits JSON; s.decode(definition, value) validates before running constructor/mapping functions. Admission errors, data errors and operational exceptions remain distinct. The safe-integer and trusted-callback limitations are part of the contract.

Design and contract

A Definition(a) specifies an acceptance predicate over admitted JSON and a conversion from accepted inputs to a. The definition is authoritative; schema emission, validation and decoding are interpretations of the same structural representation. The opaque API prevents independently injecting a schema, decoder or arbitrary rejection predicate.

For a finalized definition S and admitted input x, the intended invariant is:

Valid202012(emit_json(S), x)
    ⇔ validate(S, x) = Ok(Nil)
    ⇔ ∃v. decode(S, x) = Ok(v)

This is an equivalence of acceptance, plus a contract for v. Arrays preserve order and duplicates; dictionaries preserve keys; records construct declared types with explicit presence, default and extra-field policies. any_of converts the first matching branch; one_of accepts exactly one match. Maps change the output but cannot add rejection rules. Optional nullable fields retain three states: missing, present null, and present non-null.

Construction is static. Curried record builders collect field definitions without probing callbacks with fabricated values. Validation and emission never execute conversion callbacks. Decoding validates the original input before conversion; auxiliary conditions, membership, exclusions and key constraints inspect that input without running their converters. Semantic application checks belong after structural decoding.

The input domain is finite JSON trees after parsing, with finite binary64 numbers and Unicode scalar strings and keys. Parsing may round literals and collapse duplicate object keys. Domain checks apply to the final parsed tree. The equivalence assumes pure, total callbacks, sufficient runtime resources, and external validation of the same parsed value without coercion, inserted defaults or removed properties. Admission failure, structural rejection and operational failure are distinct; exceptions and resource exhaustion are not rejection.

emit_text(S) is byte-stable canonical JSON. Parsing it recovers emit_json(S) as a JSON value. This is a schema serialization round trip; there is no typed encoder or decode/encode inversion guarantee. Defaults, projection and maps can lose information, and some output values may be unreachable.

The technical contract specifies the complete intended semantics. Preservation arguments and differential tests support these invariants; they are not a machine-checked proof or a claim of full JSON Schema dialect support.

Limitations

JavaScript is tested on Node 24.10.0, Chrome 155, Firefox 157 and Safari 27; see Browser compatibility. The native Erlang implementation is tested on macOS arm64 with OTP 29 and Ubuntu 24.04 x86_64 with OTP 28.5.0.7 and 29.1.1, and requires a C helper; see Native Erlang setup. APIs may change before 1.0.

Further documentation

Verification

The library is checked with typed-output and rejection fixtures, generated compositions, independent JSON Schema validators, selected official suite cases and deliberate implementation mutations. These provide evidence for the contract, not a formal proof or full-dialect conformance. See the verification guide for commands, coverage and known validator discrepancies. The package source is available in the Hex archive.

License

Gargamelle is distributed under the MIT License. The retained JSON Schema test-suite fixtures carry their own license.