Schemas

@zuze/schema makes the following schemas available for you to use:

Unlike yup, joi, and ajv, @zuze/schema was designed to be functional and that means that it doesn't use methods. All validators can be added to all schemas regardless of if they make sense (but we'll warn you if you're doing something silly) via the tests function.

Because of this, our documentation of each schema focuses primarily on the default transforms that come with each schema because, again, schemas don't have methods.

Schemas#

All schema constructor functions (except lazy) have the same signature:

schema(...defs: Partial<SchemaDefinition>[])

object, array and date may also accept different parameters as their first parameter.

mixed#

A mixed schema is an abstract schema, it's use is generally not encouraged except maybe for the base of a conditional schema.

Partial definitions can be passed to it as arguments just like any other:

NOTE: mixed schemas DO NOT perform any default transform on its value at validation/cast time.

import { ast } from '@zuze/schema'
const { createSchema } = ast;
const sku = createSchema({
schema: 'mixed',
nullable: true,
label:'sku',
// a contrived example, I know :)
conditons:[
{
when: { product: { tests: [['is','snowboard']]} },
then: { schema: 'string', tests: [['matches','^[0-9A-Z]{10,20}$']]] },
otherwise: { schema: 'number', tests: [['min',10]] }
}
]
});

string#

Accepts values that are strings and attempts to cast the subject value to a string at validation/cast time.

import { ast, cast, getErrors } from '@zuze/schema'
const { createSchema } = ast;
const schema = createSchema({
schema: 'string',
tests: ['required', ['min',10]]
});
cast(schema,9) // "9"
isValidSync(schema,9); //false
getErrors(schema,9); // field must not be shorter than 10 characters

number#

A number schema attempts to coerce the subject value to a numeric value, if it isn't one.

import { ast, cast, getErrors } from '@zuze/schema'
const { createSchema } = ast;
const schema = createSchema({
schema: 'number',
tests: ['required', ['between',10,20]]
});
cast(schema, "18") // "18"
isValidSync(schema, "18"); // true
getErrors(schema, "7"); // field must be between 10 and 20

boolean#

A boolean schema attempts to coerce the subject value to a boolean (starting to notice a pattern?)

import { ast, cast, validateSync, getErrors } from '@zuze/schema'
const { createSchema } = ast;
const schema = createSchema({
schema: 'boolean',
typeError: 'must be a boolean'
});
cast(schema, "ture") // TypeError
validateSync(schema,"ture") // ValidationError - must be a boolean
isValidSync(schema, "true"); // true
getErrors(schema, "ture"); // must be a boolean

date#

A date schema is the first deviation from the regular pattern. Instead of only accepting partial schema definitions, it can accept a function (only as it's first parameter). If a function is given, that function will be used as a mechanism to parse the subject value to convert it to a Date object. If no function is given it will use date-fns parseISO (optional dependency, don't forget to install it!).

Providing your own parser allows you to tree-shake date-fns if you don't need it, or it also allows you to do some really cool relative date parsing using other libraries.

import { Date as SugarDate } from 'sugar-date';
import { ast, cast } from '@zuze/schema';
const { createSchema } = ast;
const schema = createSchema({schema: 'date'},{dateParser:SugarDate.create});
cast(schema, 'last wednesday'); //Wed Mar 11 2020 00:00:00 GMT-0300 (Atlantic Daylight Time)

lazy#

A lazy schema has no AST-equivalent because it doesn't need one. It's for developers who like to write functional code.

It accepts only a single parameter - a function that is called with the subject value that must return a schema.

import { lazy, getErrors, isValidSync } from '@zuze/schema';
const schema = lazy(value => value === 9 ? number(tests(min(9))) : string(tests(min(9))));
getErrors(schema,"9"); // field must be no shorter than 9 characters
isValidSync(schema,9); // true

object#

object and array are special schemas in that they have inner's. They can have their own transforms/validations and they can also transform/validate their subschemas. In the case of an object this is done via it's shape

const schema = {
schema:'object',
shape: {
firstName:{
schema:'string',
tests:['required', ['min',5] ]
},
lastName:{
schema:'string',
tests:['required', ['min',5] ]
},
address: {
schema: 'object',
shape: {
address1:{
schema:'string',
tests:['required', ['max',60] ]
},
province: {
schema:'string',
tests:['required',['oneOf',['BC','NL']]]
}
postalCode:{
schema:'string',
tests:['required', ['matches',/^[A-Z]\d[A-Z]\d[A-Z]\d$/i]],
conditions:[
{
// when province is BC
when: { province: { tests: [['is','BC']] } },
// postal code must start with a V
then: { tests: ['matches',/^V/i]] }
},
{
// when province is NL
when: { province: { tests: [['is','NL']] } },
// postal code must start with a A
then: { tests: ['matches',/^V/i]] }
},
],
transforms:['strip'],
}
}
},
}
}

