validate: Schema Validation and Typed JSON Decoding
| (require validate) | package: validate |
Schema validation for untrusted data — the zod workflow in Racket: declare a schema once, parse with it everywhere, get verified values or path-addressed errors. The killer application is the JSON boundary: jsexpr-schema gates decoded JSON before a schema looks at it, and to-jsexpr encodes everyday Racket data (symbols, keywords, vectors, mutable hashes) without dying in a json-module contract.
1 Parsing
procedure
v : any/c
Recognizes schemas built by this library.
procedure
(parse s v) → any/c
s : schema? v : any/c
Validates v, returning the verified (possibly transformed) value. On failure raises exn:fail:validate? carrying every error.
procedure
(parse-safe s v) → (or/c ok? err?)
s : schema? v : any/c
The non-raising form: (ok value) or (err errors).
struct
value : any/c
struct
errors : (listof validate-error?)
struct
(struct validate-error (path expected got message))
path : (listof (or/c symbol? exact-integer? string?)) expected : string? got : any/c message : string?
procedure
(render-error e) → string?
e : validate-error?
procedure
(exn:fail:validate? v) → boolean?
v : any/c
procedure
(exn:fail:validate-errors e) → (listof validate-error?)
e : exn:fail:validate?
2 Schema combinators
Every constructor is a function returning a schema?; pass one around, compose freely, recurse via lazy.
procedure
(string-schema [ #:min-length min-length #:max-length max-length #:pattern pattern]) → schema? min-length : (or/c exact-nonnegative-integer? #f) = 0 max-length : (or/c exact-nonnegative-integer? #f) = #f pattern : (or/c regexp? #f) = #f
procedure
(int-schema [#:min min #:max max]) → schema?
min : (or/c exact-integer? #f) = #f max : (or/c exact-integer? #f) = #f
Exact integers only — 3.0 is rejected.
procedure
(real-schema [#:min min #:max max]) → schema?
min : (or/c real? #f) = #f max : (or/c real? #f) = #f
procedure
(symbol-schema) → schema?
procedure
(bytes-schema) → schema?
procedure
procedure
(null-schema) → schema?
procedure
(any-schema) → schema?
procedure
(list-of s [ #:min-length min-length #:max-length max-length]) → schema? s : schema? min-length : exact-nonnegative-integer? = 0 max-length : (or/c exact-nonnegative-integer? #f) = #f
Accepts lists, reports element errors as [index]-addressed paths.
procedure
key : schema? value : schema?
Open hashes; keys and values each validated.
struct
key : symbol? schema : schema? optional? : boolean? default : any/c
procedure
(field key s [ #:optional optional? #:default default]) → field? key : (or/c symbol? string?) s : schema? optional? : boolean? = #f default : any/c = #f
procedure
fields : (listof field?) closed? : boolean? = #f
The workhorse. Lookups accept string or symbol keys, from equal- or eq-based hashes alike — JSON decoders and hand-built hasheqs both decode. Output is an immutable hash with symbol keys, containing every declared field (optional ones filled with their default), so downstream code can hash-ref without guards. Unknown keys are stripped; with #:closed? #t they become errors. Missing required fields name the field in the error.
procedure
schemas : (listof schema?)
First alternative without errors wins.
procedure
s : schema? default : any/c = #f
Maps #f and JSON 'null to default; everything else goes through s.
procedure
s : schema? pred : (-> any/c boolean?) message : string?
procedure
(map-schema s f) → schema?
s : schema? f : (-> any/c any/c)
procedure
thunk : (-> schema?)
Recursive schemas:
(define tree (object #:fields (list (field 'value (int-schema)) (field 'children (lazy (λ () (list-of tree))) #:optional #t #:default '()))))
3 The JSON boundary
procedure
(jsexpr-schema s) → schema?
s : schema?
Gates on JSON representability first, so input a schema would happily accept (a symbol, a dotted pair) still gets rejected with a JSON-shaped error.
procedure
(to-jsexpr v) → json-value?
v : any/c
Normalizes symbols, keywords, characters, vectors, proper pairs, and mutable hashes into JSON-representable shapes; 'null stays JSON null; anything else (structs, dotted pairs) raises exn:fail:validate? with a message naming the culprit. Hash keys normalize to symbols (what this Racket’s write-json requires).
procedure
(jsexpr-encode v) → string?
v : any/c
(jsexpr->string (to-jsexpr v)).
procedure
(json-value? v) → boolean?
v : any/c
JSON representability, accepting string and symbol hash keys — different Racket builds’ read-json disagree.