composite-validation v1.0.15
composite-validation
Composite validation API for JS data models. Based on this project idea. This library is designed to check fields values in some data model. It supports multiple validity conditions.
Install
npm install composite-validation
Use
For some data model...
const dataModel = {
name: 'Leonardo',
age: 35
};
...describe validation map that folows the structure of the original object using operators.
import { ValidationMap, Conditions, required, equals } from 'composite-validation';
const map = ValidationMap({
name: Conditions(v => required(v)),
age: Conditions([
v => required(v),
v => equals(v, 21)
])
});
Call function.
const result = map(dataModel);
Function applies validation map to data model, checks all validity conditions and returns object with validity states for each field.
{
"name": {
"value": "Leonardo",
"isRequired": true
},
"age": {
"error": "Value should be equal to 21",
"isRequired": true
}
}
Fine-tuning of validation
You can also apply conditions not permanently. If you pass a function to the operator, the condition will be checked only if the function returns true.
const map = ValidationMap({
name: Conditions(v => required(v)),
age: Conditions([
v => equals(v, 21, () => !!v)
])
});
For different datasets our function map() will return different results:
{
name: 'Leonardo',
age: null
};
// ...
{
"name": {
"value": "Leonardo",
"isRequired": true
},
"age": {
"value": null,
"isRequired": false
}
}
{
name: 'Leonardo',
age: 35
};
// ...
{
"name": {
"value": "Leonardo",
"isRequired": true
},
"age": {
"error": "Value should be equal to 21",
"isRequired": false
}
}
And another example:
// map.
ValidationMap({
name: Conditions(v => required(v)),
age: Conditions([
v => required(v),
(v, model) => equals(v, 18, () => model.gender === 'male')
])
});
// data 1.
{
name: 'Leonardo',
age: 35,
gender: 'male'
};
// result 1.
{
"name": {
"value": "Leonardo",
"isRequired": true
},
"age": {
"error": "Value should be equal to 18",
"isRequired": true
}
}
// data 2.
{
name: 'Ellen',
age: 23,
gender: 'female'
};
// result 2.
{
"name": {
"value": "Ellen",
"isRequired": true
},
"age": {
"value": 23,
"isRequired": true
}
}
Get access to other data object levels
As you see in previous example it's possible to get access to the another levels of the data object hierarchy. If you need to go deeper... below is more detailed example.
// data.
{
name: 'Leonardo',
age: 35,
gender: 'male',
character: {
name: 'Cobb',
eyes: 'blue',
family: [
{
relationType: 'wife',
gender: 'female'
},
{
relationType: 'son',
gender: 'male'
}
]
}
};
// map.
ValidationMap({
name: Conditions(v => required(v)),
age: Conditions([
v => required(v),
(v, model) => equals(v, 18, () => model.gender === 'male')
]),
character: ValidationMap({
eyes: Conditions([
(v, char, model) => equals(v, 'blue', () => !!(!char.name.includes('_') && model.gender === 'male')))
]),
// Apply ValidationMap to each element of array.
family: Each(ValidationMap({
relationType: Conditions(v => required(v)),
gender: Conditions(v => equals(v, 'female'))
}))
})
});
// result.
{
"name": {
"value": "Leonardo",
"isRequired": true
},
"age": {
"error": "Value should be equal to 18",
"isRequired": true
},
"character": {
"eyes": {
"value": "blue",
"isRequired": false
},
"family": {
"item0": {
"relationType": {
"value": "wife",
"isRequired": true
},
"gender": {
"value": "female",
"isRequired": false
}
},
"item1": {
"relationType": {
"value": "son",
"isRequired": true
},
"gender": {
"error": "Value should be equal to female",
"isRequired": false
}
}
}
}
}
You can use function Each() to apply ValidationMap or Conditions to each element of array. In addition, you can set custom checks in operator() and use its in Conditions.
// data.
{
arr: [1, 3, 5, 6],
entities: [
{
id: 1,
type: 2,
},
{
id: 2,
type: 1,
},
{
id: 3,
type: 2,
}
]
}
// map.
ValidationMap({
arr: Each(Conditions([
v => operator(v, () => v % 2 === 0)
])),
entities: Each(ValidationMap({
type: Conditions(v => equals(v, 2))
}))
});
// result.
{
"arr": {
"item0": {
"error": "Value is not valid",
"isRequired": false
},
"item1": {
"error": "Value is not valid",
"isRequired": false
},
"item2": {
"error": "Value is not valid",
"isRequired": false
},
"item3": {
"value": 6,
"isRequired": false
}
},
"entities": {
"item0": {
"type": {
"value": 2,
"isRequired": false
}
},
"item1": {
"type": {
"error": "Value should be equal to 2",
"isRequired": false
}
},
"item2": {
"type": {
"value": 2,
"isRequired": false
}
}
}
}
Custom operators
The library contains a limited number of operators, but you can create custom operator functions. They must match the specified signature and return instance of ValueValidationError in invalid case or instance of WrappedValue in other cases.
(data, ...args) => any;
// ...
export function myTypeCheckOperator(value: any) {
if (typeof value !== 'string') {
return new ValueValidationError('Invalid value type');
}
return new WrappedValue(value, false);
}
For convenience, it is possible to use helper functions from Utils class.
export function myTypeCheckOperator(value: any) {
const t = typeof value;
if (t !== 'string') {
return Utils.getErrorObject('Invalid value type: {$0}. Valid type is string.', false, [t]);
}
return Utils.getWrappedValue(value);
}
Custom errors
All validation error messages are reassignable. Call static function CompositeValidation.setErrorMatches() and pass your object with error mapping to set errors match globally. Use {$0}, {$1} tokens to replace them with value passed from inside the operator.
CompositeValidation.setErrorMatches({
equals: 'Not equals!', // default library operator.
})
// or...
CompositeValidation.setErrorMatches({
required: 'This value {$0} is not valid!', // default library operator.
equals: 'Not equals!', // default library operator.
myTypeCheckOperator: 'Invalid type' // your custom operator
})
Then you can call Utils.errorMatch('') inside the operator.
export function myTypeCheckOperator(value: any) {
const t = typeof value;
if (t !== 'string') {
return Utils.getErrorObject(CompositeValidationOptions.errorMatch('myTypeCheckOperator'), false, [t]);
}
return Utils.getWrappedValue(value);
}