Public entry point for .dxns schema compilation and validation.
compile/3— the one-time walk from a decoded.dxnsdocument to a populatedDextrin.Registry.validate/3— checks an already-decoded value against a named schema (decode-side, but not decode itself — see its own doc).validate_encode/3/validate_encode_tree/2— the encode-side mirror, one named schema vs. an automatic whole-tree walk.
Enforcement during actual decoding is fail-fast and automatic: every
registered struct name is checked unconditionally as part of
Dextrin.decode/2/decode_binary/2, and a violation surfaces as an
ordinary {:error, %Dextrin.Error{}} — the functions here are for
the cases that aren't "check while decoding" (see validate/3's doc).
Summary
Functions
Compiles a decoded .dxns document (see Dextrin.decode/2) into
base_registry — a struct schema for each %schema{} entry, and a
reusable named type for every other entry, e.g.
PositiveInt: {:refine :integer {min: 1}}. predicates resolves any
refine-fn: names used in the document.
Compiles and registers one Dextrin.Schema.Provider implementation
into registry — the explicit half of letting a struct's own
library define its DXN schema without depending on dextrin; see
Dextrin.Schema.Provider's own moduledoc for the full pattern this
is meant to support, including why it's a small companion module
rather than the struct's own, and the optional-dependency mechanics
that keep the struct's library dependency-free.
Validates an already-decoded value against a named, compiled schema
— for the cases that aren't "check while decoding": a value built
directly in Elixir before encoding, an opaque Dextrin.Struct
decoded before a schema became available, or checking against a
different schema than the one originally used.
Encode-time schema validation: checks value —
ordinary Elixir data you're about to encode (a plain map, atom or
string keys, or a real struct) — against a named, compiled schema
before encoding produces any output. The mirror image of
validate/3, for data on its way out rather than in: a decode-time
check protects against untrusted incoming data; this protects
against your own program accidentally handing the encoder the
wrong shape. Dextrin.encode/2/encode_binary/2's schema: opt
calls this automatically.
Automatic, name-driven encode-time validation: walks value and
checks every Dextrin.Struct or registered
application struct anywhere in it against its own schema, wherever
it turns out to be — not just what validate_encode/3's one named
schema's own field types happen to reach. Symmetric to how decode
checks every named struct unconditionally. Dextrin.encode/2/
encode_binary/2 call this automatically by default (opt out with
validate: false).
Functions
@spec compile(term(), Dextrin.Registry.t(), %{ optional(String.t()) => Dextrin.Schema.Compiled.refine_fn() }) :: {:ok, Dextrin.Registry.t()} | {:error, term()}
Compiles a decoded .dxns document (see Dextrin.decode/2) into
base_registry — a struct schema for each %schema{} entry, and a
reusable named type for every other entry, e.g.
PositiveInt: {:refine :integer {min: 1}}. predicates resolves any
refine-fn: names used in the document.
@spec register_provider(Dextrin.Registry.t(), module()) :: {:ok, Dextrin.Registry.t()} | {:error, term()}
Compiles and registers one Dextrin.Schema.Provider implementation
into registry — the explicit half of letting a struct's own
library define its DXN schema without depending on dextrin; see
Dextrin.Schema.Provider's own moduledoc for the full pattern this
is meant to support, including why it's a small companion module
rather than the struct's own, and the optional-dependency mechanics
that keep the struct's library dependency-free.
Concretely, in order:
- Decodes and compiles
module.dxn_schema/0's source (Dextrin.decode/2+compile/3, same as any other.dxnsdocument) intoregistry. Every named type and every%schema{}entry that document defines gets folded in, not only the onemodule.dxn_schema_name/0points at — a provider module for one struct can usefully define shared named types (or even other related structs) that end up available in the resulting registry regardless. - Looks up
module.dxn_schema_name/0in the now-compiled registry. If it isn't there — the provider declared a name its own schema document doesn't actually define, almost certainly a typo in one place or the other — this returns a specific{:error, _}naming both the module and the mismatched name, rather than silently registering nothing or raising a genericKeyErrordeeper inDextrin.Registry. - Associates
module.dxn_struct/0with that schema name viaDextrin.Registry.put_struct_module/3— the same call you'd make by hand for a struct schema you wrote yourself; this is what letsDextrin.encode/2/encode_binary/2serializedxn_struct/0's struct directly and lets{:reference, name}checks recognize it. - If
moduleexportsdxn_materialize/1(the one optional callback), registers it viaDextrin.Registry.put_struct_materializer/3. If not, decoding this schema falls back to the default plain field map, same as any other materializer-less schema.
Composes exactly like compile/3's own base_registry — calling
register_provider/2 more than once, for different provider modules,
threads the same growing registry through each call, same as chaining
compile/3 calls or seeding from Dextrin.Schema.Std.registry/1.
@spec validate(term(), Dextrin.Registry.t(), String.t()) :: :ok | {:error, term()}
Validates an already-decoded value against a named, compiled schema
— for the cases that aren't "check while decoding": a value built
directly in Elixir before encoding, an opaque Dextrin.Struct
decoded before a schema became available, or checking against a
different schema than the one originally used.
@spec validate_encode(term(), Dextrin.Registry.t(), String.t()) :: :ok | {:error, term()}
Encode-time schema validation: checks value —
ordinary Elixir data you're about to encode (a plain map, atom or
string keys, or a real struct) — against a named, compiled schema
before encoding produces any output. The mirror image of
validate/3, for data on its way out rather than in: a decode-time
check protects against untrusted incoming data; this protects
against your own program accidentally handing the encoder the
wrong shape. Dextrin.encode/2/encode_binary/2's schema: opt
calls this automatically.
@spec validate_encode_tree(term(), Dextrin.Registry.t()) :: :ok | {:error, term()}
Automatic, name-driven encode-time validation: walks value and
checks every Dextrin.Struct or registered
application struct anywhere in it against its own schema, wherever
it turns out to be — not just what validate_encode/3's one named
schema's own field types happen to reach. Symmetric to how decode
checks every named struct unconditionally. Dextrin.encode/2/
encode_binary/2 call this automatically by default (opt out with
validate: false).