Gargamelle

Package Version Hex Docs

Gargamelle generates a typed JSON decoder and a JSON Schema 2020-12 document from one Gleam definition.

See the technical contract.

Gargamelle also provides boundary APIs that bind a definition to a handler. Incoming JSON is validated against that definition before the handler runs. Handlers receive decoded Gleam values and the original JSON, or verified JSON without decoding.

Boundary APIs are optional; definitions, decoding and schema generation can be used directly.

Definitions can also generate browsable HTML documentation.

OperationAPI
JSON decoding into a Gleam valuedecode
JSON Schema generationschema_json / schema_string
Validated handler invocation with decoded databoundary → Boundary + forward
Validated handler invocation without decodingjson_boundary → JsonBoundary + forward_json
JSON retention with the accepting definitionverify → VerifiedJson

Getting started

Requires Gleam 1.18.1 or later.

Package installation:

gleam add gargamelle@0.4.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

import gargamelle
import gleam/io
import gleam/string

pub fn main() {
  let definition: gargamelle.Definition(List(String)) = gargamelle.array(gargamelle.text())
  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, format) 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 definition. Submission validates JSON against the stored definition 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
import gleam/io
import gleam/string

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

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

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

Output:

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

Both boundaries check their stored definition before invoking the handler. Boundary also decodes and passes the typed data and original JSON; JsonBoundary delivers a VerifiedJson certificate without running decoding conversions. Invalid input does not invoke either handler.

VerifiedJson(a) retains the original admitted JSON and the exact definition 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 definition; a certificate from another definition cannot bypass that check.

boundary_definition and json_boundary_definition expose the receiving 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 as s

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

pub fn user_definition() -> s.Definition(User) {
  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.Strict)
}

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

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

More definitions

FeatureConstructors
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, format
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 is 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.

APIs may change before 1.0.

Related libraries

Gargamelle was inspired by glon.

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 those definitions 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 Hex archive.

License

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

✨ Search Document