Migrating to 0.2.0
Version 0.2.0 restricts strings and keys to Unicode scalar values and changes recursive construction to distinguish unfinished plans from runnable definitions. It also adds a native Erlang implementation; see Native Erlang setup for installation and supported runtimes.
Strings and keys
JSON strings and object keys must contain Unicode scalar values. Unpaired
surrogates fail admission; valid surrogate pairs are preserved. Schema text
arguments have the same requirement. Result constructors return construction
errors for invalid text; direct constructors assert the precondition.
Recursive construction
Ordinary definitions still use Definition(a). Constructors now compose
Plan(a, r), and Definition(a) is its completed Plan(a, Nil) specialization.
Most code needs no new annotations. For example, this definition has the same
shape in 0.1.0 and 0.2.0:
pub type Tree {
Leaf(String)
Branch(List(Tree))
}
pub fn tree() -> Result(s.Definition(Tree), s.ConstructionError) {
s.recursive("tree", fn(self) {
s.any_of([s.map(s.text(), Leaf), s.map(s.array(self), Branch)])
})
}
Examples below assume import gargamelle as s and, where needed,
import gleam/result.
Inside a builder, an existing completed definition needs lift:
// 0.1.0
s.recursive("tree", fn(self) {
s.any_of([leaf, s.map(s.array(self), Branch)])
})
// 0.2.0: leaf is an existing Definition(Tree).
s.recursive("tree", fn(self) {
s.any_of([s.lift(leaf), s.map(s.array(self), Branch)])
})
A generic helper used both inside and outside recursive builders carries the same recursion parameter through its signature:
// 0.1.0
pub fn children(child: s.Definition(a)) -> s.Definition(List(a)) {
s.array(child)
}
// 0.2.0
pub fn children(child: s.Plan(a, r)) -> s.Plan(List(a), r) {
s.array(child)
}
Annotated unfinished builders change from Record(a), Tuple(a) and Variant(a)
to Record(a, r), Tuple(a, r) and Variant(a, r). Keep Definition(a) on
helpers that intentionally accept and return only completed definitions.
recursive supplies Plan(a, Recursive(a)). recursive_pair supplies two plans
under #(Recursive(a), Recursive(b)) and returns two completed definitions;
their output types may differ. Builder inference usually avoids those annotations.
Captured references
In 0.1.0, a builder reference had type Definition(a) and could escape as a
runnable reference. In 0.2.0 it remains a construction plan even after the builder
returns. Passing it to decode, validate, schema emission or the documentation
renderer does not compile. Store the completed result returned by recursive
or recursive_pair when later execution is needed.
Mapping callbacks may still return closures, including closures capturing completed definitions. Completed definitions own immutable recursion bodies and bindings; they require no registry installation or release operation. There is no restriction on ordinary closure outputs. A closure returning a construction plan does not make that plan independently executable.
Names, recursive descent guards, decoded-value behavior, callback suppression, defaults, canonical schema text and structured data errors retain their existing contracts. Defaults involving unresolved recursive references still fail construction; add those defaults after the recursive definition is completed.