felv is a fast easy and light (synchronous and asynchronous) JavaScript validator.
- Take only 2 minutes to know if felv is made for you.
- Take only 5 minutes to try it.
- Take only 15 minutes to read this page and learn everything you need to know!
Run the following command to add the package to your dev dependencies:
$ npm install --save felv
const felv = require('felv');
const schema = {
// Value of 'a' key is required and must be a number.
a: {
required: true,
type: 'number'
},
// Value of 'b' key is an array of numbers and strings.
b: {
type: Array,
items: {
type: ['string', 'number']
}
},
// Value of 'c' key is an instance of Object
// with properties i and j which are booleans
// of default value `true`.
c: {
type: Object,
properties: {
i: {
default: true,
type: 'boolean'
},
j: {
default: true,
type: 'boolean'
}
}
},
// Value of 'd' key is an instance of Object
// with properties of variable names and string values.
d: {
type: Object,
items: {
type: 'string'
}
}
};
const validatedValue = felv.validate(
{
a: 1,
b: [2, 'foo', 4],
c: {
i: false
},
d: {
x: 'foo',
y: 'bar'
}
},
schema
);
validatedValue
is equal to:
{
a: 1,
b: [2, 'foo', 4],
c: {
i: false,
j: true
},
d: {
x: 'foo',
y: 'bar'
}
}
You can easily validate a value from a schema:
const validatedValue = felv.validate(value, schema, options);
For repetitive validations, you should compile a validator to optimize validation performances. This can also be useful to detect schema format errors before validation occurs.
const validator = felv.compile(schema, options);
const validatedValue = validator.validate(value);
A schema is used to describe the format of a value. Each attribute of a schema is called a validation processor. A schema can embed other schemas to describe embedded structures thanks to some validation processors.
You can check the type of the value of a key:
{
foo: {
type: 'number'
},
bar: {
type: Date
},
foobar: {
type: ['number', 'string'] // number or string items
}
}
Matching values:
{
foo: 2,
bar: new Date(),
foobar: 3
}
{
foo: 2,
bar: new Date(),
foobar: 'foo'
}
You can specify a default value:
{
foo: {
default: 3,
type: 'number'
},
bar: {
default: 6
}
}
Matching value:
{
foo: 2
}
Validated value:
{
foo: 2,
bar: 6
}
You can specify a required value:
{
foo: {
required: true,
type: 'number'
},
bar: {
type: 'number'
}
}
Matching value:
{
foo: 2
}
You can specify embedded items of arrays and objects:
{
foo: {
type: Array,
items: {
type: 'number'
}
},
bar: {
type: Object,
items: {
type: 'number'
}
}
}
Matching value:
{
foo: [2, 3, 7],
bar: {a: 1, b: 5}
}
You can specify properties of objects:
{
foo: {
type: Object,
properties: {
a: {
required: true,
type: 'number'
},
b: {
default: 'bar',
type: 'string'
}
}
}
}
Matching value:
{
foo: {a: 4}
}
Validated value:
{
foo: {a: 4, b: 'bar'}
}
You can give a function to format input values:
{
foo: {
format: value => (Array.isArray(value) ? value : [value]),
type: Array,
items: {
type: 'string'
}
}
}
Matching value:
{
foo: 'bar'
}
Validated value:
{
foo: ['bar']
}
You can give a function to make custom validations and format output values:
{
foo: {
type: 'number',
validate: (value, expected) => {
if (value < 1 ||Â value > 9) {
// Throw a validation error.
expected('a number between 1 and 9');
}
return value;
}
},
bar: {
type: 'number',
// Alter validated value.
validate: value => (value > 1 ? value * 10 : value)
}
}
Matching value:
{
foo: 4,
bar: 2
}
Validated value:
{
foo: 4,
bar: 20
}
You can specify a custom error message that will be set on error occurrence:
const schema = {
foo: {
required: true,
type: 'number',
error: 'You must specify a number for "foo"'
}
};
try {
felv.validate({foo: 'bar'}, schema);
} catch(error) {
console.log(error.customMessage); // Display `You must specify a number for "foo"`.
}
You can specify different validation ways givin an array of schemas:
{
foo: [
{
required: true,
type: 'number'
},
{
default: 'bar',
type: 'string'
}
]
}
Matching values:
{foo: 2}
{foo: 'foo'}
{}
Corresponding validated values:
{foo: 2}
{foo: 'foo'}
{foo: 'bar'}
You can use asynchronous functions in validation processors accepting a function (like format
and validate
) or directly return a Promise
. In that case, a Promise
will be returned by the validate
method.
Example:
const asyncSchema = {
foo: {
validate: async (value) => {
const validatedValue = await asyncValidationFunction(value);
return validatedValue + 1;
}
},
bar: {
format: (value) => {
return new Promise((resolve, reject) => {
asyncFormattingFunction(formattedValue => resolve(formattedValue));
});
}
}
};
const validatedValue = await felv.validate({foo: 3, bar: 4}, asyncSchema);
For simplicity, validation processors are independent from each other.
Validation processors are processed in a specific order:
- format
- default
- required
- type
- items
- properties
- validate
- error
You can modulate validation with following options:
- async
- convert
- formatting
- full
- immutable
- list
- namespace
- required
- validation
Whether or not to force a Promise
result even on synchronous return.
Default value:
{
async: false
}
Whether or not to automatically convert some value types.
Default value:
{
convert: true
}
string
=>boolean
:"false"
=>false
"true"
=>true
"0"
=>false
"1"
=>true
number
=>boolean
:0
=>false
!== 0
=>true
string
=>number
:"100"
=>100
boolean
=>number
:false
=>0
true
=>1
A validation object to be passed as second argument (options
) in format
validation processors:
{
foo: {
format: (value, options) => {
// Format value from options.
}
}
}
Default value:
{
formatting: {}
}
Whether or not to process full validation event after a first error occurred.
Default value:
{
full: false
}
Whether or not to use the original value for validated value. This increase performances but must not be used for cases where the original value must be preserved.
Default value:
{
immutable: false
}
Whether or not to process schema on a list of items (array or object) instead of an object with defined properties.
Default value:
{
list: false
}
A namespace to prefix paths in error messages.
Default value:
{
namespace: '$'
}
Whether or not fields are required by default.
Default value:
{
required: false
}
A validation object to be passed as third argument (options
) in validate
validation processors:
{
foo: {
validate: (value, expected, options) => {
// Validate 'value' from 'options'.
// Throw validation error with 'expected'.
}
}
}
expected
function take 3 arguments:
{string} expectedType
: Expected type (or value description){Array.<*>} [expectedValues]
: Optional expected values{string} [customMessage]
: Optional custom error message
Default value:
{
validation: {}
}
const validator = felv.compile({foo: {type: 'string'}});
try {
const validatedValue = validator.validate({foo: 3});
} catch (error) {
// Error type: 'ValidationError'
console.log(error.name);
// Explicit message: '[$.foo](type) Expected value to be a string, got a number of value `3` instead'
console.log(error.message);
// Path of the failing value: '$.foo'
console.log(error.path);
// Failing schema attribute/validation processor: 'type'
console.log(error.attribute);
// Subject of failure: 'value'
console.log(error.subject);
// Expected type: 'string'
console.log(error.expectedType);
// Expected values: []
console.log(error.expectedValues);
// Got(gotten) type: 'number'
console.log(error.gotValue);
// Got(gotten) value: 3
console.log(error.gotValue);
// Custom message (not defined here): ''
console.log(error.customMessage);
}
You can process full validation to retrieve all validation errors of a value against a schema. This can be useful to validate a user form and return all errors for instance:
// Compile form schema at program initialization.
const schema = {
firstname: {
required: true,
type: 'string',
// Define custom error message of occuring validation error.
error: 'Firstname must be filled'
},
lastname: {
required: true,
type: 'string'
},
email: {
required: true,
type: 'string',
validate: (value, expected) => {
if (!/@/.test(value)) {
// Define custom error message with third argument
// of expected function.
expected('email', null, 'Email is incorrect');
}
return value;
},
// `error` attribute does not overwrite already defined custom message
// like 'Email is incorrect' in above validate attribute function.
error: 'Email must be filled'
},
password: {
required: true,
type: 'string',
validate: (value, expected) => {
if (value.length < 8) {
expected('a string of at least 8 character');
} else if (!/[A-Z]/.test(value)) {
expected('a string with at least one uppercase letter');
}
return value;
},
error: 'Password must at least contains 8 characters with at least one uppercase letter'
}
};
const options = {
namespace: 'form',
full: true
};
const validator = felv.compile(schema, options);
// Use compiled validator to validate user input on user subscription.
// We assume `req.body` equals `{email: 'dumb', password: 'dumb'}`
try {
const validatedValue = validator.validate({
firstname: req.body.firstname,
lastname: req.body.lastname,
email: req.body.email,
password: req.body.email
});
// Do stuff like persisting user in database...
} catch (error) {
// Error type: 'FullValidationError'
console.log(error.name);
// Use `error.pathErrorMessages` to get validation error messages associated with failing paths.
assert.deepEqual(error.pathErrorMessages, {
'form.firstname': 'Firstname must be filled',
'form.lastname': '[form.lastname](required) Expected value to be a defined value, got `undefined` instead',
'form.email': 'Email is incorrect',
'form.password': 'Password must at least contains 8 characters with at least one uppercase letter'
});
// Use `error.pathErrors` to get the validation errors instead of messages.
// Use `error.errorMessages` to get validation error message list.
assert.deepEqual(error.errorMessages, [
'Email is incorrect',
'Firstname must be filled',
'Password must at least contains 8 characters with at least one uppercase letter',
'[form.lastname](required) Expected value to be a defined value, got `undefined` instead'
]);
// Use `error.errors` to get the validation errors instead of messages.
}
Many npm
scripts are available to help testing:
$ npm run {script}
check
: lint and check unit and integration testslint
: linttest
: check unit teststest-coverage
: check coverage of unit teststest-debug
: debug unit teststest-integration
: check integration teststest-performance
: check performance (just for fun)test-watch
: work in TDD!
Use npm run check
to check that everything is ok.
If you want to contribute, just fork this repository and make a pull request!
Your development must respect these rules:
- fast
- easy
- light
You must keep test coverage at 100%.