API Reference

Module exports

The Validator Template Tag

import { validator } from 'moat-maker';

const validatorInstance = validator`{ x: number, y: number }`;

This function is intended to be used as a template tag. Do not use it any other way - calling it like a normal function (with parentheses instead of back-tick) could cause memory leaks if you pass in dynamically generated strings due to the fact that this template tag auto-caches the string inputs it receives.

The input text is expected to follow the syntax outlined in the Syntax Reference. It returns a validator instance.

validator is defined in TypeScript as a generic, and can be provided with an optional generic parameter that indicates the type of data it validates. This allows instance methods, like .assertionTypeGuard() and others, to have a more accurate type signature.

const validateString = validator<string>`string`;

Refer to .assertionTypeGuard()'s section for a more complete example of why a generic parameter can sometimes be useful.

ValidatorSyntaxError

An instance of this error is thrown whenever there's an issue in the syntax of your validation rules.

import { validator, ValidatorSyntaxError } from 'moat-maker';

try {
  validator`[number string]`;
} catch (error) {
  if (error instanceof ValidatorSyntaxError) {
    console.error('Syntax Error:', error.message);
    // -- Output --
    // Syntax Error: Expected a comma (`,`) or closing bracket (`]`). (line 1, col 9)
    //   [number string]
    //           ~~~~~~
  } else {
    throw error;
  }
}

Only the Moat Maker library can create instances of this error. The class is only exported to allow you to do instanceof checks as needed, or to use it as a TypeScript type.

Validator Static Methods

validator.from()

If a validator instance is passed in, the same validator instance is returned.

If a string is passed in, the string will be parsed as a string containing validation rules, and a new validator instance will be returned.

This is intended to help with creating easy-to-use validator factories, as described in more detail on the "replacement tools for generics" page.

validator.fromRuleset()

This function expects a ruleset as a parameter and returns a new validator instance. You can learn about the shape of a ruleset and how to build them on the type transformers page.

validator.lazy()

This function allows you to lazily fetch or build a validator instance at the moment it's needed. It expects a callback to be provided and will return a lazy evaluator (of type LazyEvaluator), which can be interpolated into new validators.

The callback accepts, as a parameter, the value it's in charge of validating. It should return a validator instance, which will be used to validate the data.

.lazy() is a versatile tool that enables multiple patterns such as circular references or multi-step validation where later steps reference data from earlier steps. Refer to either of those pages to see concrete examples of how .lazy() can be used.

validator.expectTo()

The validator.expectTo() function makes it easy to supply custom validation logic. It expects a callback that returns an error string or undefined, depending on if your custom condition is satisfied. validator.expectTo() returns an expectation instance (of type Expectation), which can then be interpolated into a validator template.

The error message string you return is expected to complete the sentence "Expect [the value] to ..." (just read off the expectTo() function name before reading the strings you return to it). End the phrase with a period, and if needed, you can add additional sentences afterward.

Example usage:

const expectNonEmptyArray = validator.expectTo(unknownValue => {
  if (!Array.isArray(unknownValue) || unknownValue.length === 0) {
    return 'be an empty array.';
  }
});

const parentValidator = validator`{
  name: string
  children: string[] & ${expectNonEmptyArray}
}`;

// ✓
parentValidator.assertMatches({
  name: 'Billy',
  children: ['Sally', 'Sam'],
});

// ✕ - Expected <receivedValue>.children, which was [object Array],
//     to be an empty array.
parentValidator.assertMatches({
  name: 'Bob',
  children: [],
});

A recommended naming convention for your custom expectations is to have the variable name start with "expect", like, expectNonEmptyArray. If the expectation is not a stand-alone one and has certain pre-conditions that are supposed to be met before it gets used, start the variable name with "andExpect", like andExpectNonEmptyArray. When interpolating, the "and" word is intended to remind you that this expectation depends on what just happened earlier in the validation process.

expectTo() is also generic. If, for example, your pre-condition is that the value you're validating must already be known to be a valid array before your custom expectation is used, you would provide unknown[] as your type parameter to describe this pre-condition. This will cause your callback's parameter to be set to the type unknkown[] as opposed to the default unknown. Below is an example of an expectation that requires a pre-condition to be met before it can be used.

