# Schema `schema/` validates plain Deft data against a [`deftype`](../01-Language/types-and-protocols) schema and converts it into typed values. It is the bridge between parsed data — a map from `[deft/parse]`, JSON, YAML, or XML — and the typed maps your application works with. The functions are native stdlib, available in every host without an import. They read the effective schema (the type's field specs merged across its `<<` parents, child keys winning) and understand the full schema vocabulary: `:type` (with hierarchy), `:required`, `:default`, `:many`, `:of`, `:coerce`, `:min`/`:max`, `:pattern`, `:enum`/`:one-of`/`:variants`, and `:validate` hooks. ## Functions | Function | Purpose | |---|---| | `schema/validate :Type $value ?opts` | Check a value against the type; returns `%Ok{:value}` or a path-annotated `%Err` | | `schema/from-value :Type $value ?opts` | Validate and convert: applies defaults and `:coerce`, wraps `:many` singles, converts nested `:Type` fields, and returns `%Ok{:value %Type{...}}` | Options (a map as the optional third argument): | Option | Applies to | Purpose | |---|---|---| | `:coerce true` | `validate` | Apply `:coerce` field conversions (`from-value` always coerces) | | `:defaults true` | `validate` | Fill missing fields from `:default` (`from-value` always applies defaults) | | `:strict true` | both | Unknown map keys are errors (otherwise they pass through, and `from-value` preserves them) | | `:all true` | both | Collect every error instead of stopping at the first | ## Errors Without `:all`, a failure is `%Err{ :path @{...} :message "..." }`. The path is a vector of field keywords and integer indices, outermost first:
set bad %{:id "A1" :items @{%{:sku 0}}}

set e [schema/validate :Order $bad]
# => %Err{ :path @{ :items 0 :sku }
#          :message "expected string, got number" }

set e [schema/validate :Order $bad %{:all true}]
# => %Err{ :errors @{ %{:path @{...} :message "..."} ... } }
Validation is recursive: nested `:Type` fields (and `:of` element types) are checked too, and the path extends through them. Recursive types — a `Tree` whose nodes contain `Tree` children — work, with a depth cap so malformed data cannot hang the validator. Union (`defunion`) fields dispatch on the value's tag and continue against the variant's own schema. ## Examples
deftype Item %{
    :sku %{:type :string :required true :pattern "^[a-z0-9-]+$"}
    :qty %{:type :number :coerce true :min 1}
}

deftype Order %{
    :id     %{:type :string  :required true}
    :items  %{:type :list    :of :Item :many true}
    :status %{:type :keyword :coerce true :enum @{:open :shut}}
}

# Validate only — no conversion, so the string qty fails
set raw %{:id "A1" :items @{%{:sku "tea" :qty "2"}} :status :open}
[schema/validate :Order $raw]
# => %Err{ :path @{ :items 0 :qty } :message "expected number, got string" }

# Validate with coercion enabled
[schema/validate :Order $raw %{:coerce true}]
# => %Ok{ :value ... }

# Convert to a real %Order{} — the canonical typed-data entry point
set r [schema/from-value :Order $raw]
[ok? $r]                                  # => true
set order [get $r :value]
[type $order]                             # => "Order"
[type [nth $order~items 0]]               # => "Item"
[nth $order~items 0]~qty                  # => 2
Typical uses:
# Typed config from a deft-literal file
set text  [unwrap [fs/read "config.dft"]]
set typed [schema/from-value :Server [deft/parse $text]]
if [err? $typed] { echo "bad config:" [get $typed :path] [get $typed :message] }

# The same pipeline works for any parsed format
[schema/from-value :Config [json/parse $body]]
[schema/from-value :Config [yaml/load $path]]
## Built-in schemas Two system types are registered by the VM, so no import is needed: - `:Prelude` — the `.deft/prelude.dft` shape (`%Prelude{...}`): `:mounts`, `:paths` (legacy), `:pkgs`, `:registry`, `:themes`, `:font`, `:keys`, `:sandbox`, `:window`, `:workspace`, `:lockdown`, `:launcher`, `:ai`. - `:WorkspaceSpec` — one node of a `.deft/workspaces/<name>.dft` panel tree: group fields (`:group`/`:split`, `:ratios`, `:group-title`, `:collapsed`, `:explicit`, …), leaf fields (`:app`/`:package`/`:source`, `:id`, `:focus`, `:terminal`, `:surface`, …), per-window options (`:x`/`:y`/`:w`/`:h`, `:borderless`, `:headless`, …), and the top-level metadata (`:theme`, `:font`, `:vars`). `:children` is recursive. Both config kinds are validated at load — the prelude in `loadPrelude`, workspace configs in `Workspace.applySpec` — via `schema/validate` with `:strict` and `:all`, so unknown fields and wrong types print a `warning: … schema violation …` line naming the offending path while the config still applies. A `:pkg` typo for `:pkgs`, for example, warns instead of being silently ignored. ## See Also - Types and Protocols — `deftype` and the full field-spec vocabulary - Data Formats — json/yaml readers feeding `from-value` - Introspection — `info/props` and `:effective-schema` - Filesystem — reading the documents you validate - Persistence — deriving storage layout and typed CRUD accessors from the same schemas