JSValid is a functional approach to validation in JavaScript. It provides a minimal, composable and extensible way to express and enforce value constraints.
JSValid specifies the signature of a special kind of function, called a validator. Validators accept as a single argument the subject, which is the value to be validated, and return an array of violations. An empty array indicates a valid subject.
A violation is an object with the following properties, of which only message
is mandatory:
message
: A string describing the violation.path
: An array of keys describing where the violation occurred (for example,["people", 0]
).code
: A string uniquely identifying the type of violation.a
: Exhibit A.b
: Exhibit B.
A factory is any function that returns a validator function. Here is a demonstration showing how factories, validators and violations work together.
// A validator is made by invoking a factory.
const my_validator = valid.object({
question: valid.string(),
answer: valid.integer()
});
// Invoking the validator with a subject returns an array.
const my_violations_array = my_validator({
question: "What is the answer to life, the universe and everything?",
answer: "Love."
});
// The array may be inspected for violations.
// [
// {
// message: "Not of type integer.",
// path: ["answer"]
// code: "not_type_a",
// a: "integer"
// }
// ]
An object containing several factory functions is exported by jsvalid.js:
import valid from "./jsvalid.js";
const {
boolean,
number,
integer,
string,
function,
array,
object,
wun_of,
all_of,
not,
literal,
any
} = valid;
Incidentally, these factories complement the specifiers provided by JSCheck, a testing tool written by Douglas Crockford.
The boolean validator permits only true
and false
.
The number validator permits only numbers that satisfy Number.isFinite
. This excludes Infinity
and NaN
.
Specifying either of minimum or maximum imposes bounds on the subject. Bounds are inclusive unless either of exclude_minimum or exclude_maximum are true.
function valid_latitude() {
return valid.number(-90, 90);
}
function valid_longitude() {
return valid.number(-180, 180, false, true);
}
function valid_weight() {
return valid.number(0);
}
The integer validator permits only numbers that satisfy Number.isSafeInteger
.
Specifying either of minimum or maximum imposes inclusive bounds on the subject.
The string validator permits only strings.
The subject must conform to the regular_expression.
function valid_tracking_number() {
return valid.string(/^[0-9]{24}$/);
}
The length of the subject must conform to the length_validator.
function valid_note() {
return valid.string(valid.integer(1, 140));
}
The function validator permits only functions. The arity of the subject must conform to the length_validator, if it is specified.
The array validator permits only arrays, as determined by Array.isArray
.
Each element in the subject must conform to the validator. The length of the array must conform to the length_validator, if it is specified.
Each element in the subject must conform to the validator at the corresponding position in the validator_array. If the length_validator is omitted, the subject must be the same length as the validator_array.
function valid_location() {
return valid.array([valid_longitude(), valid_latitude()]);
}
function valid_mail_journey() {
return valid.array(valid_location(), valid.integer(1));
}
It is possible that the length_validator may permit a subject longer than the validator_array. In such a case, each surplus element of the subject must conform to the rest_validator. If the rest_validator is undefined, the sequence of surplus elements must conform to the sequence of validators formed by repeating the validator_array.
The object validator permits only bona fide objects, not null
or arrays.
The required_properties and optional_properties parameters are objects containing validators. Either parameter may be undefined.
The value of each property on the subject must conform to the corresponding validator in required_properties or optional_properties. Where no corresponding validator is found, the property is permitted only if allow_strays is true
. Additionally, the subject must contain every key found on required_properties.
function valid_parcel() {
return valid.object(
{
id: valid_tracking_number(),
size: "parcel",
weight: valid_weight()
},
{
delivery_advice: valid_note()
}
);
}
Each key found on the subject by Object.keys
must conform to the key_validator. Each corresponding value must conform to the value_validator.
All keys are permitted if the key_validator is undefined. Likewise, all values are permitted if the value_validator is undefined.
The number of properties must conform to the length_validator, if it is defined.
function valid_tracking_info() {
return valid.object(valid_tracking_number(), valid_mail_journey());
}
The wun_of validator permits only values that conform to at least wun of the validators in the validator_array.
function valid_size() {
return valid.wun_of(["letter", "parcel", "postcard"]);
}
A definition of the word "wun" may be found here.
The appropriate validator is chosen according to some characteric of the subject. This helps to produce a more compact violations array.
The validator_object contains the validators. The property names are the classifications. The classifier function is called with the subject, and ideally returns a classification string (or number). The classifier may indicate that the subject is unclassifiable by throwing an exception or returning a value that is not a string (or number).
The subject is permitted if it classifies as and conforms to wun of the validators.
function valid_mail() {
return valid.wun_of(
{
letter: valid_letter(),
parcel: valid_parcel(),
postcard: valid_postcard()
},
function classifier(subject) {
return subject.size;
}
);
}
The all_of validator permits only values that conform to every validator in the validator_array. It runs the validators in sequence, stopping at the first violation (unless exhaustive is true
).
The not validator permits only values that do not conform to the validator.
function valid_flat_mail() {
return valid.not(valid_parcel());
}
The literal validator permits only values equal to the expected_value. The ===
operator is used to determine equality unless either of the values is NaN
, in which case Number.isNaN
is used.
Some factories provided by JSValid accept validators as arguments. Where a non-function is provided in place of a validator, it is automatically wrapped with valid.literal
. Thus, the expression
valid.string(valid.literal(1))
may be written more succinctly as
valid.string(1)
Consequently, valid.literal
is only needed in cases where expected_value is a function or undefined
.
The any validator permits any value.
It is easy to make your own validators. Here is a factory that returns a validator that permits only multiples of n.
function valid_multiple_of(n) {
return function (subject) {
return (
subject % n === 0
? []
: [{message: `Not a multiple of ${n}.`}]
);
};
}