// Before using this expectation, make sure you've already
// validated that the value is an array. If you don't,
// this expectation may throw, because it will try to access
// a `length` property that may not exist on the value.
const andExpectNonEmptyArray = validator.expectTo<unknown[]>(array => {
  if (array.length === 0) {
    return 'be an empty array.';
  }
});

const parentValidator = validator`{
  name: string
  // First validate that 'children' is an array of strings.
  // After that, we've satisfied the custom expectation's pre-condition
  // so we're ok to use it to check if the array is non-empty.
  children: string[] & ${andExpectNonEmptyArray}
}`;

validator.isValidator()

Returns true if you pass in a validator instance. Returns false if anything else is passed in.

validator.isExpectation()

Returns true if you pass in an expectation instance. Returns false if anything else is passed in.

validator.isLazyEvaluator()

Returns true if you pass in a LazyEvaluator instance. Returns false if anything else is passed in.

The Validator Instance

Validator instances are used to perform various kinds of runtime assertions and type-checks against unknown data. The instances are of the type Validator<TypeTheyValidate=unknown>.

Validators can be interpolated into other validators, letting you build up more complicated validators from simpler ones.

<validator instance>.matches()

Expects any value as a parameter. Returns true if the provided value matches the validator.

<validator instance>.assertMatches()

Expects any value as a parameter. Throws a TypeError if the value fails to match the validator. Returns the supplied argument as-is.

An optional second "options" argument can be provided to configure the behavior of this method. options.at is used to describe the value that's being validated, and options.errorPrefix will be attached to the beginning of any generated error message. Both of these are described in detail on the customizing assertion error messages page.

options.errorFactory can also be supplied. This should be a callback that expects, as parameters, the arguments you would expect an error constructor to receive (usually just a message parameter, but other optional arguments may be received by an error constructor). The callback should return an instance of an error class of your choice. If validation fails, instead of throwing a TypeError, the default, this errorFactory() callback will be called, which will decide what gets thrown.

const errorFactory = (...args) => {
  const myError = new Error(...args);
  myError.code = 'VALIDATION_FAILED';
  return myError;
};

// ✕ - An instance of `Error` containing a code property set to "VALIDATION_FAILED"
//     will be thrown.
validator`number`
  .assertMatches('a string', { errorFactory });

<validator instance>.assertionTypeGuard()

This function behaves exactly like validatorInstance.assertMatches(), with the only exception being that it does not return anything.

This function was given a different TypeScript type signature than .assertMatches(). .assertionTypeGuard() is declared with TypeScript's asserts keyword, allowing it to be used for type-narrowing purposes. Keep in mind that there is a handful of restrictions on how TypeScript assert functions can be used and what they can return, which is why this is provided as a separate function.

If you need type narrowing, use this function, if you don't, or if you're not using TypeScript, use .assertMatches().

Example usage:

// For the type-narrowing to work, you must supply a type parameter
// to the validator with `Validator<...>`.
const validateNumber: Validator<string> = validator`string`;

const unknownValue: unknown = 'Hi There!';

// Assert that `unknownValue` is, in fact, a string
validateNumber.assertionTypeGuard(unknownValue);

// Now TypeScript will let you use unknownValue as a string, because
// we just proved it was.
console.log(unknownValue.slice(3));

In the above example, if you replace .assertionTypeGuard() with .assertMatches(), TypeScript won't let this compile, because you haven't proved to TypeScript that unknownValue is a string.

<validator instance>.assertArgs()

When you wish to validate user input to your API functions, it is recommended to use this function, as it is capable of providing more descriptive error messages than .assertMatches(). A detailed description of how to use this method can be found on the custom assertion messages page.

<validator instance>.ruleset

This contains the ruleset that the validator follows as it validates data. This ruleset is generally the result of parsing the text provided in the validator template tag. This information is provided for the purpose of enabling validator transformers, which is described in more detail on its dedicated page.

Last updated