Sito is a JavaScript lightweight schema validator built without any dependencies. The API is heavily inspired by Yup.
npm install sitoDefine 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 requiredThe 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'SitoValidationError(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): GenericValidatorvalidator.forbidden(enabled?: boolean, options?: ForbiddenOptions): GenericValidatorvalidator.message(message: string | function): GenericValidatorvalidator.combine(...validators: GenericValidator[]): GenericValidatorvalidator.check({ message: string | function, validate: function, optional?: boolean, common?: boolean }): GenericValidatorcombine(...validators: GenericValidator[]): GenericValidatorcheck({ message: string|function, validate: function, optional?: boolean, common?: boolean }): GenericValidatorboolean()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:ValidationErrorpath:string, indicates where the error thrown.pathis equal topayloadat the root level.key:string, indicates property key.message:string, error messagevalue:any, checked value
Thrown on failed validations, with the following properties
name:BulkValidationErrorerrors:ValidationError[]message:string
const schema = object({ foo: required() })
await schema.assert({}) // throws error with message => foo is requiredassertBulk 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') // okMethod 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 messagemessage 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 validvalidator.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 validmessage:string | function(path: string, value: any, key: string|void): string|stringvalidate:validate: function(value: any, key: string, shape: any): boolean|Promise<boolean>optional?:boolean, defaulttrueenabled?:boolean, defaulttruecommon?: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 validclass 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) // => falseDefine a equals validator.
const validator = equals(1)
await validator.isValid(1) // => true
await validator.isValid(3) // => falseDefine 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) // => trueDefine a forbidden validator.
string().forbidden()
// or
forbidden()Method takes flag enabled so you can disable such check on the fly.
await forbidden(false).isValid({}) // => trueDefine 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' }) // okNormalize 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') // => falseDefine 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 attributeYou 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']) // => falseYou 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 attributeForce 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 attributeYou 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' },
}) // okIt 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 neededDefine 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.