Internal, compiled representation of a .dxns type expression — the
vocabulary of DXNS.md §1 (any, never, primitive, reference,
list-of, list-items, set-of, tuple-of, tuple-of-all, map-of, array-of, tagged, doc, if,
ordered-map-of, sorted-set-of, enum, one-of, any-of,
all-of, not, nilable, refine, struct, and shape and case,
design/shapes-and-case.md) Dextrin.Schema.Compiler turns a
parsed .dxns value into, and what matches?/2 below checks a
decoded value against. This vocabulary is fixed, not extensible from
outside an actual library change — what schema authors extend
instead is composing these forms into a reusable named type (see
Dextrin.Schema.Compiler's own moduledoc), which needs no new form
at all.
Summary
Types
What a check knows besides the type and the value: the registry
(nil when there is none -- strict and trusted are then off), and the
fields of the nearest enclosing struct or shape by name, which a
{:case} reads its sibling from (nil outside any).
One of a {:list-items}'s items: a type for exactly one item, or a
repeat of a group of them, most nil when there is no upper bound
(DXNS.md §1a).
Functions
value checked against type in context: {:ok, value} with every
shape's missing defaults filled in (design/shapes-and-case.md, Jan,
2026-10-20), or :error. Nothing but a shape changes a value.
The context for a check with registry, the fields siblings around
it.
Whether value matches type — shared by both decode
(Dextrin.Schema.Reader.read/5) and encode-time
validation (Dextrin.Schema.Validator.validate_for_encode/3): the
same recursive check, the same function, for both directions. It is
conform/3 answered as a boolean, so there is one check, not two.
Types
@type context() :: %{ registry: Dextrin.Registry.t() | nil, siblings: %{required(String.t()) => term()} | nil }
What a check knows besides the type and the value: the registry
(nil when there is none -- strict and trusted are then off), and the
fields of the nearest enclosing struct or shape by name, which a
{:case} reads its sibling from (nil outside any).
@type particle() :: {:one, t()} | {:repeat, [particle()], non_neg_integer(), non_neg_integer() | nil}
One of a {:list-items}'s items: a type for exactly one item, or a
repeat of a group of them, most nil when there is no upper bound
(DXNS.md §1a).
@type t() :: :any | :never | {:primitive, String.t()} | {:reference, String.t()} | {:list_of, t()} | {:set_of, t()} | {:list_items, [particle()]} | {:tuple_of, [t()]} | {:tuple_of_all, t()} | {:tagged, String.t(), t()} | {:doc, t(), %{optional(String.t()) => term()}} | {:if, t(), t(), t()} | {:map_of, t(), t()} | {:array_of, t()} | {:ordered_map_of, t(), t()} | {:sorted_set_of, t()} | {:enum, [term()]} | {:one_of, [t()]} | {:any_of, [t()]} | {:all_of, [t()]} | {:not, t()} | {:shape, String.t(), [Dextrin.Schema.Field.t()]} | {:case, String.t(), [{term(), t()}], t() | :none} | {:named, String.t()} | {:generic, [String.t()], t()} | {:apply, String.t(), [t()]} | {:param, String.t()} | {:nilable, t()} | {:refine, t(), %{optional(String.t()) => term()}}
Functions
value checked against type in context: {:ok, value} with every
shape's missing defaults filled in (design/shapes-and-case.md, Jan,
2026-10-20), or :error. Nothing but a shape changes a value.
@spec context(Dextrin.Registry.t() | nil, %{required(String.t()) => term()} | nil) :: context()
The context for a check with registry, the fields siblings around
it.
@spec matches?(t(), term(), Dextrin.Registry.t() | nil) :: boolean()
Whether value matches type — shared by both decode
(Dextrin.Schema.Reader.read/5) and encode-time
validation (Dextrin.Schema.Validator.validate_for_encode/3): the
same recursive check, the same function, for both directions. It is
conform/3 answered as a boolean, so there is one check, not two.
{:reference, name} checks the originating schema's name, not
the value's own shape — which is how it correctly rejects a
materialized value that satisfies its own schema but is the wrong
one for this field. Three sources carry that name: an opaque,
unregistered Dextrin.Struct carries its own wire name directly; a
value that went through a registered schema (decode or encode
-time validation alike) carries it via Validated — Dextrin.Schema.Reader.read/5
wraps every result in one regardless of materializer shape, and
encode-time validation wraps every recognized struct the same way
before checking it (Dextrin.Schema.Validator's internal
wrap_and_check helper), specifically so this one function can check both without needing
two implementations. The third, encode-only source: registry
(nil for decode, which never needs it) lets a real, unwrapped
application struct's __struct__ be checked against whatever module
was registered for name (Dextrin.Registry.put_struct_module/3) —
needed only for a struct that turned out to have no schema of its
own to be wrapped by. Nothing else matches a reference (DXN.md
§4.7): a plain map, however much it looks like the struct, and an
application struct registered for no schema or for another are type
errors -- a value matches only its declared type.