Light validation utilities for TypeScript classes. With bsy-validation, class properties are decorated with validators, and then plain objects can be validated against a class's validation schema.
Note that version 2 is a complete rewrite, and is not backwards compatible.
With yarn:
yarn add bsy-validation@2.x.x
With npm:
npm install --save bsy-validation@2.x.x
Ensure that you have experimentalDecorators
and emitDecoratorMetadata
enabled in your tsconfig.json
.
First, decorate a class's properties with the @Validate
decorator, supplying
one or more
Validators
as arguments. All of the
Validator
classes can be found in the
validator directory.
For example, here is a decorated Person
class.
import {
Validate, IntValidator, StringValidator, MaxLengthValidator, EmailValidator,
PhoneValidator, DateValidator, NumberValidator
} from 'bsy-validation';
export class Person {
@Validate(new IntValidator())
id: number;
@Validate(
new StringValidator(),
new MaxLengthValidator(50))
name: string;
@Validate(new PhoneValidator())
phone: string;
@Validate(new EmailValidator())
email: string;
@Validate(new DateValidator())
dob: Date;
@Validate(new NumberValidator())
weight: number;
}
An object can be checked against this class's validation schema using an
ObjectValidator
instance. This class's validate
method takes two arguments: An object to
validate, and a the constructor of a @Validate
-decorated class. It returns a
promise that is resolved if the object is valid, or rejected with one or more
errors if the object is invalid.
Here's an example of a valid Person
object.
import { ObjectValidator } from 'bsy-validation';
import { Person } from './person';
const validator = new ObjectValidator();
// Joe Dirt is valid.
const joeDirt = {
id: 42,
name: 'Joe Dirte',
phone: '530-444-5555',
email: 'dirte@itsfrench.com',
dob: '1983-09-19T00:00:00.000Z',
weight: 205
};
validator
.validate(joeDirt, Person)
.then(() => console.log('Joe Dirt is valid.'));
The above logs "Joe Dirt is valid." Here's an example of an invalid object.
import { ObjectValidator } from 'bsy-validation';
import { Person } from './person';
const validator = new ObjectValidator();
// Donald Trump is invalid.
const emperorTrump = {
id: 74.7, // Not a valid int.
name: 'Trust me people, my name is Mister Magnificient Trump', // Too long.
weight: 239 // A lie, but valid.
};
validator
.validate(emperorTrump, Person)
.catch(err => console.error(JSON.stringify(err, null, 2)));
The above logs an error that describes why the object is invalid.
{
"code": "VAL_ERROR_LIST",
"name": "ValidationErrorList",
"detail": "Validation errors occurred.",
"errors": [
{
"code": "VALIDATION_ERROR",
"name": "ValidationError",
"detail": "\"name\" must be at most 50 characters long.",
"field": "name"
},
{
"code": "VALIDATION_ERROR",
"name": "ValidationError",
"detail": "\"id\" must be a valid integer.",
"field": "id"
}
]
}
For a given @Validate
-decorated property, validators are applied in order.
If one of the validators fails, then execution halts. For example, in the
Person
class defined above, Person.name
has two validators:
StringValidator
and
MaxLengthValidator.
StringValidator
is applied first, then
MaxLengthValidator
is called if
StringValidator
passes.
const aryaStark = {
name: false // Not a string. A girl has no name.
};
validator
.validate(aryaStark, Person)
.catch(err => console.error(JSON.stringify(err, null, 2)));
In the above example, aryaStark.name
is not a string, so MaxLengthValidator is never executed. The above logs the following error.
{
"code": "VAL_ERROR_LIST",
"name": "ValidationErrorList",
"detail": "Validation errors occurred.",
"errors": [
{
"code": "VALIDATION_ERROR",
"name": "ValidationError",
"detail": "\"name\" must be a string.",
"field": "name"
}
]
}
The built-in validators consider null
and undefined
properties to be valid.
So, for example, an empty object is considered to be a valid Person
. To
ensure that a property is present and/or non-null, use the
DefinedValidator
and
NotNullValidator.
In the following class, the message
property must be defined, cannot be null, and must be a string.
class Greeting {
@Validate(
new DefinedValidator(),
new NotNullValidator(),
new StringValidator())
message: string;
}
When defining custom validators, null
and undefined
values should be considered valid.
To create a custom validator, implement the
Validator
interface. You must define a validate
method that returns a boolean or a
promise, and a getErrorMessage
method that's used when validation fails.
Here's an example.
// File: ./odd-number-validator.ts
import { Validator, NumberValidator } from 'bsy-validation';
class OddNumberValidator implements Validator {
/**
* Check that val is a number and is odd.
*/
validate(val: any): boolean {
const numVal = new NumberValidator();
return val === undefined || val === null ||
numVal.validate(val) && Number(val) % 2 === 1;
}
/**
* Describe the validation error.
*/
getErrorMessage(propName: string): string {
return `"${propName}" must be an odd number.`;
}
}
Then, use the validator as follows.
import { Validate, ObjectValidator } from 'bsy-validation';
import { OddNumberValidator } from './odd-number-validator';
class Preferences {
@Validate(new OddNumberValidator())
favoriteNumber: number;
}
new ObjectValidator()
.validate({favoriteNumber: 18}, Preferences)
.catch(err => console.error(JSON.stringify(err, null, 2)));
When run, the above prints the following error.
{
"code": "VAL_ERROR_LIST",
"name": "ValidationErrorList",
"detail": "Validation errors occurred.",
"errors": [
{
"code": "VALIDATION_ERROR",
"name": "ValidationError",
"detail": "\"favoriteNumber\" must be an odd number.",
"field": "favoriteNumber"
}
]
}