# 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