expectations-js
A developer-friendly, no-dependency Typescript data-validation framework
Installation
npm install expectations-js
What does it do?
When dealing with user input, you cannot rely on the data to be valid.
This library allows you to easily validate data, and get a list of errors if the data is invalid.
You just need to define a set of expectations to which your data should conform.
If it does not, you will get a list of errors, which you can then use to display to the user.
This is particularly suited to form validation, but it can be used for any kind of data validation. (e.g. API responses)
Usage
To get started, you can import these functions:
import { when, expect, validate, isValid } from '@fosco110/expectations-js';
You will then need to create an array, in which you can put your validation rules.
const expectations = [
expect('name').toMatch(/^[a-zA-Z]+$/),
when('age').isLessThan(18).expect('name').not.toEqual('John'),
]
To run the validation, you can use the validate
function.
const data = {
name: 'John',
age: 17,
}
const res = validate(data, expectations);
If you need to check whether the data is valid, you can use the isValid
function.
const valid = isValid(data, expectations);
API
The expect
function takes a key, and creates a Expectation
object.
You can then chain methods to it, to create a validation rule.
expect('name').toMatch(/^[a-zA-Z]+$/)
By default, when you expect
a key, it will be considered required. If you want to allow it to be undefined, you can use the notRequired
method.
expect('name').notRequired().toMatch(/^[a-zA-Z]+$/)
If you need to run an inverse check, just use not
.
expect('name').not.toMatch(/^[a-zA-Z]+$/)
If you need to perform a validation on multiple items (e.g. an array), you can use the each
method.
expect('names').each().toMatch(/^[a-zA-Z]+$/)
For more complex operations (e.g. two values dependent on eachother), use the when
method.
You can then
expect a key to be a certain value, or then
expect some more expectations.
when('age').isLessThan(18).then(expect('name').not.toBe('John'))
when('age').isLessThan(18).then([
expect('name').not.toBe('John'),
expect('surname').not.toBe('Doe'),
])
You can chain these conditions with the and
and or
methods.
when('age').isLessThan(18).and('parentConsent').is(true).then(expect('name').not.toBe('Mike')) // Mike is a bad kid, he can't ever use this service
when('age').isLessThan(18).or('parentConsent').is(false).then(expect('name').toBe('John')) // John is special, he doesn't need his parents' consent
You can also chain the onConditionsMet
, onConditionsNotMet
, onExpectationsMet
, onExpectationsNotMet
methods to set custom error messages.
when('age').isLessThan(18).and('parentConsent').is(false).onConditionsNotMet('parentConsent', "You must have your parent's consent to use this service")
when('age').isLessThan(18)
If you put a %key%
in the error message, it will be replaced with the value of the key.
If you need it capitalized, just use %key.capitalize%
.
expect('age').toBeLessThan(18).ifNot('%key.capitalize% must be 18 or older') // Age must be 18 or older
In case of more complex validation operations (e.g. an object, an object inside an array, an object inside another object), you can use SubExpectations.
const data = {
user: {
name: 'John',
age: 17,
},
};
const expectations = [
expect('user').toSatisfy([
expect('name').toMatch(/^[a-zA-Z]+$/),
expect('age').toBeGreaterThan(18),
])
];
const res = validate(data, expectations);
/**
* res = {
* user: {
* age: 'age must be greater than 18',
* },
* }
*/
You can chain these conditions with the and
and or
methods, and use the error
method to set a custom error message.
when('age').isLessThan(18).and('parentConsent').is(false).error('parentConsent', "You must have your parent's consent to use this service")
Interpreting return values
The validate
function returns an object, whose keys are set when a validation fails.
If the key corresponds to an array, it will by default create an empty array. Check for failures in the array, to see what went wrong.
If it corresponds to an object, it will create an empty object. Again, check for failures in the object, to see what went wrong.
Example
const data = {
name: 'John',
age: 17,
agreeToTerms: false,
}
const expectations = [
expect('name').toMatch(/^[a-zA-Z]+$/),
when('age').isLessThan(18).then(
expect('name').not.toEqual('John').ifNot("Sorry John, you're too young")
),
expect('agreeToTerms').toBe(true).ifNot('You must agree to the terms'),
]
const res = validate(data, expectations);
In this example, the res
object will look like this:
{
name: "Sorry John, you're too young",
agreeToTerms: 'You must agree to the terms',
}
This means that the validation failed for the fields name
and agreeToTerms
.
NOTE: The
age
field is not present in theres
object, because the validation passed for that field. If you wanted to check for the user to be at least 18, you could put this expectation:expect('age').toBeGreaterThan(17).ifNot("You must be 18 or older to use this service")or
expect('age').not.toBeLessThan(18).ifNot("You must be 18 or older to use this service")
Debugging
If you need to debug your expectations, you can chain the debug
method to any expectation.
expect('name').debug().toMatch(/^[a-zA-Z]+$/)
You can chain this anywhere, on the
when
function too.when('age').debug().isLessThan(18).expect('name').not.toEqual('John')