Rust API reference#
The Rust crate is shape-schema (the library is
shape: use shape::…), in rs/.
It matches the canonical TypeScript for validation outcomes, produced values
and exact error text; the parity page
lists its divergences. This page is the Rust surface and its idioms; the
behaviour of each builder is in the builder reference.
Compilation#
use shape::{shape, Options, Schema, Spec, Token};
let s: Schema = shape(spec); // any `impl Into<Spec>`
let s = Schema::new(spec); // the same
let s = Schema::with_options(spec, &Options::default());
let s = Schema::parse("String.Min(2)")?; // the string form; Err(ExprError)
A Spec is what a schema is compiled from:
| Form | Example | Meaning |
|---|---|---|
| a type token | Token::String |
required, of that kind |
| a literal | 8080, "x", true, Value::Null, null() |
optional, that value the default |
| a literal object | from_map(map) |
a Map as a spec: an object of those properties, each a literal |
| an object | obj([("a", Spec::from(1))]), shape!({ "a": 1 }) |
a closed object of those properties |
| an array | arr([Spec::from(Token::String)]), shape!([String]) |
an array of that element shape; two or more elements are a tuple |
| a regexp | Regex::new("^a")? |
a required string matching it |
| a node | min(2, Token::Number) |
a builder's result |
shape! writes a spec as the example it is, in the serde_json::json! style:
objects in braces, arrays in brackets, the type tokens bare, and any other
Rust expression a spec can be made from:
use shape::{min, shape, Schema, Token};
let s = Schema::new(shape!({
"name": String,
"port": 8080,
"tags": [String],
"age": min(0, Token::Integer),
"addr": { "zip": String },
}));
Schema methods#
| Method | Returns | Notes |
|---|---|---|
validate(Value) |
Result<Value, ValidationError> |
the produced value, defaults injected |
validate_ctx(Value, &mut Context) |
the same | with a context of the caller's, for custom validators |
validate_into::<T>(impl Into<Value>) |
Result<T, IntoError> |
produce, then deserialize into T by serde (feature serde, on by default) |
valid(&Value) / matches(&Value) |
bool |
a verdict, nothing produced or rendered |
error(&Value) |
Vec<FieldError> |
every issue, empty when valid |
json() |
Result<Value, JsonError> |
the declarative JSON, read back by build |
json_schema() |
Value |
the JSON Schema export (draft 2020-12) |
standard() |
StandardSchema |
the Standard Schema V1 surface: version 1, vendor "shape", and validate(Value), a StandardResult of the value or the issues, never failing |
node() |
&Node |
the compiled tree |
defs() |
&HashMap<String, Arc<Node>> |
the defined nodes |
validate takes its input by value and produces in place. Value::Undefined
is no value at all, as a bare shape() call is in TypeScript. The Node of
the tree is a plain struct (kind: Kind, required, its children, its
befores and afters as Validators); Node::of(kind) is a bare one.
Values#
shape::Value is what is validated and produced: what JSON can carry, plus
the four things the canonical behaviour needs and JSON cannot say.
| Variant | Stands for |
|---|---|
Undefined |
no value: a missing property, an absent argument |
Null |
a present null |
Bool, Num(f64), Str, BigInt |
the scalars; every number is a double, NaN included |
Arr(Vec<Value>), Obj(Map) |
containers; Map is an insertion-ordered IndexMap |
Date(i64) |
a date, as milliseconds since the epoch |
Func(u64) |
an opaque function value, told apart by id |
From conversions exist for the Rust scalars, &str, String, Vec,
Map and BigInt, and, with the serde feature, both ways with
serde_json::Value (an undefined property is dropped, NaN and a function
become null, a date its ISO text). The accessors are is_undefined(),
is_null(), as_f64(), as_str(), as_arr(), as_obj() and type_of(),
the JavaScript typeof.
Tokens#
Token::{Any, String, Number, Boolean, Object, Array, Function, Integer, Date, BigInt}.
A token is required (but for Any) and carries the kind's empty value as the
default it injects once made optional. Token::Object accepts any keys;
Token::Array any elements.
Builders#
Every builder is a free function taking the spec it applies to as its last
argument, and a chain method on Node. buildize(spec) is the node a spec
compiles to, where a chain starts; pass any() for the bare form.
| Builder | Chain |
|---|---|
required(spec), optional(spec), default(value, spec), default_of(value) |
.required(), .optional(), .default_to(value) |
skip(spec), ignore(spec), empty(spec), nullable(spec), fault(msg, spec), never(spec) |
.skip(), .ignore(), .empty(), .nullable(), .fault(msg), .never() |
open(spec), closed(spec), child(child, spec), rest(child, spec) |
.open(), .closed(), .child(c), .rest(c) |
type_(kind, spec), func(spec) |
.type_(kind), .string(), .number(), .boolean(), .object(), .array(), .function(), .integer(), .date(), .any(), .func() |
exact(values) |
.exact(values) |
min(bound, spec), max(..), above(..), below(..), len(length, spec) |
.min(b), .max(b), .above(b), .below(b), .len(n) |
check(f, spec), check_re(regex, spec), before(f, spec), after(f, spec) |
.check(f), .check_re(re), .before(f), .after(f) |
one(shapes), some(shapes), all(shapes), discriminated(tag, branches) (the branches (name, spec) pairs) |
n/a |
define(name, spec), refer(name, spec), refer_with(name, ReferOptions, spec) |
.define(name), .refer(name), .refer_with(name, opts) |
rename(name, spec), rename_with(name, RenameOptions, spec) |
.rename(name), .rename_with(name, opts) |
key(), key_depth(depth), key_join(depth, sep), key_args(&[Value]) |
n/a |
catch(fallback, spec), transform(f, spec), describe(text, spec) |
.catch(v), .transform(f), .describe(text) |
coerce(spec), email(spec), url(spec), uuid(spec), date_time(spec), ip(spec), ipv4(spec), ipv6(spec) |
.coerce(), .email(), .url(), .uuid(), .date_time(), .ip(), .ipv4(), .ipv6() |
pick(names, spec), omit(names, spec), partial(spec), extend(extra, spec) |
.pick(names), .omit(names), .partial(), .extend(extra) |
The chain method for Default is default_to, to leave Default::default
alone; the one for Type is type_, since type is a keyword. Builders
consume the node they are given and return it, so a compiled Schema is
immutable and Send + Sync; a base to reshape twice is cloned first.
The argument types: pick and omit take a Names, made from a &str, a
String, a Vec or an array of them; type_ takes a TypeRef, made from
a Kind, a Token, a kind's name or a Node; check_re takes a
regex::Regex; exact any iterator of values. ReferOptions has fill
(substitute even when the value is absent) and strict (a name with no
define is an error); RenameOptions has keep (keep the original key
too) and claim (other keys to read from when the renamed one is missing).
A builder called with a wrong argument (min("x", ..), len(-1, ..),
define("", ..), pick("z", ..)) cannot fail as TypeScript's throwing
builders do; it returns a fault node that accepts nothing and reports the
message at validation. The string form refuses it instead: expr and
Schema::parse return an ExprError for a wrong argument (Shape: Min needs a number) as for a malformed expression, which is what expr throws
in TypeScript.
Custom validators#
check, before and after take Fn(&mut State, &mut Update) -> bool,
Send + Sync + 'static (the ValidatorFn type). The State is the
value, the node, the path (path_arr, a slice of PathPart;
path_str() dotted; path_keys() as strings), the key,
parent_is_array, absent (missing, rather than present and null),
is_match (a valid call: nothing is produced), check_name, and the
Context; the Update is what the validator reports: err (an
UpdateErr: a text with $PATH and $VALUE, a FieldError, or several),
why and mark, a replacement val, a replacement node, done to stop
the node's other checks, and fatal to end the walk's errors there.
transform takes Fn(Value, &mut State) -> Value (the TransformFn
type).
The Context carries custom, a typed map of the caller's own state
(get::<T>, set), refs, the defined shapes met on the call, and
err, every error raised.
Options#
Options has three switches, as the canonical ShapeOptions does:
key_expr (on: "a: Min(1)" keys are key expressions), meta with
meta_suffix (off: "a$$" keys are metadata for "a") and val_expr with
val_expr_mark (off: the string under the mark key is an expression applied
to the object).
Errors#
ValidationError holds issues: Vec<FieldError> and prints them joined by
newlines, the canonical text. A FieldError has path, path_arr, key,
kind, value, why, mark, text, args and check, and prints its
text; see the error reference. The why codes and mark
numbers are the WHY_* and MARK_* constants of shape::error. Both
types are std::error::Error, as are IntoError (Validation or
Deserialize, from validate_into), ExprError, ArguError and
JsonSchemaError, the last three newtypes of their message.
The string form#
expr(src) parses an expression to a Node; expr_apply(src, carrier)
applies one to a spec, as key and value expressions do;
stringify_node(&node, inline) renders a node as its spec text, inline
writing a string value bare, as a composite message does. The grammar is the one of
the string DSL.
JSON Schema#
Schema::json_schema() and shape::json_schema(&node) export;
shape::from_json_schema(&Value) imports, returning a Spec to compile or
compose further, or a JsonSchemaError. shape::jsonschema::JSON_SCHEMA_DRAFT
is the $schema URL written.
Positional arguments (Argu)#
use shape::{skip, Spec, Token, Value};
let argu = shape::Argu::new("mylib");
let sig = argu.signature(
"foo",
[
("a", Spec::from(Token::Number)),
("b", Spec::from(skip(Token::String))),
],
)?;
let named: shape::Map = sig.apply(vec![Value::from(2)])?; // {"a": 2, "b": Undefined}
argu.validate(args, whence, spec) compiles and applies in one call. A
skip slot is optional and shifts the ones after it; a rest slot
captures whatever remains; too many arguments, or one that does not match,
is an ArguError with the canonical text
(mylib (foo): Too many arguments for type signature (was 3, expected 2)).
Version#
shape::VERSION is the crate version.