stch.schema documentation
A library for data shape definition and validation.
A Schema is just Clojure data, which can be used to
document and validate Clojure functions and data.
For example,
(def FooBar {:foo Keyword :bar [Number]}) ;; a schema
(check FooBar {:foo :k :bar [1.0 2.0 3.0]})
==> nil
representing successful validation, but the following
all return helpful errors describing how the provided
data fails to measure up to schema FooBar's standards.
(check FooBar {:bar [1.0 2.0 3.0]})
==> {:foo missing-required-key}
(check FooBar {:foo 1 :bar [1.0 2.0 3.0]})
==> {:foo (not (keyword? 1))}
(check FooBar {:foo :k :bar [1.0 2.0 3.0] :baz 1})
==> {:baz disallowed-key}
Schema lets you describe your leaf values using the
Any, Keyword, Number, String, and Int definitions below,
or (in Clojure) you can use arbitrary Java classes or
primitive casts to describe simple values.
From there, you can build up schemas for complex types
using Clojure syntax (map literals for maps, set literals
for sets, vector literals for sequences, with details
described below), plus helpers below that provide optional values,
enumerations, arbitrary predicates, and more.
Schema also provides macros for defining records with
schematized elements (defrecord'), and named or anonymous
functions (fn' and defn') with schematized inputs and
return values. In addition to producing better-documented
records and functions, these macros allow you to retrieve
the schema associated with the defined record or function.
Moreover, functions include optional *validation*, which will throw
an error if the inputs or outputs do not match the provided schemas:
(defrecord' FooBar
[foo :- Int
bar :- String])
(defn' quux :- Int
[foobar :- Foobar
mogrifier :- Number]
(* mogrifier (+ (:foo foobar) (Long/parseLong (:bar foobar)))))
(quux (FooBar. 10 "5") 2)
==> 30
(fn-schema quux)
==> (Fn Int [(Record user.FooBar {:foo Int, :bar java.lang.String}) java.lang.Number])
(with-fn-validation (quux (FooBar. 10.2 "5") 2))
==> Input to quux does not match schema: [(named {:foo (not (integer? 10.2))} foobar) nil]
As you can see, the preferred syntax for providing
type hints to schema's defrecord', fn', and defn' macros
is to follow each element, argument, or function name with a
:- schema. Symbols without schemas default to a schema of Any.
In Clojure, class (e.g., java.lang.String) and primitive schemas
(long, double) are also propagated to tag metadata to ensure
you get the type hinting and primitive behavior you ask for.
If you don't like this style, standard Clojure-style
typehints are also supported:
(fn-schema (fn' [^String x]))
==> (Fn Any [java.lang.String])
You can directly type hint a symbol as a class, primitive,
protocol, or simple schema. For complex schemas, due to Clojure's
rules about ^, you must enclose the schema in a {:s schema}
map like so:
(fn-schema (fn' [^{:s [String]} x]))
(Fn Any [java.lang.String])
(We highly prefer the :- syntax to this abomination, however.)
See the docstrings of defrecord', fn', and defn' for more
details about how to use these macros.
+missing+
A sentinel value representing missing portions
of the input data.
->AnythingSchema
(->AnythingSchema _)
Positional factory function for class stch.schema.AnythingSchema.
->ConditionalSchema
(->ConditionalSchema preds-and-schemas)
Positional factory function for class stch.schema.ConditionalSchema.
->EnumSchema
(->EnumSchema vs)
Positional factory function for class stch.schema.EnumSchema.
->EqSchema
(->EqSchema v)
Positional factory function for class stch.schema.EqSchema.
->FnSchema
(->FnSchema output-schema input-schemas)
Positional factory function for class stch.schema.FnSchema.
->IntersectionSchema
(->IntersectionSchema schemas)
Positional factory function for class stch.schema.IntersectionSchema.
->MapEntrySchema
(->MapEntrySchema kspec val-schema)
Positional factory function for class stch.schema.MapEntrySchema.
->NamedSchema
(->NamedSchema schema name)
Positional factory function for class stch.schema.NamedSchema.
->OptionSchema
(->OptionSchema schema)
Positional factory function for class stch.schema.OptionSchema.
->OptionalKey
(->OptionalKey k)
Positional factory function for class stch.schema.OptionalKey.
->PredicateSchema
(->PredicateSchema p? pred-name)
Positional factory function for class stch.schema.PredicateSchema.
->ProtocolSchema
(->ProtocolSchema p)
Positional factory function for class stch.schema.ProtocolSchema.
->RecordSchema
(->RecordSchema klass schema)
Positional factory function for class stch.schema.RecordSchema.
->RecursiveSchema
(->RecursiveSchema schema-var)
Positional factory function for class stch.schema.RecursiveSchema.
->RequiredKey
(->RequiredKey k)
Positional factory function for class stch.schema.RequiredKey.
->Single
(->Single schema optional? name)
Positional factory function for class stch.schema.Single.
->UnionSchema
(->UnionSchema schemas)
Positional factory function for class stch.schema.UnionSchema.
Any
Any value, including nil.
Enumerate
(Enumerate & vs)
A value that must be = to some element of vs.
Eq
(Eq v)
A value that must be (= v).
Fn
macro
(Fn)
(Fn output-schema & arity-schema-specs)
Produce a function schema from an output schema
and a list of arity input schema specs, each of which
is a vector of argument schemas, ending with an optional
'& more-schema' specification where more-schema must
be a sequence schema.
Currently function schemas are purely descriptive;
there is no validation except for functions defined directly
by fn' or defn'.
I
(I & schemas)
A value that must satisfy every schema in schemas.
Keyword
A Clojure keyword.
List
(List)
(List x)
A Clojure list of x's.
Named
A Java string, Clojure symbol, or Clojure keyword.
One
(One schema)
(One schema name)
A single required element of a sequence
(not repeated, the implicit default).
Option
(Option schema)
A value that must either be nil or satisfy schema.
Optional
(Optional schema)
(Optional schema name)
A single optional element of a sequence
(not repeated, the implicit default).
Pair
(Pair first-schema second-schema)
(Pair first-schema first-name second-schema second-name)
A schema for a pair of schemas and
optionally their names.
Predicate
macro
(Predicate p?)
(Predicate p? pred-name)
Protocol
(Protocol p)
A value that must satsify? protocol p.
Queue
(Queue)
(Queue x)
A Clojure queue of x's.
Record
(Record klass schema)
A Record instance of type klass, whose
elements match map schema 'schema'.
Regex
A regular expression.
U
(U & schemas)
A value that must satisfy at least one schema in schemas.
Vector
(Vector)
(Vector x)
A Clojure vector of x's.
assert!
macro
(assert! form & format-args)
Like assert, but throws a RuntimeException and
takes args to format. Only for use in client-code.
check
(check schema x)
Return nil if x matches schema; otherwise, returns a
value that looks like the 'bad' parts of x with
ValidationErrors at the leaves describing the failures.
checker
(checker schema)
Compile an efficient checker for schema, which returns
nil for valid values and error descriptions otherwise.
conditional
(conditional & preds-and-schemas)
Define a conditional schema. Takes args like cond,
(conditional pred1 schema1 pred2 schema2 ...),
and checks the first schema where pred is true on the value.
Unlike cond, throws if the value does not match any condition.
:else may be used as a final condition in the place of (constantly true).
More efficient than U, since only one schema must be checked.
defn'
macro
(defn' & defn-args)
Like defn, except that schema-style typehints can
be given on the argument symbols and on the function
name (for the return value).
You can call fn-schema on the defined function to
get its schema back, or use with-fn-validation to
enable runtime checking of function inputs and outputs.
(defn' foo :- Num
[x :- Int
y :- Num]
(* x y))
(fn-schema foo)
==> (Fn java.lang.Number [Int java.lang.Number])
(with-fn-validation (foo 1 2))
==> 2
(with-fn-validation (foo 1.5 2))
==> Input to foo does not match schema: [(named (not (integer? 1.5)) x) nil]
See (doc stch.schema) for details of the :- syntax for
arguments and return schemas.
The overhead for checking if run-time validation should be used is very
small -- about 5% of a very small fn call. On top of that, actual
validation costs what it costs.
You can also turn on validation unconditionally for this
fn only by putting ^:always-validate metadata on the fn name.
Gotchas and limitations:
- The output schema always goes on the fn name, not the
arg vector. This means that all arities must share the same
output schema. Schema will automatically propagate primitive
hints to the arg vector and class hints to the fn name,
so that you get the behavior you expect from Clojure.
- Schema metadata is only processed on top-level arguments.
I.e., you can use destructuring, but you must put schema
metadata on the top-level arguments, not the destructured variables.
Bad: (defn' foo [{:keys [x :- Int]}])
Good: (defn' foo [{:keys [x]} :- {:x Int}])
- Only a specific subset of rest-arg destructuring is supported:
- & rest works as expected
- & [a b] works, with schemas for individual elements parsed
out of the binding, or an overall schema on the vector.
- & {} is not supported.
- Unlike defn, a final attr-map on multi-arity functions
is not supported.
defrecord'
macro
(defrecord' name field-schema extra-key-schema? extra-validator-fn? & opts+specs)
Define a record with a schema.
In addition to the ordinary behavior of defrecord,
this macro produces a schema for the Record, which
will automatically be used when validating instances of
the Record class:
(defrecord' FooBar
[foo :- Int
bar :- String])
(stch.schema.util/class-schema FooBar)
==> (Record user.FooBar {:foo Int, :bar java.lang.String})
(check FooBar (FooBar. 1.2 :not-a-string))
==> {:foo (not (integer? 1.2)), :bar (not (instance? java.lang.String :not-a-string))}
See (doc stch.schema) for details of the :- syntax
for record elements.
Moreover, optional arguments extra-key-schema? and
extra-validator-fn? can be passed to augment the record schema.
- extra-key-schema is a map schema that defines validation
for additional key-value pairs not in the record base
(the default is to not allow extra mappings).
- extra-validator-fn? is an additional predicate that
will be used as part of validating the record value.
The remaining opts+specs (i.e., protocol and interface
implementations) are passed through directly to defrecord.
Finally, this macro replaces Clojure's map->name constructor
with one that is more than an order of magnitude faster
(as of Clojure 1.5), and provides a new strict-map->name
constructor that throws or drops extra keys not in the
record base.
error!
macro
(error! s)
(error! s m)
Generate a cross-platform exception in client
(non-compilation) code.
explain
(explain this)
Expand this schema to a human-readable format suitable
for pprinting, also expanding class schematas at the leaves.
Example:
user> (explain {:a Keyword :b [Int]} )
{:a Keyword, :b [Int]}
explicit-schema-key
(explicit-schema-key ks)
fn'
macro
(fn' & fn-args)
fn' : defn' :: clojure.core/fn : defn
See (doc defn) for details.
Additional gotchas and limitations:
- Like defn', the output schema must go on the fn name.
If you want an output schema, your function must have a name.
- Unlike defn', the function schema is stored in metadata on the fn.
Clojure's implementation for metadata on fns currently produces a
wrapper fn, which will decrease performance and negate the benefits
of primitive type hints compared to clojure.core/fn.
fn-schema
(fn-schema f)
Produce the schema for a function defined
with fn' or defn'.
letfn'
macro
(letfn' fnspecs# & body#)
make-fn-schema
(make-fn-schema output-schema input-schemas)
A function outputting a value in output schema,
whose argument vector must match one of input-schemas,
each of which should be a sequence schema. Currently
function schemas are purely descriptive; they validate
against any function, regargless of actual input and
output types.
map->AnythingSchema
(map->AnythingSchema m__5818__auto__)
Factory function for class stch.schema.AnythingSchema, taking a map of keywords to field values.
map->ConditionalSchema
(map->ConditionalSchema m__5818__auto__)
Factory function for class stch.schema.ConditionalSchema, taking a map of keywords to field values.
map->EnumSchema
(map->EnumSchema m__5818__auto__)
Factory function for class stch.schema.EnumSchema, taking a map of keywords to field values.
map->EqSchema
(map->EqSchema m__5818__auto__)
Factory function for class stch.schema.EqSchema, taking a map of keywords to field values.
map->FnSchema
(map->FnSchema m__5818__auto__)
Factory function for class stch.schema.FnSchema, taking a map of keywords to field values.
map->IntersectionSchema
(map->IntersectionSchema m__5818__auto__)
Factory function for class stch.schema.IntersectionSchema, taking a map of keywords to field values.
map->MapEntrySchema
(map->MapEntrySchema m__5818__auto__)
Factory function for class stch.schema.MapEntrySchema, taking a map of keywords to field values.
map->NamedSchema
(map->NamedSchema m__5818__auto__)
Factory function for class stch.schema.NamedSchema, taking a map of keywords to field values.
map->OptionSchema
(map->OptionSchema m__5818__auto__)
Factory function for class stch.schema.OptionSchema, taking a map of keywords to field values.
map->OptionalKey
(map->OptionalKey m__5818__auto__)
Factory function for class stch.schema.OptionalKey, taking a map of keywords to field values.
map->PredicateSchema
(map->PredicateSchema m__5818__auto__)
Factory function for class stch.schema.PredicateSchema, taking a map of keywords to field values.
map->ProtocolSchema
(map->ProtocolSchema m__5818__auto__)
Factory function for class stch.schema.ProtocolSchema, taking a map of keywords to field values.
map->RecordSchema
(map->RecordSchema m__5818__auto__)
Factory function for class stch.schema.RecordSchema, taking a map of keywords to field values.
map->RecursiveSchema
(map->RecursiveSchema m__5818__auto__)
Factory function for class stch.schema.RecursiveSchema, taking a map of keywords to field values.
map->RequiredKey
(map->RequiredKey m__5818__auto__)
Factory function for class stch.schema.RequiredKey, taking a map of keywords to field values.
map->Single
(map->Single m__5818__auto__)
Factory function for class stch.schema.Single, taking a map of keywords to field values.
map->UnionSchema
(map->UnionSchema m__5818__auto__)
Factory function for class stch.schema.UnionSchema, taking a map of keywords to field values.
map-entry
(map-entry kspec val-schema)
named
(named schema name)
A value that must satisfy schema, and has a name
for documentation purposes.
optional-key
(optional-key k)
An optional key in a map.
optional-key?
(optional-key? ks)
pred-internal
(pred-internal p? pred-name)
A value for which p? returns true (and does not throw).
Optional pred-name can be passed for nicer validation errors.
protocol-name
(protocol-name protocol)
recursive
(recursive schema-var)
Support for (mutually) recursive schemas by passing a var that points to a schema,
e.g (recursive #'ExampleRecursiveSchema).
required-key
(required-key k)
A required key in a map.
required-key?
(required-key? ks)
safe-get
macro
(safe-get m k)
Like get but throw an exception if not found.
A macro just to work around cljx function
placement restrictions. Only valid in client
(non-compilation) code.
schema-choice
(schema-choice pred if-schema else-schema)
If the predicate returns truthy, use the if-schema,
otherwise use the else-schema.
schematize-fn
(schematize-fn f schema)
Attach the schema to fn f at runtime,
extractable by fn-schema.
set-fn-validation!
(set-fn-validation! on?)
Globally turn on schema validation for all
fn' and defn' instances.
specific-key?
(specific-key? ks)
start-walker
(start-walker sub-walker schema)
The entry point for creating walkers. Binds the provided
walker to subschema-walker, then calls it on the provided schema.
For simple validation, pass walker as sub-walker. More
sophisticated behavior (coercion, etc), can be achieved by
passing a sub-walker that wraps walker with additional behavior.
subschema-walker
The function to call within 'walker' implementations to
create walkers for subschemas. Can be dynamically bound
(using start-walker below) to create different walking behaviors.
For the curious, implemented using dynamic binding rather
than making walker take a subschema-walker as an argument
because some behaviors (e.g. recursive schema walkers)
seem to require mind-bending things like fixed-point
combinators that way, but are simple this way.
validate
(validate schema value)
Throw an exception if value does not satisfy schema;
otherwise, return value.
validation-error
macro
(validation-error schema value expectation & [fail-explanation])
walker
(walker this)
Produce a function that takes [data], and either
returns a walked version of data (by default, usually
just data), or a util/ErrorContainer containing value
that looks like the 'bad' parts of data with ValidationErrors
at the leaves describing the failures.
If this is a composite schema, should let-bind (subschema-walker
sub-schema) for each subschema outside the returned fn.
Within the returned fn, should break down data into constituents,
call the let-bound subschema walkers on each component, and then
reassemble the components into a walked version of the data
(or an ErrorContainer describing the validaiton failures).
Attempting to walk a value that already contains a
util/ErrorContainer produces undefined behavior.
User code should never call `walker` directly.
Instead, it should call `start-walker` below.
with-fn-validation
macro
(with-fn-validation & body)
Execute body with input and ouptut schema validation
turned on for all defn' and fn' instances.