A general purpose JavaScript validation library based on type combinators
Features
- concise yet expressive syntax
- validates native types, refinements, objects, lists and tuples, enums, unions, dicts, intersections
- validates structures with arbitrary level of nesting
- detailed informations on failed validations
- lightweight alternative to JSON Schema
- reuse your domain model written with tcomb
Documentation
Basic usage
If you don't know how to define types with tcomb you may want to take a look at its README file.
The main function is validate
:
validate(value, type, [options]) -> ValidationResult
value
the value to validatetype
a type defined with the tcomb libraryoptions
(optional) is an object with the following keyspath: Array<string | number>
path prefix for validationcontext: any
passed togetValidationErrorMessage
(useful for i18n)strict: boolean
(defaultfalse
) iftrue
no additional properties are allowed while validating structs
returns a ValidationResult
object containing the result of the validation
Note.
options
can be an array (aspath
prefix) for backward compatibility (deprecated)
Example
var t = require('tcomb-validation');
var validate = t.validate;
validate(1, t.String).isValid(); // => false
validate('a', t.String).isValid(); // => true
You can inspect the result to quickly identify what's wrong:
var result = validate(1, t.String);
result.isValid(); // => false
result.firstError().message; // => 'Invalid value 1 supplied to String'
// see `result.errors` to inspect all errors
Primitives
// null and undefined
validate('a', t.Nil).isValid(); // => false
validate(null, t.Nil).isValid(); // => true
validate(undefined, t.Nil).isValid(); // => true
// strings
validate(1, t.String).isValid(); // => false
validate('a', t.String).isValid(); // => true
// numbers
validate('a', t.Number).isValid(); // => false
validate(1, t.Number).isValid(); // => true
// booleans
validate(1, t.Boolean).isValid(); // => false
validate(true, t.Boolean).isValid(); // => true
// optional values
validate(null, maybe(t.String)).isValid(); // => true
validate('a', maybe(t.String)).isValid(); // => true
validate(1, maybe(t.String)).isValid(); // => false
// functions
validate(1, t.Function).isValid(); // => false
validate(function () {}, t.Function).isValid(); // => true
// dates
validate(1, t.Date).isValid(); // => false
validate(new Date(), t.Date).isValid(); // => true
// regexps
validate(1, t.RegExp).isValid(); // => false
validate(/^a/, t.RegExp).isValid(); // => true
Refinements
You can express more fine-grained contraints with the refinement
syntax:
// a predicate is a function with signature: (x) -> boolean
var predicate = function (x) { return x >= 0; };
// a positive number
var Positive = t.refinement(t.Number, predicate);
validate(-1, Positive).isValid(); // => false
validate(1, Positive).isValid(); // => true
Objects
Structs
// an object with two numerical properties
var Point = t.struct({
x: t.Number,
y: t.Number
});
validate(null, Point).isValid(); // => false
validate({x: 0}, Point).isValid(); // => false, y is missing
validate({x: 0, y: 'a'}, Point).isValid(); // => false, y is not a number
validate({x: 0, y: 0}, Point).isValid(); // => true
validate({x: 0, y: 0, z: 0}, Point, { strict: true }).isValid(); // => false, no additional properties are allowed
Interfaces
Differences from structs
- also checks prototype keys
var Serializable = t.interface({
serialize: t.Function
});
validate(new Point(...), Serializable).isValid(); // => false
Point.prototype.serialize = function () { ... }
validate(new Point(...), Serializable).isValid(); // => true
Lists and tuples
Lists
// a list of strings
var Words = t.list(t.String);
validate(null, Words).isValid(); // => false
validate(['hello', 1], Words).isValid(); // => false, [1] is not a string
validate(['hello', 'world'], Words).isValid(); // => true
Tuples
// a tuple (width x height)
var Size = t.tuple([Positive, Positive]);
validate([1], Size).isValid(); // => false, height missing
validate([1, -1], Size).isValid(); // => false, bad height
validate([1, 2], Size).isValid(); // => true
Enums
var CssTextAlign = t.enums.of('left right center justify');
validate('bottom', CssTextAlign).isValid(); // => false
validate('left', CssTextAlign).isValid(); // => true
Unions
var CssLineHeight = t.union([t.Number, t.String]);
validate(null, CssLineHeight).isValid(); // => false
validate(1.4, CssLineHeight).isValid(); // => true
validate('1.2em', CssLineHeight).isValid(); // => true
Dicts
// a dictionary of numbers
var Country = t.enums.of(['IT', 'US'], 'Country');
var Warranty = t.dict(Country, t.Number, 'Warranty');
validate(null, Warranty).isValid(); // => false
validate({a: 2}, Warranty).isValid(); // => false, ['a'] is not a Country
validate({US: 2, IT: 'a'}, Warranty).isValid(); // => false, ['IT'] is not a number
validate({US: 2, IT: 1}, Warranty).isValid(); // => true
Intersections
var Min = t.refinement(t.String, function (s) { return s.length > 2; }, 'Min');
var Max = t.refinement(t.String, function (s) { return s.length < 5; }, 'Max');
var MinMax = t.intersection([Min, Max], 'MinMax');
MinMax.is('abc'); // => true
MinMax.is('a'); // => false
MinMax.is('abcde'); // => false
Nested structures
You can validate structures with an arbitrary level of nesting:
var Post = t.struct({
title: t.String,
content: t.String,
tags: Words
});
var mypost = {
title: 'Awesome!',
content: 'You can validate structures with arbitrary level of nesting',
tags: ['validation', 1] // <-- ouch!
};
validate(mypost, Post).isValid(); // => false
validate(mypost, Post).firstError().message; // => 'tags[1] is `1`, should be a `Str`'
Customise error messages
You can customise the validation error message defining a function getValidationErrorMessage(value, path, context)
on the type constructor:
var ShortString = t.refinement(t.String, function (s) {
return s.length < 3;
});
ShortString.getValidationErrorMessage = function (value) {
if (!value) {
return 'Required';
}
if (value.length >= 3) {
return 'Too long my friend';
}
};
validate('abc', ShortString).firstError().message; // => 'Too long my friend'
How to keep DRY?
In order to keep the validation logic in one place, one may define a custom combinator:
function mysubtype(type, getValidationErrorMessage, name) {
var Subtype = t.refinement(type, function (x) {
return !t.String.is(getValidationErrorMessage(x));
}, name);
Subtype.getValidationErrorMessage = getValidationErrorMessage;
return Subtype;
}
var ShortString = mysubtype(t.String, function (s) {
if (!s) {
return 'Required';
}
if (s.length >= 3) {
return 'Too long my friend';
}
});
Use cases
Form validation
Let's design the process for a simple sign in form:
var SignInInfo = t.struct({
username: t.String,
password: t.String
});
// retrieves values from the UI
var formValues = {
username: $('#username').val().trim() || null,
password: $('#password').val().trim() || null
};
// if formValues = {username: null, password: 'password'}
var result = validate(formValues, SignInInfo);
result.isValid(); // => false
result.firstError().message; // => 'Invalid value null supplied to /username: String'
JSON schema
If you don't want to use a JSON Schema validator or it's not applicable, you can just use this lightweight library in a snap. This is the JSON Schema example of http://jsonschemalint.com/
{
"type": "object",
"properties": {
"foo": {
"type": "number"
},
"bar": {
"type": "string",
"enum": [
"a",
"b",
"c"
]
}
}
}
and the equivalent tcomb-validation
counterpart:
var Schema = t.struct({
foo: t.Number,
bar: t.enums.of('a b c')
});
let's validate the example JSON:
var json = {
"foo": "this is a string, not a number",
"bar": "this is a string that isn't allowed"
};
validate(json, Schema).isValid(); // => false
// the returned errors are:
- Invalid value "this is a string, not a number" supplied to /foo: Number
- Invalid value "this is a string that isn't allowed" supplied to /bar: "a" | "b" | "c"
Note: A feature missing in standard JSON Schema is the powerful refinement syntax.
Api reference
ValidationResult
ValidationResult
represents the result of a validation. It containes the following fields:
errors
: a list ofValidationError
if validation failsvalue
: an instance oftype
if validation succeded
// the definition of `ValidationError`
var ValidationError = t.struct({
message: t.String, // a default message for developers
actual: t.Any, // the actual value being validated
expected: t.Function, // the type expected
path: list(t.union([t.String, t.Number])) // the path of the value
}, 'ValidationError');
// the definition of `ValidationResult`
var ValidationResult = t.struct({
errors: list(ValidationError),
value: t.Any
}, 'ValidationResult');
#isValid()
Returns true if there are no errors.
validate('a', t.String).isValid(); // => true
#firstError()
Returns an object that contains an error message or null
if validation succeeded.
validate(1, t.String).firstError().message; // => 'value is `1`, should be a `Str`'
validate(value, type, [options]) -> ValidationResult
value
the value to validatetype
a type defined with the tcomb libraryoptions
(optional) is an object with the following keyspath: Array<string | number>
path prefix for validationcontext: any
passed togetValidationErrorMessage
(useful for i18n)strict: boolean
(defaultfalse
) iftrue
no additional properties are allowed while validating structs
Tests
Run npm test
License
The MIT License (MIT)