Validators
Validators allow additional checking beyond the basic field types. For example, you might want a
number field that only accepts positive numbers, or a string field that must be
a properly formatted URL.
A validator must either:
- return a validated value (may be a variant of the input, but must still pass type checking)
- throw (or return) an Error
In other words, in addition to ensuring valid inputs, validators may also be used to ensure consistency by providing trivial conversions, such as trimming strings, constraining ranges, changing case, etc.
A schema field validator may be set to one of the following:
-
literal: field value must match exactly- ex:
validator: "apple"
-
regex: either an actualRegExpor a regex string- ex:
validator: /[A-Za-z][A-Za-z0-9]+/
-
function: sync or async- ex:
validator: (value) => ((value % 2)? new Error("Don't be odd!") : value)
-
$keyword: execute named validator- ex:
validator: "$integer"
-
{$keyword: ...}: execute named composite validator- ex:
validator: {$and: ["$integer", "$positive"]}
In order to differentiate types and literals from validators, validator references always start with a $
The Configurator provides a default ValidatorRegistry, but you can also pass in your own.
import { Configurator, Validators } from '@versionzero/configurator';
const configurator = new Configurator();
const validators = configurator.validators;
or, just as with TypeRegistry, you might consider building your own custom ValidatorRegistry library:
import {MyCorpValidators} from '@mycorp/standard-validators'; // class MyCorpValidators extends Validators {...}
const validators = new MyCorpValidators();
const configurator = new Configurator({validators})
Validators may be asynchronous, allowing more complex domain-specific logic. For example:
validators.register('inside-git-repo',
async (value) => {
async function check(current) {
try {
const s = await stat(path.join(current, '.git'));
if (s && s.isDirectory()) {
return current;
}
}
catch (err) {
if (err.code !== 'ENOENT') {
throw new Error(`Error checking ${current}: ${err.message}`);
}
// else ignore
}
const parent = path.dirname(current);
if (parent === current) {
throw new Error(`No .git directory found in ${value} or any parent directory`);
}
return check(parent);
}
if (typeof value !== 'string') {
throw new Error(`Invalid path: ${value}`);
}
return check(value);
}
);
Built-in Basic Validators
-
$nonempty -
$alpha -
$alphanum -
$number -
$numeric -
$positive -
$negative -
$integer -
$uuid -
$file- must be existing file -
$directory- must be existing directory -
$readable -
$writable -
$url -
$httpurl -
$hostname -
$port -
$email -
$ipv4 -
$ipv6 -
$reachable- dns must resolve
Built-in Composite Validators
$and: [...]- all validators in list must pass, returns final validation result$or: [...]- any of the validators in the list must pass, returns first validation result$not: <validator>- the supplied validator must not pass$length: {min, max, exact}- string length must either beexactor betweenminandmax(can omit)$range: {min, max}- numeric value must be betweenminandmax(either can be omitted)$in: [...]- the value must exactly match one of the supplied items$each: [...]- the input value must be an array, and all provided validators must pass on each value
By combining validators, you can achieve many complex effects:
schema.field(`sleep`, {type: 'duration', validator: {$range: {min: 500, max: 60000}}, default: '15s'}); // see "types" docs
schema.field(`project`, {type: 'string', validator: {$and: ['$directory', '$inside-git-repo']}}) // see above
schema.field(`preferences`, {type: 'string', validator: {$and: ['$file', /.+\.(?:json|yaml)$/ig]}});
schema.field(`host`, {type: 'string', validator: {$or: ['$ipv4', '$ipv6', '$reachable']}})