Gargamelle

Package Version Hex Docs

Gargamelle generates typed JSON decoders and JSON Schema 2020-12 documents from the same Gleam definition.

Technical contract.

Gargamelle also provides gargamelle/boundary APIs that bind a contract to a handler. A contract combines a definition with optional semantic checks of decoded values. Incoming JSON must satisfy that contract before the handler runs. Handlers receive decoded Gleam values and original JSON, or verified JSON.

Definitions, decoding and schema generation can be used independently of boundary APIs.

OperationAPIModule
Definition constructionstring, safe_integer, array, record, …gargamelle
Direct admitted JSON constructionjson_* constructorsgargamelle
JSON decoding into a Gleam valuedecodegargamelle
Decoding or validation with semantic requirementsdecode_contract / validate_contractgargamelle
JSON Schema generationschema_json / schema_stringgargamelle
JSON retention with the accepting contractverify → VerifiedJsongargamelle
Validated handler invocation with decoded datanew → Boundary + forwardgargamelle/boundary
Validated handler invocation with original JSONjson → JsonBoundary + forward_jsongargamelle/boundary
Typed output through a receiving boundaryoutput + sendgargamelle/boundary
Loading and saving through one contractpaired → PairedBoundary + load / savegargamelle/boundary
Typed JSON encodingencode_jsongargamelle/encoding
Encoding checked against a Contractencodegargamelle/encoding
Contract error summaries and JSON reportssummary / to_jsongargamelle/diagnostics

Getting started

Requires Gleam 1.18.1 or later.

Package installation:

gleam add gargamelle@1.0.0

JavaScript

Target configuration in gleam.toml:

target = "javascript"

Erlang

Target configuration in gleam.toml:

target = "erlang"

The native regex helper requires make and a C compiler. Helper compilation:

gleam deps download
make -C build/packages/gargamelle/priv/regex-src

Native Erlang setup documents tested runtimes and deployment requirements.

Basic example

One definition generates the schema and validates JSON during decoding:

import gargamelle
import gleam/io
import gleam/string

pub fn main() {
  let definition: gargamelle.Definition(List(String)) = gargamelle.array(gargamelle.string())
  let schema: String = gargamelle.schema_string(definition, gargamelle.Pretty)
  let assert Ok(json): Result(gargamelle.Json, _) = gargamelle.parse("[\"Brainy\", \"Jokey\"]")
  let assert Ok(data): Result(List(String), _) = gargamelle.decode(definition, json)

  io.println(schema)
  io.println(string.join(data, ", "))
}

Output:

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

Definition(List(String)) describes both accepted JSON and decoded output. parse returns opaque Json; decode produces List(String). The Result annotations describe the whole Ok(...) pattern; _ leaves the error type inferred. schema_string returns the schema as a String. Pretty uses two-space indentation; Compact produces canonical JSON. to_string(json, layout) serializes any Gargamelle Json, not just schemas.

Parsing and decoding errors are separate results. The example uses assertions for brevity; application code handles those errors explicitly. validate checks acceptance without constructing the decoded output.

Receiving boundaries

A boundary binds a handler to its input contract. Submission checks JSON against the stored contract and invokes the handler only on success, combining validation and handler invocation in one operation.

This complete example uses the same array-of-strings definition for two receivers:

import gargamelle/boundary as gargamelle_boundary
import gargamelle
import gleam/io
import gleam/string

pub fn main() {
  let names: gargamelle.Definition(List(String)) = gargamelle.array(gargamelle.string())
  let assert Ok(json): Result(gargamelle.Json, _) = gargamelle.parse("[\"Brainy\", \"Jokey\"]")

  let receiver = gargamelle_boundary.new(
    gargamelle.contract(names),
    fn(decoded_names, _original_json) { string.join(decoded_names, ", ") },
  )
  let assert Ok(response): Result(String, _) = gargamelle_boundary.forward(receiver, json)
  io.println(response)

  let json_receiver = gargamelle_boundary.json(
    gargamelle.contract(names),
    fn(verified) {
      gargamelle.to_string(gargamelle.verified_json(verified), gargamelle.Compact)
    },
  )
  let assert Ok(response): Result(String, _) = gargamelle_boundary.forward_json(json_receiver, json)
  io.println(response)
}

