A simple yet powerful JavaScript/TypeScript data validator.
Table of Contents
- v1.x doc
- Migration from v1.x to v2.x
- Installation
- Usage
- Key Features
- Kitchen Sink
- Core concepts
- Decoders
This should be installed as one of your project dependencies :
npm -i validatex
or
yarn add validatex
ValidateX aims on providing users with decoders and validators to identify the type of the value and validate the value simultaneously. Along with the decoders and validators that validatex provides, you can create custom decoders/validators.
- Functions as decoders/validators
- Supports Typescript
- Static type inference
- Easy to extend
- Easy to create custom decoders/validators
import v, { Typeof } from 'validatex'
const userSchema = v.array(
v.object({
name: v.string(v.minlenth(1)),
mobile: v.toggle(v.number(v.max(9999999999))),
address: v.partial({
postcode: v.number(),
street1: v.string(v.minlenth(1)),
street2: v.toggle(v.string()),
}),
}),
);
type UserSchema = Typeof<typeof userSchema>
// Runtime type
//type UserSchema = {
// mobile?: number | undefined;
// name: string;
// address: {
// street2: string;
// postcode?: number | undefined;
// street1?: string | undefined;
// };
//}[]
// if a valid data is given to the schema it returns it back
const value1 = [
{
name: 'foo',
mobile: 1234567890,
address: {
postcode: 1,
street1: 'street 1',
street2: 'street 2',
},
},
];
expect(userSchema(value1)).toEqual(value1);
// if an invalid data is given to the schema it throws an error
const value2 = [
{
name: 'foo',
mobile: 1234567890,
address: {
postcode: 1,
street1: 'street 1',
street2: 'street 2',
},
},
{
address: { street1: 'street 1' },
},
];
// the root of the schema is an array, so is the root of the error
const expectedError = JSON.stringify([
{
"index":1,
"error": {
"name":"Expected string but got undefined.",
"address": {
"street2":"Expected string but got undefined."
}
}
}
]);
expect(() => userSchema(userSchema, value2).toThrow(expectedError);
Decoder is a function that parses unkown value, validates it and returns a known value. A decoder has the following signature.
(val: unknown, context?: Context | undefined) => T
Context
type Context = {
key?: string | undefined;
raw?: Record<string, any>;
index?: number | undefined;
schema?: any;
}
const defaultErrorMsg = 'Not a string';
const str = decoder<string, unknown>({
typeGuard: (val: unknown): val is string => typeof val === 'string',
getDefaultErrorMsg: () => defaultErrorMsg,
defaultParser: identity,
});
const nameSchema = str();
expect(nameSchema('foo')).toEqual('foo');
expect(nameSchema.bind(nameSchema, 1)).toThrow(defaultErrorMsg);
const defaultErrorMsg = 'Not a string';
const customErrorMsg = 'It should be a string.';
const customValidationErrorMsg = "must not contain '-'.";
const str = decoder<string, unknown>({
typeGuard: (val: unknown): val is string => typeof val === 'string',
getDefaultErrorMsg: () => defaultErrorMsg,
defaultParser: identity,
});
const alphaNumSchema = str({
parse(val) {
if (typeof val === 'string' || typeof val === 'number') return `${val}`;
return val;
},
validate(val, { key } = { key: undefined }) {
if (val.includes('-')) return `${key} ${customValidationErrorMsg}`;
},
errorMsg: customErrorMsg,
});
expect(alphaNumSchema('valid string')).toEqual('valid string');
// it parses
expect(alphaNumSchema(1)).toEqual('1');
// it respects custom error msg
expect(alphaNumSchema.bind(alphaNumSchema, true)).toThrow(customErrorMsg);
// it respects custom validator
expect(
alphaNumSchema.bind(alphaNumSchema, 'hello-there', { key: 'akey' }),
).toThrow(customValidationErrorMsg);
// it respects getErrorMsg
const schema = str({
getErrorMsg: (val, { key } = { key: undefined }) => {
return `${key} must not be ${val}`;
},
});
expect(schema.bind(schema, 1, { key: 'akey' })).toThrow(
'akey must not be 1',
);
// it accepts an array of validators
const schemaB = str({ validate: [isSingleWord, max5] });
expect(schemaB.bind(schemaB, 'kathmandu')).toThrow(maxError);
expect(schemaB.bind(schemaB, 'new road')).toThrow(singleWordError);
expect(schemaB('abc')).toEqual('abc');
Validator is a function that validates a known value as its name suggests . It returns an error if a value is invalid else returns nothing. A decoder uses 1 or many validators to validate a known value.
(val: T, context?: Context | undefined) => string | Record<string, string> | undefined
import v, { TypeOf } from 'validatex'
const loginSchema = v.object({
username: v.string(),
password: v.string()
})
type LoginSchema = TypeOf<typeof loginSchema>
// type LoginSchema = {
// username: string;
// password: string;
// }
The string decoder parses unknown value, validates it and returns an error or a string value on the basis of the validation.
The signature of string decoder looks like:
(options?: DecoderOption<string>) => (val: unknown, context?: Context | undefined) => string
const name = v.string()
type DecoderOption<T> =
| Validator<T>
| Validator<T>[]
| CustomErrorMsg
| DecoderFullOption<T>;
function alphaNumeric(val: string) {
return /[a-zA-Z][\da-zA-Z]+/.test(val) ? undefined : 'Must be alpha numeric.';
}
const username = v.string(alphaNumeric);
expect(username('user1')).toEqual('user1');
// value validation
expect(() => username('123')).toThrow('Must be alpha numeric.');
const customErrMsg = "This is a custom error message";
const username = v.string(customErrMsg);
expect(username.bind(username, 1)).toThrow(customErrorMsg);
The minlength() validates the minimum length of textual data. It takes two parameters, size and customError.
minLength(size, customError?)
Name | Type | Description |
---|---|---|
size | number | It is the minimum length of textual data. It accepts a number. |
customError | string | It is the customized error. If a customized error isn't passed into the validator then it returns default error message. |
// Invalid value
expect(minlength(5)("suku")).toEqual(`Should be at least 5 characters long`);
// Valid value
expect(minlength(5)("summit")).toEqual(undefined);
// Passing customError
const errorMsg = 'Custom error.';
expect(minlength(6, errorMsg)("suku")).toEqual(errorMsg);
The maxlength() validates the minimum length of textual data. It takes two parameters, size and customError.
maxLength(size, customError?)
Name | Type | Description |
---|---|---|
size | number | It is the maximum length of textual data. It accepts a number. |
// Invalid value
expect(maxlength(3)("suku")).toEqual(`Should be no longer than ${len} characters`.);
// Valid value
expect(maxlength(6)("summit")).toEqual(undefined);
// Passing customError
const errorMsg = 'Custom error.';
expect(maxlength(3, errorMsg)("suku")).toEqual(errorMsg);
The email validator checks if a value is a valid email or not. It only takes customError as a parameter.
maxLength(customError?)
// Invalid value
expect(email()('aninvalid.email')).toEqual('The email is not valid');
// Valid value
expect(email()('someone@somewhere.com')).toEqual(undefined);
// Passing customError
const errorMsg = 'Custom error.';
expect(email(errorMsg)('aninvalid.email')).toEqual(errorMsg);
The length validator validates the exact length of the textual data. The two parameters of this validator are : size and customError.
length(size, customError?)
Name | Type | Description |
---|---|---|
size | number | It represents the exact length of the textual data. |
// Invalid value
const expectedError = `Should be 5 characters long`;
expect(length(5)('1234')).toEqual(expectedError);expect(length(5)('1234')).toEqual(expectedError);
// Valid value
expect(length(1)('1')).toEqual(undefined);
// Passing customError
const errorMsg = 'Custom error.';
expect(length(5, errorMsg)('1')).toEqual(errorMsg);
The pattern validator checks if a value matches a specific pattern. It takes two parameters: regex and customError.
pattern(regex, customError?)
Name | Type | Description |
---|---|---|
regex | regular expression | A pattern that entered data needs to follow. |
// Invalid value
expect(pattern(/^[a-b]/)('z')).toEqual(`The value 'z' doen't match the pattern /^[a-b]/`);
// Valid value
expect(pattern(/^[a-z0-9]*$/)('12ba')).toEqual(undefined);
// Passing customError
const errorMsg = 'Custom error.';
expect(pattern(/^[a-z0-9]*$/, errorMsg)('abc_123')).toEqual(errorMsg);
The number decoder takes an unknown value, validates it and returns an error or a number depending on the outcome of the validation.
(options?: DecoderOption<number>) => (val: unknown, context?: Context | undefined) => number
const age = v.number();
expect(age(20)).toEqual(20);
// validation of value except number
expect(() => age("twenty")).toThrow('Expected number but got string.');
The min validator checks whether a value is greater than the minimum value specified. The two parameters of the validators are: number and customError.
min(number, customError?)
Name | Type | Description |
---|---|---|
number | number | Minimum value that entered data needs to be greater than. |
// Invalid value
expect(min(15)(12)).toEqual(`Value 12 should be greater than 15`);
// Valid value
expect(min(15)(17)).toEqual(undefined);
// Passing customError
const errorMsg = 'Custom error.';
expect(min(6, errorMsg)(5)).toEqual(errorMsg);
The max validator checks whether a value is smaller than the maximum value specified. The two parameters of the validators are: number and customError.
max(number, customError?)
Name | Type | Description |
---|---|---|
number | number | Maximum value that entered data needs to be smaller than. |
// Invalid value
expect(max(12)(15)).toEqual(`Value 15 should be smaller than 12`);
// Valid value
expect(max(6)(4)).toEqual(undefined);
// Passing customError
const errorMsg = 'Custom error.';
expect(max(3, errorMsg)(5)).toEqual(errorMsg);
The boolean decoder takes an unknown value, validates it and returns an error or a number depending on the outcome of the validation.
(options?: DecoderOption<boolean>) => (val: unknown, context?: Context | undefined) => boolean
const married = v.boolean();
expect(married(true)).toEqual(true);
// validation of value except boolean
expect(() => married(1)).toThrow('Expected boolean but got number.');
The isTrue validator checks whether a boolean value is true. The only parameter of the validator is: customError.
isTrue(customError?)
Name | Type | Description |
---|---|---|
customError | string | It is the customized error. If a customized error isn't passed into the validator then it returns default error message. |
// false value
const expectedError = `Expected true but got false`;
expect(isTrue()(false)).toEqual(expectedError);
// true value
expect(isTrue()(true)).toEqual(undefined);
// Passing customError
const errorMsg = `Custom error message`;
expect(isTrue(errorMsg)(false)).toEqual(errorMsg);
The isFalse validator checks whether a boolean value is false. The only parameter of the validator is: customError.
isFalse(customError?)
// true value
const expectedError = `Expected false but got true`;
expect(isFalse()(true)).toEqual(expectedError);
// false value
expect(isFalse()(false)).toEqual(undefined);
// Passing customError
const errorMsg = `Custom error message`;
expect(isFalse(errorMsg)(true)).toEqual(errorMsg);
The date decoder takes an unknown value, validates if it and returns an error or a Date depending on the outcome of the validation.
(options?: DecoderOption<Date>) => (val: unknown, context?: Context | undefined) => Date
If the unknown value is string, it parses the string to a date object. The decoder will return a date object only if the input is a valid date else return an error that says "Expected Date but got object."
const dateOfBirth = v.date();
// Both the "/" or "-" symbols work well
expect(dateOfBirth(new Date("2001/04/05"))).toEqual(new Date("2001/04/05"));
// Parses string to a date object.
expect(dateOfBirth("2001-04-05")).toEqual(new Date("2001-04-05"));
// Invalid date validation
expect(() => dateOfBirth("2001-15-32")).toThrow("Expected Date but got object.");
// Validation of value except Date object and string
expect(() => dateOfBirth(2001)).toThrow("Expected Date but got number.");
The minDate validator set the minimum date and checks whether the entered date comes after the minimum date. It takes two parameters: date and customError.
minDate(date, customError?)
Name | Type | Description |
---|---|---|
date | date object | Minimum date that can be entered. |
// Invalid value
const expectedError = `The entered date must come after Mon Apr 05 2021`;
expect(minDate(new Date("2021/4/5"))(new Date("2021/3/4"))).toEqual(expectedError);
// Valid value
expect(minDate(new Date("2021/4/5"))(new Date("2021/4/6"))).toEqual(undefined);
// Passing customError
const errorMsg = `The date entered doesn't exist.`;
expect(minDate(new Date("2021/4/5"), errorMsg)(new Date("2020/4/6"))).toEqual(errorMsg);
The maxDate validator set the maximum date and checks whether the entered date comes before the maximum date. It takes two parameters: date and customError.
maxDate(date, customError?)
Name | Type | Description |
---|---|---|
date | date object | Maximum date that can be entered. |
// Invalid value
const expectedError = `The entered date must come before Mon Apr 05 2021`;
expect(maxDate(new Date("2021/4/5"))(new Date("2021/6/4"))).toEqual(expectedError);
// Valid value
expect(maxDate(new Date("2021/4/5"))(new Date("2020/5/6"))).toEqual(undefined);
// Passing customError
const errorMsg = `The date entered doesn't exist.`;
expect(maxDate(new Date("2021/4/5"), errorMsg)(new Date("2022/4/6"))).toEqual(errorMsg);
The literal decoder parses an any value, validates if it and returns an error or a Premitive value depending on the outcome of the validation.
The type Premitive looks like :
type Premitive = string | number | boolean | null | undefined;
Signature of literal decoder
(val: T, options?: DecoderOption<Premitive>) => (val: any, context?: Context | undefined) => Premitive
const apple = v.literal('apple');
expect(apple('apple')).toEqual('apple');
expect(() => apple('banana')).toThrow(`Expected 'apple', but got 'banana'.`);
The object decoder parses any value, validates if it and returns an error or a Schema value depending on the outcome of the validation.
Schema
type Schema = Record<string, Decoder | Switch<any>>;
Signature of object decoder
(schema: T, option?: ObjectOption) => (rawData: any, context?: Context) => Schema
const userSchema = v.object({
name: string(),
address: string()
});
const val = { name: 'foo', address: 'bar' };
expect(userSchema(val)).toEqual(val);
const expectedError = JSON.stringify({
name: 'Expected string but got undefined.',
address: 'Expected string but got undefined.',
});
// invalid value
expect(() => userSchema({})).toThrow(expectedError);
// non-object validation
expect(userSchema.bind(userSchema, 1)).toThrow(expectedError)
//unknown field validation
const data = {
username: 'a username',
password: 'a password',
unknownField1: 'unknown field 1',
unknownField2: 'unknown field 2',
};
expect(userSchema.bind(userSchema, data)).toThrow(`{"unknownField1":"Unknown field.","unknownField2":"Unknown field."}`)
(schema: T, option?: PartialOption) => (val: any, context?: Context) => Schema
// making all fields optional
const userSchema = partial({
username: string(),
password: string(),
});
expect(userSchema({})).toEqual({});
// working with toggle
const userSchema = partial({
username: string(),
password: toggle(string()),
});
expect(userSchema.bind(userSchema, {})).toThrow('');
The array decoder parses any value, validates if its value is of type Arrary and returns an error or an Array value depending on the outcome of the validation.
Decoder
type Decoder = (value: any, context?: Context) => any;
Signature of object decoder
(schema: T, option?: ArrayOptions>) => (val: any, context?: Context) => Array
const namesSchema = array(string());
const value = ['foo', 'bar'];
// valid value
expect(namesSchema(value)).toEqual(value);
const value = [1, undefined];
const expectedError = [
{ index: 0, error: 'Expected string but got number.' },
{ index: 1, error: 'Expected string but got undefined.' },
];
// invalid value
expect(namesSchema.bind(namesSchema, value)).toThrow(
JSON.stringify(expectedError),
);
const expectedError = [
{ index: 0, error: 'Expected string but got undefined.' },
];
// Non-array value
expect(namesSchema.bind(namesSchema, undefined)).toThrow(
JSON.stringify(expectedError),
);
const expectedError = [
{ index: 0, error: 'Expected string but got undefined.' },
];
// empty array validation
expect(namesSchema.bind(namesSchema, [])).toThrow(
JSON.stringify(expectedError),
);
// custom validation
const expectedError = `Only 2 items are allowed`;
function allowOnly2(values: string[]) {
if (values.length > 2) return expectedError;
}
const namesSchema = array(string(), allowOnly2);
expect(namesSchema.bind(namesSchema, ['a', 'b', 'c', 'd'])).toThrow(
expectedError,
);
The enum decoder parses any value, validates if its value is of type EnumValues and returns an error or a EnumValues value depending on the outcome of the validation.
type EnumValues = string | number | boolean;
Signature of object decoder
(values: [...T], option?: DecoderOption<T>) => (val: unknown, context?: Context) => EnumValues
const fruits = v.enum(['apple', 'banana']);
// valid value
const apple = 'apple';
expect(fruits(apple)).toEqual(apple);
// invalid value
const expectedError = `Expected one of ['apple', 'banana'] but got 'tomato'.`;
expect(() => fruits('tomato')).toThrow(expectedError);
(schemas: T, option?: DecoderOption<K>) => (val: unknown, context?: Context) => T
const alphaNumeric = union([string(), number()]);
// valid values
expect(alphaNumeric('a')).toEqual('a');
expect(alphaNumeric(1)).toEqual(1);
// invalid value
expect(() => alphaNumeric(true)).toThrow(
'The value did not match any of the given schemas.',
);