Valivar
Mostly derived from eivindfjeldstad/validate; Valivar provides dynamic schemas for modern javascript and typescript validation and sanitization. In Valivar you can use catch-all keys $
and *
to define any array or object respectively, letting you define a single schema that can apply consistent rules for growing and changing datasets.
Install
Node.js or Browser
npm install valivar
Deno
import valivar from "https://deno.land/x/valivar/mod.ts"
Import in HTML
<script type="text/javascript" src="dist/valivar.js"></script>
From CDN
<script type="text/javascript" src="https://unpkg.com/valivar"></script>
Examples
For a full list see the examples directory (coming)
Play with it
Reademe rewrite in progress…
Usage
Define a schema and call .validate()
with the object you want to validate.
The .validate()
function returns an array of validation errors.
import { Schema } from 'valivar'
const user = new Schema({
username: {
type: String,
required: true,
length: { min: 3, max: 32 }
},
pets: [{
name: {
type: String
required: true
},
animal: {
type: String
enum: ['cat', 'dog', 'cow']
}
}],
address: {
street: {
type: String,
required: true
},
city: {
type: String,
required: true
}
zip: {
type: String,
match: /^[0-9]+$/,
required: true
}
}
})
const errors = user.validate(obj)
Each error has a .path
, describing the full path of the property that failed validation, and a .message
describing the error.
errors[0].path //=> 'address.street'
errors[0].message //=> 'address.street is required.'
Custom error messages
You can override the default error messages by passing an object to Schema.message()
.
const post = new Schema({
title: { required: true }
})
post.message({
required: (path) => `${path} can not be empty.`
})
const [error] = post.validate({})
assert(error.message = 'title can not be empty.')
It is also possible to define messages for individual properties:
const post = new Schema({
title: {
required: true,
message: 'Title is required.'
}
})
And for individual validators:
const post = new Schema({
title: {
type: String,
required: true,
message: {
type: 'Title must be a string.',
required: 'Title is required.'
}
}
})
Nesting
Objects and arrays can be nested as deep as you want:
const event = new Schema({
title: {
type: String,
required: true
},
participants: [{
name: String,
email: {
type: String,
required: true
},
things: [{
name: String,
amount: Number
}]
}]
})
Arrays can be defined implicitly, like in the above example, or explicitly:
const post = new Schema({
keywords: {
type: Array,
each: { type: String }
}
})
Array elements can also be defined individually:
const user = new Schema({
something: {
type: Array,
elements: [
{ type: Number },
{ type: String }
]
}
})
Nesting also works with schemas:
const user = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
}
})
const post = new Schema({
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
author: user
})
If you think it should work, it probably works.
Naming conflicts
Validate will naively assume that a nested object where all property names are validators is not a nested object.
const schema = new Schema({
pet: {
type: {
required: true,
type: String,
enum: ['cat', 'dog']
}
}
});
In this example, the pet.type
property will be interpreted as a type
rule, and the validations will not work as intended. To work around this we could use the slightly more verbose properties
rule:
const schema = new Schema({
pet: {
properties: {
type: {
required: true,
type: String,
enum: ['cat', 'dog']
}
}
}
});
In this case the type
property of pets.properties
will be interpreted as a nested property, and the validations will work as intended.
Custom validators
Custom validators can be defined by passing an object with named validators to .use
:
const hexColor = val => /^#[0-9a-fA-F]$/.test(val)
const car = new Schema({
color: {
type: String,
use: { hexColor }
}
})
Define a custom error message for the validator:
car.message({
hexColor: path => `${path} must be a valid color.`
})
Custom types
Pass a constructor to .type
to validate against a custom type:
class Car {}
const user = new Schema({
car: { type: Car }
})
Chainable API
If you want to avoid constructing large objects, you can add paths to a schema by using the chainable API:
const user = new Schema()
user
.path('username').type(String).required()
.path('address.zip').type(String).required()
Array elements can be defined by using $
as a placeholder for indices:
const user = new Schema()
user.path('pets.$').type(String)
This is equivalent to writing
const user = new Schema({ pets: [{ type: String }]})
Typecasting
Values can be automatically typecast before validation.
To enable typecasting, pass an options object to the Schema
constructor with typecast
set to true
.
const user = new Schema(definition, { typecast: true })
You can override this setting by passing an option to .validate()
.
user.validate(obj, { typecast: false })
To typecast custom types, you can register a typecaster:
class Car {}
const user = new Schema({
car: { type: Car }
})
user.typecaster({
Car: (val) => new Car(val)
})
Property stripping
By default, all values not defined in the schema will be stripped from the object.
Set .strip = false
on the options object to disable this behavior. This will likely be changed in a future version.
Strict mode
When strict mode is enabled, properties that are not defined in the schema will trigger a validation error. Set .strict = true
on the options object to enable strict mode.