Dextrin.Schema.Validator (Dextrin v0.3.0)

Copy Markdown View Source

Checks a value's fields against a Dextrin.Schema.Compiled schema (required fields and declared types) — shared by decode (resolve/3, whose result Dextrin.Schema.Reader builds a value from) and encode-time validation (validate_for_encode/3/validate_tree_for_encode/2, which never transforms value). Both directions reuse the exact same resolve_fields/3/TypeExpr.matches?/3 field-checking — encode's own values are wrapped in the same internal Dextrin.Schema. Validated marker decode already uses, via wrap_and_check/2, so there's one type-checking implementation, not two that could drift.

Enforcement is decode-time and fail-fast, with no lenient escape hatch to get the materialized value anyway — a violation is always an error, on the same channel an ordinary syntax error already uses.

Every violation is a Dextrin.Error carrying its DXN.md §5 category -- schema/field-count, schema/missing-field, schema/type-mismatch, schema/shadowed-field -- which the text and binary decoders and the encoder pass on unchanged, so a violation is the same error whichever channel it travelled on.

Summary

Functions

fields checked against compiled: every declared field, in the schema's own order, as {name, value} pairs keyed by string.

Checks value — ordinary Elixir data you're about to encode — against compiled. Never materializes; value itself is never transformed.

Walks value looking for every Dextrin.Struct or registered application struct anywhere in the tree and checks each one against its own schema, if the registry has one — regardless of what any enclosing field's declared type is, and recursing fully into whatever it finds (not just confirming type identity). Dextrin.encode/2/encode_binary/2 call this automatically (opt out with validate: false).

Types

fields()

@type fields() :: {:keyed, [{String.t(), term()}]} | {:positional, [term()]}

Functions

resolve(compiled, fields, registry)

@spec resolve(Dextrin.Schema.Compiled.t(), fields(), Dextrin.Registry.t()) ::
  {:ok, [{String.t(), term()}]} | {:error, Dextrin.Error.t()}

fields checked against compiled: every declared field, in the schema's own order, as {name, value} pairs keyed by string.

A required field that is missing is an error, and so is a present field whose value does not match its declared type. An optional field that is missing takes its default, or stays absent when it has none -- DXN tells an absent field from one written nil. Nested values have already been read, bottom-up; their internal Validated markers are stripped here, so nothing past this point sees one.

Which schema the fields are read with, and what is built from them, is Dextrin.Schema.Reader's business.

validate_for_encode(compiled, value, registry)

@spec validate_for_encode(Dextrin.Schema.Compiled.t(), term(), Dextrin.Registry.t()) ::
  :ok | {:error, Dextrin.Error.t()}

Checks value — ordinary Elixir data you're about to encode — against compiled. Never materializes; value itself is never transformed.

validate_tree_for_encode(value, registry)

@spec validate_tree_for_encode(term(), Dextrin.Registry.t()) ::
  :ok | {:error, Dextrin.Error.t()}

Walks value looking for every Dextrin.Struct or registered application struct anywhere in the tree and checks each one against its own schema, if the registry has one — regardless of what any enclosing field's declared type is, and recursing fully into whatever it finds (not just confirming type identity). Dextrin.encode/2/encode_binary/2 call this automatically (opt out with validate: false).