Output:

Brainy, Jokey
["Brainy","Jokey"]

Both boundaries check their stored contract before invoking the handler. Boundary also decodes and passes the typed data and original JSON; JsonBoundary delivers a VerifiedJson certificate. Contracts without semantic checks validate without decoding; semantic checks require one decoding pass. Structural or semantic rejection invokes neither handler.

VerifiedJson(a) retains the original admitted JSON and the exact contract that accepted it. It does not contain a decoded a. The type parameter identifies the definition’s output type, not its particular constraints: two definitions producing String can accept different inputs. Receiving boundaries always check their own stored contract; a certificate from another contract cannot bypass that check.

definition, json_definition, output_definition and paired_definition expose each boundary’s structural definition for inspection and schema generation. Verified JSON boundaries documents certificate retention and application responses.

Guarantees and scope

The technical contract specifies the JSON domain, external validator settings, decoded-value semantics and limits.

Typed records

import gargamelle

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

pub fn user_definition() -> gargamelle.Definition(User) {
  let assert Ok(fields) =
    gargamelle.record(fn(name) { fn(active) { User(name, active) } })
    |> gargamelle.required("name", gargamelle.string())
  let assert Ok(fields) = gargamelle.required(fields, "active", gargamelle.boolean())
  gargamelle.finish(fields, gargamelle.Strict)
}

pub fn read_user(source: String) {
  let assert Ok(json) = gargamelle.parse(source)
  gargamelle.decode(user_definition(), json)
}

For input {"name":"Brainy","active":true}, read_user returns Ok(User("Brainy", True)). gargamelle.Strict rejects undeclared fields. gargamelle.schema_string(user_definition(), gargamelle.Pretty) generates the record’s schema.

More definitions

FeatureConstructors
Arbitrary admitted JSONanything
Recursive structuresrecursive, recursive_pair
Tuples and array membershiptuple, element, contains
Object key and presence constraintsproperty_names, dependent_required
Conditional constraints and exclusionif_then, if_then_else, exclude
Numeric constraintsbounds, number_bounds, integer_multiple_of
Schema metadatatitle, description, examples, default_annotation, format_annotation
Explicit UUID validationassert_format(_, Uuid)

Metadata does not change acceptance. A schema default annotation does not insert a value; a defaulted record field is a separate decoding feature. format_annotation adds an annotation, while assert_format adds validation constraints.

Advanced definitions documents these features and the HTML documentation recipe.

Limitations

JavaScript is tested on Node 24.10.0, Chrome 155, Firefox 157 and Safari 27. Native Erlang requires a C regex helper and is tested on macOS arm64 with OTP 29 and Ubuntu 24.04 x86_64 with OTP 28.5.0.7 and 29.1.1. Browser compatibility and Native Erlang setup document the compatibility matrix, regex Unicode differences and deployment requirements. Other runtimes and platforms are unverified; Windows native support is unavailable.

Compatibility commitments are specified in the technical contract, section 18.

Related libraries

Gargamelle was originally inspired by glon, but is not a fork.

Sextant also combines typed decoding with JSON Schema 2020-12 generation. Its use-based field API supports definitions whose later fields depend on earlier decoded values. Gargamelle uses static definitions and curried record constructors; its schema describes structural acceptance, while application rules are checked separately. Receiving boundaries bind structural definitions and semantic requirements to handlers.

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. The package source is available in the repository and the Hex archive.

License

Gargamelle is distributed under the MIT License. Licenses contains the library, native regex engine and retained JSON Schema test-suite notices.

Authorship

This package was authored by Codex Astra, with occasional direction and review from the human repository maintainer.

✨ Search Document