array#

An array schema's subschema is given to it via of

Let's validate the following structure

{
users: [
{
firstName: 'freddie',
lastName: 'mercury'
}
]
}
const schema = {
schema: 'object',
shape: {
users: {
schema: 'array',
of: {
schema: 'object',
shape: {
firstName: {
schema: 'string',
tests: ['required']
},
lastName: {
schema: 'string',
tests: ['required']
},
}
},
tests: [ ['min',4] ] // at least 4 user objects
}
}
}

tuple#

A tuple is an fixed length array of values. Instead of having a tuple schema, we just use array but instead of passing of a single schema, we pass it an array of schemas:

Let's look at a similar structure to above:

{
users: [ ['Freddie', 'Mercury'] ]
}
const schema = {
schema: 'object',
shape: {
users: {
schema: 'array',
of: {
schema: 'array',
of: [
{
schema: 'string',
label: 'first name',
tests: ['required'],
},
{
schema: 'string',
label: 'last name',
tests: ['required'],
},
],
},
tests: [['min', 4]],
},
},
};

Functions#

All schema operating functions accept the following arguments:

cast#

cast(schema: SchemaDefinition, value: any, options?: ValidationOptions): any

Casts a value using a schema's transforms. Transforms are not run when the ValidationOption strict is true.

cast(string(), 9); // 9
cast(array(compact()),[1,0]); // [1]

validate#

validate(schema: SchemaDefinition, value: any, options: ValidationOptions): any

When sync is false, returns a Promise that resolves to the passed in value or rejects with a ValidationError.

await validate(string(tests(required())), ''); // ValidationError: field is required

validateSync#

Alias for validate with { sync: true } option. Returns the passed in value or throws a ValidationError

validateAt#

validateAt(path: string, schema: SchemaDefinition, value: object | any[], options: ValidationOptions): any

Note: The value passed in must be the value described by the entire schema definition, not the value at path.

const schema = object({
fieldA:array(object({
fieldB:string(tests(min(10)))
}))
})
await validateAt('fieldA[1].fieldB', schema, {
fieldA:[
{ fieldB: 9 },
{ fieldB: 'short' }
]
});
// throws ValidationError field must be no shorter than 10 characters

validateAtSync#

Alias for validateAt with { sync: true } option.

isValid#

isValid(schema: SchemaDefinition, value: any, options: ValidationOptions): Promise<boolean>

isValid and isValidSync do not throw errors. These methods always return booleans or Promises that resolve to a boolean.

isValidSync#

Alias for isValid with { sync: true } option.

isValidAt#

isValid(path: string, schema: SchemaDefinition, value: any, options: ValidationOptions): boolean | Promise<boolean>

Same as validateAt except this method always returns a boolean or a Promise that resolves to a boolean.

isValidSyncAt#

Alias for isValidAt with { sync: true } option.

getErrors#

getErrors(schema: SchemaDefinition, value: any, options?: ValidationOptions)

getErrors is similar to validate except that it returns (instead of throws) error messages.

If accepts an option of multi: true to return error messages as an array if abortEarly is false.

const schema = string(tests(required(),between(5,10),email()));
getErrors(schema, 'jim'); // field must be between 5 and 10 characters
getErrors(schema, 'jim', { abortEarly: false, multi: true});
/*
[
'field must be between 5 and 10 characters'
'field must be a valid email address'
]
*/

In the case of on object schema it maps errors to an object where the paths are the keys:

getErrors(
object({
firstName: string(tests(min(5))),
lastName: string(tests(between(5,10))),
address: object({
street: string(tests(required())),
city: string(tests(not('london'))),
postal: string(tests(max(6))),
})
}),
{
firstName: 'joe',
lastName: 'fred',
address: {
city: 'london',
postal: 'not a postal'
}
},
{
abortEarly: false,
sync: true
}
);
/*
{
firstName: 'firstName must be not shorter than 5 characters',
lastName: 'lastName must be between 5 and 10 characters',
address.street: 'address.street is required',
address.city: 'address.city must not be london',
address.postal: 'address.postal must be no longer than 6 characters'
}
*/

getErrorsSync#

Alias for getErrors with { sync: true } option.