Sito is a JavaScript lightweight schema validator built without any dependencies. The API is heavily inspired by Yup.
npm install sito
Define a validator, validate the object, array or any scalar values. The schema definition is extremely declarative that allows building complex schemas of interdependent validators.
import { object, array, number, string } from 'sito'
const objectSchema = object({
foo: object({
bar: number()
}),
})
await objectSchema.assert({
foo: {
bar: 'a',
},
}) // throws error with message => foo.bar should be a number
const arraySchema = array([
string(),
number(),
])
await arraySchema.assert([
'foo',
'bar',
]) // throws error with message => [1] should be a number
const arrayOfValidatorsSchema = array(string())
await arrayOfValidatorsSchema.assert([
'foo',
'bar',
'baz',
42,
]) // throws error with message => [3] should be type of string
const mapOfValidatorsSchema = object(
object({
name: string().required(),
age: number().required(),
}),
)
await mapOfValidatorsSchema.assert({
john: { name: 'john', age: 28 },
pit: { name: 'pit' },
}) // throws error with message => pit.age is required
The exported functions are factory methods of validators:
import {
required,
boolean,
forbidden,
exists,
oneOf,
string,
number,
object,
array,
date,
check,
combine,
} from 'sito'
If you need the validator classes, they are also exported:
import {
GenericValidator,
StringValidator,
NumberValidator,
SchemaValidator,
ObjectValidator,
ArrayValidator,
DateValidator,
} from 'sito'
Sito
ValidationError(message: string, value: any, path: string, key: string)
BulkValidationError(errors: ValidationError[])
- GenericValidator
validator.assert(payload: any): Promise<void>
validator.assertBulk(payload: any): Promise<void>
validator.validate(payload: any): Promise<ValidationError[]>
validator.isValid(payload: any): Promise<Boolean>
validator.required(enabled?: boolean): GenericValidator
validator.forbidden(enabled?: boolean, options?: ForbiddenOptions): GenericValidator
validator.message(message: string | function): GenericValidator
validator.combine(...validators: GenericValidator[]): GenericValidator
validator.check({ message: string | function, validate: function, optional?: boolean, common?: boolean }): GenericValidator
combine(...validators: GenericValidator[]): GenericValidator
check({ message: string|function, validate: function, optional?: boolean, common?: boolean }): GenericValidator
boolean()
oneOf(values: any[])
equals(value: any)
required(enabled?: boolean)
forbidden(enabled?: boolean), options?: ForbiddenOptions)
transform(mapper?: Mapper, options?: TransformOptions)
- StringValidator|string
- NumberValidator|number
- ArrayValidator|array
- ObjectValidator|object
- DateValidator|date
Thrown on failed validations, with the following properties
name
:ValidationError
path
:string
, indicates where the error thrown.path
is equal topayload
at the root level.key
:string
, indicates property key.message
:string
, error messagevalue
:any
, checked value
Thrown on failed validations, with the following properties
name
:BulkValidationError
errors
:ValidationError[]
message
:string
const schema = object({ foo: required() })
await schema.assert({}) // throws error with message => foo is required
assertBulk
method forces to validate the whole payload and collect errors, if there are some errors, BulkValidationError will be thrown
const schema = object({ foo: required() }).strict()
await schema.assertBulk({ foo: 'bar', baz: 42 })
/**
throws error =>
{
message: 'Bulk Validation Failed',
errors: [{
name: 'ValidationError',
message: 'baz is forbidden attribute',
path: 'baz',
value: 42,
key: 'baz'
}]
}
*/
validate
method performs validation and returns an array of the errors
const schema = object({ foo: required() }).strict()
await schema.validate({ foo: 'bar', baz: 42 })
/**
=> [{
name: 'ValidationError',
message: 'baz is forbidden attribute',
path: 'baz',
value: 42,
key: 'baz'
}]
*/
isValid
method performs validation and returns true
in case of successful validation, otherwise false
await array([number()]).isValid(['ops']) // false
Method takes flag enabled
so you can disable such check on the fly.
const schema = string().required()
await schema.assert('sito') // ok
Method takes flag enabled
so you can disable such check on the fly.
options
object - takes ignoreNil
flag, which allows to ignore null
and undefined
values.
const MALE = 'm'
const FEMALE = 'f'
const schema = object({
name: string(),
gender: oneOf([FEMALE, MALE]),
age: (value, key, obj) => number()
.min(18)
.forbidden(obj.gender === FEMALE)
.message('It is not decent to ask a woman about her age 8)'),
})
await schema.assert({ name: 'Tolya', gender: 'm', age: 41 })
// ok
await schema.assert({ name: 'Zina', gender: 'f', age: 38 })
// throws error with message => It is not decent to ask a woman about her age 8)
Set custom message
:
const schema = string().message('custom message')
await schema.assert(5) // throws error with message => custom message
message
method takes function as well:
const schema = object({
foo: string().message((path, value, key) => `${path} is not valid`,)
})
await schema.assert({ foo: 5 }) // throws error with message => foo is not valid
validator.check({ message: string | function, validate: function, optional?: boolean, common?: boolean }): GenericValidator
You can enrich validator with custom check using check
method.
const secret = 'mankivka'
const schema = object({
secret: new GenericValidator().check({
optional: false,
message: 'secret is not valid',
validate: value => value === secret,
})
})
await schema.assert({ secret: 'popivka' }) // throws error with message => secret is not valid
message
:string | function(path: string, value: any, key: string|void): string|string
validate
:validate: function(value: any, key: string, shape: any): boolean|Promise<boolean>
optional?:
boolean
, defaulttrue
enabled?:
boolean
, defaulttrue
common?:
boolean
, defaultfalse
A check marked as common
forces the validator to run this check on all other checks of this validator.
check({ message: string|function, validate: function, optional?: boolean, common?: boolean }): GenericValidator
Also, you can create a generic validator with a custom check using the check
factory.
const secret = 'mankivka'
const schema = object({
secret: check({
optional: false,
message: path => `${path} is not valid`,
validate: value => value === secret,
})
})
await schema.assert({ secret: 'popivka' }) // throws error with message => secret is not valid
class DateValidator extends GenericValidator {
constructor() {
super()
this.check({
common: true,
message: path => `${path} is not a date`,
validate: value => new Date(value).toString() !== 'Invalid Date',
})
}
inFuture() {
return this.check({
message: path => `${path} should be in future`,
validate: value => new Date(value).getTime() > Date.now(),
})
}
}
const date = () => new DateValidator()
const schema = object({
dob: date().inFuture().required()
}).required()
await schema.assertBulk({ dob: 'not a date' })
/**
throws error =>
{
"name": "BulkValidationError",
"message": "Bulk Validation Failed",
"errors": [
{
"name": "ValidationError",
"message": "dob is not a date",
"path": "dob",
"key": "dob",
"value": "not a date"
},
{
"name": "ValidationError",
"message": "dob should be in future",
"path": "dob",
"key": "dob",
"value": "not a date"
}
]
}
*/
This may also be required if you need to expand the validator's prototype
NumberValidator.expand({
safe() {
return this.check({
validate: value => value < Number.MAX_SAFE_INTEGER,
message: key => `${key} is not safe`,
})
},
})
It might be useful if you need to merge validators
const userIdSchema = string().max(50).required()
.combine(
check({
validate: value => User.exists({ where: { id: value } }),
message: (path, value) => `User not found by id ${value}`,
})
)
It is a factory function which generates instances of GenericValidator with provided validators
const userIdSchema = combine(
string().max(50).required(),
check({
validate: value => User.exists({ where: { id: value } }),
message: (path, value) => `User not found by id ${value}`,
})
)
Define a boolean validator.
boolean()
Define a oneOf validator.
const validator = oneOf([1, 2])
await validator.isValid(1) // => true
await validator.isValid(3) // => false
Define a equals validator.
const validator = equals(1)
await validator.isValid(1) // => true
await validator.isValid(3) // => false
Define a required validator.
string().required()
// or
required()
Method takes flag enabled
so you can disable such check on the fly.
await required(false).isValid(null) // => true
Define a forbidden validator.
string().forbidden()
// or
forbidden()
Method takes flag enabled
so you can disable such check on the fly.
await forbidden(false).isValid({}) // => true
Define a transformer that will be called before the validation. After the transformation the resulted value will be set into payload.
const helper = {
kyiv: 'Kyiv'
}
const schema = oneOf(['Mankivka', 'Kyiv']).transform((value, key, payload) => helper[value] || value)
const payload = { city: 'kyiv' }
await schema.assert(payload) // ok
assert.deepStrictEqual(payload, { city: 'Kyiv' }) // ok
Normalize the value before the validation.
const schema = object({
a: boolean(),
b: date(),
c: number(),
d: array(),
})
const payload = {
a: 'true',
b: '1689670492966',
c: '5',
d: 'foo',
}
await schema.assert(payload, { normalize: true })
/**
* The `payload` will be transformed to the following:
* {
a: true,
b: new Date(1689670492966),
c: 5,
d: ['foo'],
*}
*/
/**
* You can do the same by defining the normalization explicitly
*/
const schema = object({
a: boolean().normalize(),
b: date().normalize(),
c: number().normalize(),
d: array().normalize(),
})
await schema.assert(payload)
Define a string validator.
string()
Set the expected length for the string.
Set the minimum expected length for the string.
Set the maximum expected length for the string.
Takes a regex pattern to check the string.
await string().pattern(/(foo|bar)/).isValid('foo') // => true
await string().pattern(/(foo|bar)/).isValid('baz') // => false
Define a number validator.
number()
Set the minimum value allowed.
Set the maximum value allowed.
Value must be an integer.
Value must be a positive number.
Value must be a negative number.
Force the validator to perform type checking
Define an array validator.
array()
A strict
method makes the schema strict or no, it means that each attribute that is not defined in the schema will be rejected.
const schema = array([string().required()]).strict()
await schema.assert(['foo', 'bar']) // throws error with message => [1] is forbidden attribute
You can define the shape for an array.
array().shape([number()])
// or
array([number()])
You are able to define validator for each element of an array.
const schema = array().of(string().min(2))
await schema.isValid(['ab', 'abc']) // => true
await schema.isValid(['ab', 'a']) // => false
You can also pass some validator to the array constructor.
array().of(number())
// or
array(number())
It accepts function as well, which should return instance of GenericValidator.
array().of((value, idx, array) => number())
// or
array((value, idx, array) => number())
const fnSchema = array(
(value, idx) => number().forbidden(idx === 100),
)
assert.strictEqual(await fnSchema.isValid([1]), true)
const numbersList = [...Array(100), 5].map(() => Math.random())
await fnSchema.assert(numbersList) // throws error with message => [100] is forbidden attribute
Force the validator to check that the provided array is not empty.
Force the validator to check that the provided array has less than or equal n
elements`.
Force the validator to check that the provided array has more than or equal n
elements`.
Force the validator to check that the provided array has n
elements`.
Define object validator.
object()
A strict
method makes the schema strict or no, it means that each attribute that is not defined in the schema will be rejected.
const schema = object({
foo: string().required(),
}).strict()
await schema.assert({ foo: 'bar', baz: 42 }) // throws error with message => baz is forbidden attribute
You can define the shape of an object.
object().shape({
num: number(),
})
// or
object({
num: number(),
})
You can also pass a validator to the object constructor.
object().of(number())
// or
object(number())
const schema = object(
object({ name: string() })
)
await schema.assert({
foo: { name: 'john' },
bar: { name: 'doe' },
}) // ok
It accepts function as well, which should return instance of GenericValidator.
object().of((value, key, object) => number())
// or
object((value, key, object) => number())
const ALLOWED_MUSICIANS = ['drums', 'bass', 'piano']
const fnSchema = object(
(value, key) => object({
name: string().required().min(2).max(35),
level: number().min(0).max(10),
})
.forbidden(!ALLOWED_MUSICIANS.includes(key))
.message(`${key} is not needed`),
)
const musiciansMap = {
bass: {
name: 'Valera',
level: 10,
},
drums: {
name: 'Andrii',
level: 9,
},
piano: {
name: 'Victor',
level: 10,
},
voice: {
name: 'Olga',
level: 10,
},
}
await fnSchema.assert(musiciansMap) // throws error with message => voice is not needed
Define a date validator.
date()
Force the validator to check that the provided date is in the future.
Force the validator to check that the provided date is in the past.
Force the validator to check that the date is today.
Force the validator to check that the provided date is before the validated one.
Force the validator to check that the provided date is after the validated one.