A Typescript-based validation library with an extensible, fluent API and human-readable error messages.
Similar to and somewhat inspired by v8n, but addresses some limitations of that library:
- Full Typescript support (v8n has no typings)
- Error messages describing why validation failed (in English but localizable)
- Easy to differentiate between null and undefined for optional values
- Fluent validation of object keys and values
npm install tsfv
import tsfv from 'tsfv';
const words = 'HELLO WORLD';
tsfv
.length(1, 64)
.pattern(/([A-Z]+\s*)+/)
.orUndefined()
.check(words, 'words');
const objv = tsfv
.keys(tsfv.every(tsfv.length(2, 8)).some(tsfv.exact('id')))
.values(tsfv.every(tsfv.anyOf(tsfv.integer(), tsfv.string())));
objv.check({ id: 42, foo: 'bar' });
objv.testAll({ x: Math.PI }).forEach(err => console.log(err.message));
// Expected object with keys array with every element length between 2 and 8 and array with some element value exactly equal to "id"
// Expected object with values array with every element (integer or string)
const aoav = tsfv.every(
tsfv.properties({
ids: tsfv.every(tsfv.string().length(1, 10)).minLength(1)
})
);
aoav.check([{ ids: ['2'] }, { ids: ['1', '99'], extra: 'stuff' }]);
aoav.testAll([{ ids: [] }, { ids: [''] }]).forEach(err => console.log(err.message));
// Expected array with every element object with properties ids (array with every element string and length between 1 and 10 and minimum length of 1)
const div3 = tsfv.numeric().predicate(v => v % 3 === 0, 'divisible by 3');
div3.check(9);
div3.check('9');
div3.testAll(Math.PI).forEach(err => console.log(err.message));
// Expected divisible by 3
div3.testAll(words).forEach(err => console.log(err.message));
// Expected numeric
// Expected divisible by 3
New validation builder methods can be added to the API using Typescript Module Augmentation:
// uppercase.ts
import { Validation } from 'tsfv';
// augment appropriate interface for type in fluent API
declare module 'tsfv/dist/api' {
export interface StringValidationReturning<V> {
uppercase(): V;
}
}
// augment Validation class
declare module 'tsfv' {
export interface Validation {
uppercase(): Validation;
}
}
// add method to Validation class
Validation.prototype.uppercase = function(this: Validation): Validation {
return this.withRule({
id: 'uppercase',
describe: () => 'uppercase string',
test: v => /^[A-Z]+$/.test(v)
});
};
Use the extension by importing its module, in addition to tsfv
:
import tsfv from 'tsfv';
import './uppercase';
tsfv.uppercase().check('ABC');
tsfv.uppercase().testAll('abc').forEach(err => console.log(err.message));
// Expected uppercase string
The core library always returns error messages in English. However, validation errors and rules preserve enough information to localize error messages into other languages:
import tsfv, { ValidationError } from 'tsfv';
import { RuleInstance } from 'tsfv/dist/api';
function describeAufDeutsch(rule: RuleInstance): string {
switch (rule.id) {
case 'length':
return `Länge zwischen ${rule.min} und ${rule.max}`;
case 'pattern':
return `eine Zeichenfolge die zu ${rule.regex} passt`;
// ...
default:
throw new Error(`Unbekannte Regel: ${rule.id}`);
}
}
function errorAufDeutsch(error: ValidationError): string {
let message = `Erwartet ${describeAufDeutsch(error.rule)}`;
if (error.variable) {
message += ` fĂĽr "${error.variable}"`;
}
return message;
}
tsfv
.length(1, 64)
.pattern(/([A-Z]+\s*)+/)
.orUndefined()
.testAll(null, 'words')
.map(err => console.log(errorAufDeutsch(err)));
// Erwartet eine Länge zwischen 1 und 64 für "words"
// Erwartet eine Zeichenfolge die zu /([A-Z]+\s*)+/ passt fĂĽr "words"
The entire API is exposed via the default export of the library, generally imported as tsfv
.
Note that all of the methods used to configure a validator are chainable. The return types shown below are simplified and illustrative; refer to the Typescript definition files for actual return types.
The following methods are used to perform validation once the validator object has been built by calling builder methods.
check(value: any, name?: string): void
Validates the given value and throws a ValidationError
if it is invalid.
An optional variable name can be provided that will be included in any error message.
Example:
tsfv.string().check('x'); // returns
tsfv.not.string().check('x'); // throws ValidationError
test(value: any): boolean
Tests whether the given value is valid according to the rules checked by this validator.
Example:
tsfv.string().test('x'); // true
tsfv.not.string().test('x'); // false
testAll(value: any, name?: string): ValidationError[]
Validates the given value and returns an array of ValidationError
containing any and all failing rules.
An optional variable name can be provided that will be included in any error message.
Example:
tsfv.numeric().predicate(v => v % 3 === 0, 'divisible by 3').testAll(null).forEach(err => console.log(err.message));
// Expected numeric
// Expected divisible by 3
describe(): string
Returns an English description of the rules checked by this validator.
Example:
tsfv.numeric().predicate(v => v % 3 === 0, 'divisible by 3').describe(); // numeric and divisible by 3
Modifiers change the behavior of the immediately following rule. They are implemented as property getters, so they are not followed by parentheses.
readonly not: Omit<InvertedValidation<this>, 'not'>
The not
modifier inverts/negates the logic of the immediately following rule.
It also disables any type-narrowing of the return type of that rule, essentially causing it to return a
validator of type this
instead of a type-specific validator (such as NumericValidation
or StringValidation
).
Double inversion/negation is not allowed by the type system.
Example:
tsfv.string().test('x'); // true
tsfv.not.string().test('x'); // false
The following methods validate the general type of a value.
array(): ArrayValidation
Returns a new validator that checks that the validated value is an array.
Assuming the method call was not preceded by not
, the interface of the returned validator
will contain only validation builders that apply to arrays.
Example:
tsfv.array().every(tsfv.positive()).test([1, 2, 3]); // true
boolean(): AnyValidation
Returns a new validator that checks that the validated value is a boolean value (true
or false
).
Example:
tsfv.boolean().test(false); // true
tsfv.boolean().test(1); // false
integer(): NumericValidation
Returns a new validator that checks that the validated value is an integer.
Assuming the method call was not preceded by not
, the interface of the returned validator
will contain only validation builders that apply to numbers.
Example:
tsfv.integer().test(42); // true
tsfv.integer().test(1.1); // false
null(): AnyValidation
Returns a new validator that checks that the validated value is null
.
Example:
tsfv.null().test(null); // true
tsfv.null().test(undefined); // false
number(): NumericValidation
Returns a new validator that checks that the validated value is a number
(including NaN
).
Assuming the method call was not preceded by not
, the interface of the returned validator
will contain only validation builders that apply to numbers.
Example:
tsfv.number().test(42); // true
tsfv.number().test(Infinity); // true
tsfv.number().test(NaN); // true
tsfv.number().test('42'); // false
numeric(): NumericValidation
Returns a new validator that checks that the validated value is numeric (including numeric strings
and Number
but excluding NaN
).
Assuming the method call was not preceded by not
, the interface of the returned validator
will contain only validation builders that apply to numbers.
Example:
tsfv.numeric().test(42); // true
tsfv.numeric().test(Infinity); // true
tsfv.numeric().test(NaN); // false
tsfv.numeric().test('42'); // true
object(): ObjectValidation
Returns a new validator that checks that the validated value is an object (not including arrays).
Assuming the method call was not preceded by not
, the interface of the returned validator
will contain only validation builders that apply to objects.
Example:
tsfv.object().test({}); // true
tsfv.object().test(new Date()); // true
tsfv.object().test([]); // false
tsfv.object().test('42'); // false
string(): StringValidation
Returns a new validator that checks that the validated value is a string.
Assuming the method call was not preceded by not
, the interface of the returned validator
will contain only validation builders that apply to strings.
Example:
tsfv.string().test('42'); // true
tsfv.string().test(42); // false
undefined(): AnyValidation
Returns a new validator that checks that the validated value is undefined
.
Example:
tsfv.undefined().test(undefined); // true
tsfv.undefined().test(null); // false
The following methods apply to validating values of any type.
anyOf(...validators: Validator[]): AnyValidation
Checks that at least one of the given validators passes, essentially providing an or
operator.
If no validators are provided, no values are considered valid.
Example:
tsfv.anyOf(tsfv.number(), tsfv.string()).test(42); // true
tsfv.anyOf(tsfv.number(), tsfv.string()).test('hello'); // true
tsfv.anyOf(tsfv.number(), tsfv.string()).test(null); // false
equal(value: any): AnyValidation
Returns a new validator that checks that the validated value loosely equals (using ==
) the given value.
Example:
tsfv.equal(null).test(undefined); // true
tsfv.equal(42).test('42'); // true
tsfv.equal('hello').test({ toString: () => 'hello' }); // true
tsfv.equal(NaN).test(NaN); // false
exact(value: any): AnyValidation
Returns a new validator that checks that the validated value strictly equals (using ===
) the given value.
Example:
tsfv.exact(null).test(null); // true
tsfv.exact(null).test(undefined); // false
tsfv.exact(42).test(42); // true
tsfv.exact(42).test('42'); // false
tsfv.exact(NaN).test(NaN); // false
predicate(test: Predicate, description: string): AnyValidation
Returns a new validator that checks that the validated value against the given predicate function. The given description string is used in error messages for invalid values.
Example:
tsfv.numeric().predicate(v => v % 3 === 0, 'divisible by 3').test(9); // true
The following methods apply to validating numeric values, which include number
, strings parsable as numbers, and Number
objects.
between(min: number, max: number): NumericValidation
Returns a new validator that checks that the validated value is numeric and within the given range, inclusive.
Throws an Error
if min > max
.
Example:
tsfv.between(1, 10).test(5); // true
tsfv.between(1, 10).test('5'); // true
tsfv.between(1, 10).test(0); // false
greaterThan(bound: number): NumericValidation
Returns a new validator that checks that the validated value is numeric and greater than the given minimum.
Example:
tsfv.greaterThan(1).test(2); // true
tsfv.greaterThan(1).test('2'); // true
tsfv.greaterThan(1).test(1); // false
greaterThanOrEqual(bound: number): NumericValidation
Returns a new validator that checks that the validated value is numeric and greater than or equal to the given minimum.
Example:
tsfv.greaterThanOrEqual(1).test(2); // true
tsfv.greaterThanOrEqual(1).test('1'); // true
tsfv.greaterThanOrEqual(1).test(0); // false
lessThan(bound: number): NumericValidation
Returns a new validator that checks that the validated value is numeric and less than the given minimum.
Example:
tsfv.lessThan(1).test(0); // true
tsfv.lessThan(1).test('0'); // true
tsfv.lessThan(1).test(1); // false
lessThanOrEqual(bound: number): NumericValidation
Returns a new validator that checks that the validated value is numeric and less than or equal to the given minimum.
Example:
tsfv.lessThanOrEqual(1).test(0); // true
tsfv.lessThanOrEqual(1).test('1'); // true
tsfv.lessThanOrEqual(1).test(2); // false
positive(): NumericValidation
Returns a new validator that checks that the validated value is numeric and positive (> 0
).
Example:
tsfv.positive().test(1); // true
tsfv.positive().test('1'); // true
tsfv.positive().test(0); // false
negative(): NumericValidation
Returns a new validator that checks that the validated value is numeric and negative (< 0
).
Example:
tsfv.negative().test(-1); // true
tsfv.negative().test('-1'); // true
tsfv.negative().test(0); // false
The following methods apply to validating the length of strings and arrays.
length(min: number, max?: number): LengthValidation
Checks that the length of the validated value is within the given range, inclusive.
If max
is omitted, it is assumed to equal min
.
Throws an Error
if min > max
.
Example:
tsfv.length(1).test([1]); // true
tsfv.length(1, 3).test([]); // false
tsfv.length(1).test('a'); // true
tsfv.length(1, 3).test(''); // false
minLength(min: number): LengthValidation
Checks that the length of the validated value is greater than or equal to the given minimum.
Example:
tsfv.minLength(1).test([1]); // true
tsfv.minLength(1).test([]); // false
tsfv.minLength(1).test('a'); // true
tsfv.minLength(1).test(''); // false
maxLength(max: number): LengthValidation
Checks that the length of the validated value is less than or equal to the given maximum.
Example:
tsfv.maxLength(1).test([1]); // true
tsfv.maxLength(1).test([1, 2]); // false
tsfv.maxLength(1).test('a'); // true
tsfv.maxLength(1).test('ab'); // false
empty(): LengthValidation
Returns a new validator that checks that the validated value is an empty string or array.
Example:
tsfv.empty().test([]); // true
tsfv.empty().test([1]); // false
tsfv.empty().test(''); // true
tsfv.empty().test('a'); // false
The following methods apply to validating strings.
pattern(regex: RegExp): StringValidation
Returns a new validator that checks that the validated value is a string matching the given regular expression.
Example:
tsfv.pattern(/([A-Z]+\s*)+/).test('HELLO WORLD'); // true
tsfv.pattern(/([A-Z]+\s*)+/).test('hello world'); // false
contains(str: string): StringValidation
Returns a new validator that checks that the validated value is a string containing the given substring.
Example:
tsfv.contains('ell').test('hello'); // true
startsWith(str: string): StringValidation
Returns a new validator that checks that the validated value is a string starting with the given substring.
Example:
tsfv.startsWith('hell').test('hello'); // true
endsWith(str: string): StringValidation
Returns a new validator that checks that the validated value is a string ending with the given substring.
Example:
tsfv.endsWith('ello').test('hello'); // true
The following methods apply to validating arrays.
includes(value: any): ArrayValidation
Returns a new validator that checks that the validated value is an array containing the given element.
Example:
tsfv.includes(2).test([1, 2, 3]); // true
every(elementValidator: Validator): ArrayValidation
Returns a new validator that checks that the validated value is an array for which every element passes the given validator.
Example:
tsfv.every(tsfv.positive()).test([1, 2, 3]); // true
some(elementValidator: Validator): ArrayValidation
Returns a new validator that checks that the validated value is an array for which at least one element passes the given validator.
Example:
tsfv.some(tsfv.positive()).test([-1, 2, -3]); // true
The following methods apply to validating objects.
instanceOf(ctor: Function): ObjectValidation
Returns a new validator that checks that the validated value is an object that is an instance of the given class (or a subclass).
Example:
tsfv.instanceOf(Object).test({}); // true
tsfv.instanceOf(Function).test(() => 0); // true
keys(arrayValidator: Validator): ObjectValidation
Returns a new validator that checks that the validated value is an object for which the keys pass the given validator.
Example:
tsfv.keys(tsfv.length(1)).test({ id: 1 }); // true
tsfv.keys(tsfv.every(tsfv.length(1))).test({ a: 1, b: 2 }); // true
values(arrayValidator: Validator): ObjectValidation
Returns a new validator that checks that the validated value is an object for which the values pass the given validator.
Example:
tsfv.values(tsfv.every(tsfv.numeric())).test({ a: 1, b: '2' }); // true
properties<T>(propertyValidator: { [P in keyof T]: Validator }, only = false): ObjectValidation
Returns a new validator that checks that the validated value is an object with properties that pass the given validator.
If only
is true, additional, unvalidated properties will fail validation; otherwise, they are ignored.
Example:
tsfv.properties({ a: tsfv.number(), b: tsfv.string() }).test({ a: 1, b: '2' }); // true
tsfv.properties({ a: tsfv.number() }, true).test({ a: 1, b: '2' }); // false
The following methods build a validator that accepts null
and/or undefined
, in addition to valid values.
optional(): this
Returns a new validator that allows null
or undefined
, in addition to valid values.
Example:
tsfv.string().optional().test('hello'); // true
tsfv.string().optional().test(null); // true
tsfv.string().optional().test(undefined); // true
orNull(): this
Returns a new validator that allows null
, in addition to valid values.
Example:
tsfv.string().orNull().test('hello'); // true
tsfv.string().orNull().test(null); // true
tsfv.string().orNull().test(undefined); // false
orUndefined(): this
Returns a new validator that allows undefined
, in addition to valid values.
Example:
tsfv.string().orUndefined().test('hello'); // true
tsfv.string().orUndefined().test(undefined); // true
tsfv.string().orUndefined().test(null); // false
tsfv
is available under the ISC license.