sema-validate
0.1.0Schema validation for maps and data structures
sema-validate
Schema validation for maps and data structures.
Install
sema pkg add sema-validate
Quick start
(import "sema-validate")
(define user-schema
(validate/schema
{:name (validate/required string?)
:age (validate/required (validate/all-of number? (validate/in-range 0 150)))
:email (validate/required (validate/matches #"^.+@.+\..+$"))
:role (validate/required (validate/one-of "admin" "user" "guest"))
:nickname (validate/optional string?)}))
(validate/valid? user-schema
{:name "Alice" :age 30 :email "alice@example.com" :role "admin"})
; => #t
(validate/errors user-schema {:name 42 :age -5 :role "superadmin"})
; => ({:message "expected all-of: number?, in-range 0 150" :path (:age) :value -5}
; {:message "required field missing" :path (:email) :value nil}
; {:message "expected string?" :path (:name) :value 42}
; {:message "expected one-of: admin, user, guest" :path (:role) :value "superadmin"})
Spec shapes
A spec map pairs each key with one of:
- a bare predicate —
{:name string?}(treated as required), - a validator built by the
validate/*constructors, - a nested spec map —
{:address {:street string? :zip string?}}— validated recursively; error:paths accumulate, e.g.(:address :zip).
validate/required and validate/optional accept all three, so an entire
nested map can be made optional:
(define sch (validate/schema {:address (validate/optional {:street string?})}))
(validate/valid? sch {}) ; => #t (absent is fine)
(validate/valid? sch {:address {:street "Main"}}) ; => #t
(validate/valid? sch {:address {:street 42}}) ; => #f
API
| Function | Description |
|---|---|
(validate/schema spec) |
Create a schema from a spec map |
(validate/valid? schema data) |
#t if data matches, #f otherwise |
(validate/errors schema data) |
List of error maps; empty list if valid |
(validate/assert! schema data) |
Return data, or raise with readable details |
(validate/required pred) |
Field is required (predicate, validator, or nested spec) |
(validate/optional pred) |
Field may be absent; checked when present |
(validate/one-of . values) |
Value must equal one of the values |
(validate/all-of . preds) |
Value must satisfy every predicate/validator |
(validate/list-of pred) |
List whose every element matches |
(validate/matches pattern) |
String matching a regex |
(validate/in-range lo hi) |
Number in the inclusive range |
(validate/min-length n) |
String/list with at least n chars/elements |
(validate/max-length n) |
String/list with at most n chars/elements |
validate/schema
(validate/schema {:name string? :age number?})
Wraps a spec map. All other functions take the schema as their first argument.
validate/valid?
(validate/valid? user-schema {:name "Alice" :age 30
:email "a@b.co" :role "user"})
; => #t
validate/errors
Returns a list of {:path :message :value} maps — :path is the list of keys
from the root to the failing field:
(validate/errors (validate/schema {:address {:zip (validate/matches #"^\d{4}$")}})
{:address {:zip "x"}})
; => ({:message "expected string matching ^\d{4}$" :path (:address :zip) :value "x"})
validate/assert!
Returns the data unchanged when valid; otherwise raises an error joining every failure, with nested paths dot-separated:
(validate/assert! user-schema {:address {}})
; error: validate/assert!: validation failed — age: required field missing;
; email: required field missing; name: required field missing;
; role: required field missing
validate/required / validate/optional
(validate/required string?) ; required predicate
(validate/optional (validate/in-range 0 5)) ; optional validator
(validate/required {:street string?}) ; required nested map
Bare predicates and validator constructors default to required;
validate/optional is how a field becomes skippable.
validate/one-of
(validate/one-of "admin" "user" "guest")
Compares with equal?, so any value type works.
validate/all-of
(validate/all-of number? (validate/in-range 0 150))
Accepts a mix of bare predicates and validators.
validate/list-of
(validate/list-of string?)
(validate/list-of (validate/in-range 0 100))
Fails for non-lists; the empty list is valid.
validate/matches
(validate/matches #"^\d{4}$")
Fails for non-strings.
validate/in-range
(validate/in-range 0 150) ; inclusive on both ends; fails for non-numbers
validate/min-length / validate/max-length
(validate/min-length 8) ; strings: character count; lists: element count
(validate/max-length 64)
Fail for values that are neither strings nor lists.
Testing
sema pkg add sema-test # once
sema tests.sema
License
MIT
| Version | Size | Published |
|---|---|---|
| 0.1.0 | 5 KB | 2026-07-07 21:28:20 |