zz (zz v0.2.0)
View SourceZod-like parsing and validation for Erlang.
Each combinator returns a parser/1 (or optional_parser/1 from
optional/1). Compose them, then run with parse/2:
Z = zz:map(#{name => zz:binary(), age => zz:integer(#{min => 0})}),
{ok, _} = zz:parse(Z, #{name => <<"x">>, age => 1}).On failure, the nested errors/0 shape can be flattened to a
path-addressed list of issues with issues/1.
Summary
Functions
Accept any input. Output equals input.
Validate that input is an atom.
Validate that input is a binary, with optional min/max byte size and
regex constraints.
Equivalent to bitstring/1.
Validate that input is a bitstring, with optional min/max
bit_size/1 constraints.
Validate that input is a boolean.
Validate that input is a list of Unicode codepoints ([char()]),
i.e. the old-style Erlang string representation. Element errors are
wrapped as {list, Index, [not_char]}.
Validate that input is a float, with optional min/max.
Format issues/0 as a human-readable binary, one issue per line in
the form path: code [extras]. Empty paths render as (root).
Useful for logs and human-facing error output.
Validate that input is a function (any arity).
Validate that input is a function with the given arity.
Validate that input is an integer, with optional min/max.
Validate that input is iodata() (a binary or iolist()). Validation
walks the entire structure via iolist_size/1, so cost is linear in
the total bytes addressed.
Validate that input is an iolist() (a possibly-improper list of
bytes, binaries, and nested iolists). Use iodata/0 if a raw binary
should also be accepted.
Defer construction of a parser until parse time. Use to build self-referential (recursive) schemas without infinite recursion at definition time.
Validate that input is a list (any contents).
Validate a homogeneous list, parsing each element with Z. Optional
min/max constrain length.
Validate that input equals Value exactly (=:=).
Validate that input is a map (passthrough on contents).
Equivalent to map/2.
Validate a map against Schema. unknown_keys controls handling of keys
not in Schema: strip (drop, default), passthrough (keep), strict
(error).
Validate a homogeneous map where every key is parsed by KZ and every
value by VZ. Use this for caches, dictionaries, and other arbitrary-
keyed maps where the key shape is uniform.
Validate that input is a negative integer (=< -1).
Validate that input is a non-negative integer (>= 0).
Validate that input is undefined or matches Z. Sugar for
union([literal(undefined), Z]).
Validate that input is a number (integer or float).
Run parser Z against Input.
Validate that input is a process identifier.
Validate that input is a positive integer (>= 1).
Validate that input is a reference (e.g. from make_ref/0).
Validate that input is a tuple (passthrough on contents).
Validate a fixed-arity tuple where each element is parsed by the
corresponding parser at the same position in Zs. Element errors
are wrapped as {tuple, Index, InnerErrors} with 1-based Index.
Validate against the first parser that succeeds. If none match, returns
{error, [{no_match, [Errors1, Errors2, ...]}]} where each entry is the
errors list from the corresponding parser, in input order. Empty union
yields {error, [{no_match, []}]}.
Types
-type binary_options() :: #{min => non_neg_integer(), max => non_neg_integer(), regex => iodata()}.
-type bitstring_options() :: #{min => non_neg_integer(), max => non_neg_integer()}.
-type errors() :: [error()].
-type issues() :: [issue()].
-type list_options() :: #{min => non_neg_integer(), max => non_neg_integer()}.
-type map_options() :: #{unknown_keys => strip | passthrough | strict}.
-type optional_parser() :: optional_parser(term()).
-type optional_parser(T) :: {optional, parser(T)}.
-type path() :: [term()].
-type result(T) :: {ok, T} | {error, errors()}.
-type schema() :: #{term() => parser() | optional_parser()}.
Functions
Accept any input. Output equals input.
Validate that input is an atom.
Equivalent to binary/1.
-spec binary(binary_options()) -> parser(binary()).
Validate that input is a binary, with optional min/max byte size and
regex constraints.
Equivalent to bitstring/1.
-spec bitstring(bitstring_options()) -> parser(bitstring()).
Validate that input is a bitstring, with optional min/max
bit_size/1 constraints.
Validate that input is a boolean.
Validate that input is a single Unicode codepoint (char/0), an
integer in 0..16#10FFFF.
Validate that input is a list of Unicode codepoints ([char()]),
i.e. the old-style Erlang string representation. Element errors are
wrapped as {list, Index, [not_char]}.
-spec enum([T]) -> parser(T).
Validate that input equals (=:=) one of Values. Fails with
not_in_enum if no value matches. Equivalent to a union/1 of
literal/1s but with a flat error code.
Equivalent to float/1.
-spec float(float_options()) -> parser(float()).
Validate that input is a float, with optional min/max.
Format issues/0 as a human-readable binary, one issue per line in
the form path: code [extras]. Empty paths render as (root).
Useful for logs and human-facing error output.
-spec function() -> parser(fun()).
Validate that input is a function (any arity).
Validate that input is a function with the given arity.
Equivalent to integer/1.
-spec integer(integer_options()) -> parser(integer()).
Validate that input is an integer, with optional min/max.
Validate that input is iodata() (a binary or iolist()). Validation
walks the entire structure via iolist_size/1, so cost is linear in
the total bytes addressed.
Validate that input is an iolist() (a possibly-improper list of
bytes, binaries, and nested iolists). Use iodata/0 if a raw binary
should also be accepted.
Flatten nested errors/0 into a flat list of issue/0 records,
each with a path to the failing position and a code.
Compound errors carry extra fields:
unknown_keysissues includekeys => [term()].no_matchissues includebranches => [issues()], one per union branch in input order.invalid_keyissues (frommap_of/2key validation) includekey => term()(the offending key) anderrors => issues().
Defer construction of a parser until parse time. Use to build self-referential (recursive) schemas without infinite recursion at definition time.
Thunk is called on every descent into the lazy parser, so it should
be cheap (typically just fun() -> some_parser_fn() end). The thunk
must return a fresh parser — returning the lazy parser itself would
loop forever at parse time.
tree() ->
zz:union([
zz:literal(leaf),
zz:tuple({
zz:literal(node),
zz:lazy(fun() -> tree() end),
zz:lazy(fun() -> tree() end)
})
]).
Validate that input is a list (any contents).
Equivalent to list/2.
-spec list(parser(T), list_options()) -> parser([T]).
Validate a homogeneous list, parsing each element with Z. Optional
min/max constrain length.
-spec literal(T) -> parser(T).
Validate that input equals Value exactly (=:=).
Validate that input is a map (passthrough on contents).
Equivalent to map/2.
-spec map(schema(), map_options()) -> parser(#{term() => term()}).
Validate a map against Schema. unknown_keys controls handling of keys
not in Schema: strip (drop, default), passthrough (keep), strict
(error).
Validate a homogeneous map where every key is parsed by KZ and every
value by VZ. Use this for caches, dictionaries, and other arbitrary-
keyed maps where the key shape is uniform.
Key errors are wrapped as {map_key, OriginalKey, InnerErrors}; value
errors are wrapped as {map_value, OriginalKey, InnerErrors}.
zz:map_of(zz:binary(), zz:integer()).
-spec neg_integer() -> parser(neg_integer()).
Validate that input is a negative integer (=< -1).
-spec non_neg_integer() -> parser(non_neg_integer()).
Validate that input is a non-negative integer (>= 0).
Validate that input is undefined or matches Z. Sugar for
union([literal(undefined), Z]).
Validate that input is a number (integer or float).
-spec optional(parser(T)) -> optional_parser(T).
Mark a parser as optional in a schema/0. Inside a map/2 schema, an
optional key may be absent without producing an error.
Run parser Z against Input.
Validate that input is a process identifier.
-spec pos_integer() -> parser(pos_integer()).
Validate that input is a positive integer (>= 1).
Validate that input is a reference (e.g. from make_ref/0).
Validate that input is a tuple (passthrough on contents).
Validate a fixed-arity tuple where each element is parsed by the
corresponding parser at the same position in Zs. Element errors
are wrapped as {tuple, Index, InnerErrors} with 1-based Index.
Validate against the first parser that succeeds. If none match, returns
{error, [{no_match, [Errors1, Errors2, ...]}]} where each entry is the
errors list from the corresponding parser, in input order. Empty union
yields {error, [{no_match, []}]}.