Custom Predicates

Predicates are functions used by verifica to decide if value is matching some validation rule, and if no, then what is the error. To be valid a Predicate , functions need to follow certain format:

function isSmallNumber(verificable): VerificaError | VerificaError[] | undefined {
    // perform all necessary checks here,
    
    // return VerificaError or array of those errors if invalid,
    // return undefined/nothing otherwise,
    
    // ot throw VerificaException with provided errors.
}
  • They need to take one argument which will be Verificable object to be checked.

  • They need to return: VerificaError, an array of VerificaError objects, or undefined / nothing.

  • They can also throw VerificaException with errors, that will be treated the same by verifica as returning VerificaError object(s).

Patterns

In general, fulfilling the requirements specified above is enough for a function to be considered as a Predicate . There is a set of useful patterns however, that can make writing Predicate functions easier. Those patterns are listed below.

Using rawValue() function:

Using rawValue() function we can get raw value behind representing it Verificable object. We can do our necessary tests directly on it, and then return error / no error based on that.

const { rawValue, makeError } = require("verifica");

function isSmallNumber(verificable) {
    // get raw value to be checked:
    const value = rawValue(verificable);
    
    // return error if not matching:
    if (!(typeof value === "number") || value < 0 || value > 10) {
        return makeError(verificable, {
            type: "isSmallNumber",
        });
    }
    
    // return nothing if OK.
}

And let's try it out:

const { asVerificable, isValid, getErrors } = require("verifica");

const value = 14;
const verificable = asVerificable(value);

isValid(verificable, isSmallNumber); // false
getErrors(verificable, isSmallNumber);
// [{ type: "isSmallnumber", path: [] }]

Using ensure() function:

Instead of using rawValue() , we can also call ensure() with some prerequisite condition predicate, like isNumber here. If it throws VerificaException, the validation will be considered not passed with errors coming from thrown VerificaException . If it doesn't throw, we already now value is a number, so we only need to check for other custom conditions (like < 0, > 10 in the example) and decide there if error / no error should be returned.

const { ensure, isNumber, makeError } = require("verifica");

function isSmallNumber(verificable) {
    // get value ensuring first that it is a number:
    const value = ensure(verificable, isNumber);
    // since ensure didn't throw, we know value is a number here.
    // if ensure would throw VerificaException with errors,
    // it will be treated as returning those error(s).
    
    
    // return error if not matching:
    if (value < 0 || value > 10) {
        return makeError(verificable, {
            type: "isSmallNumber",
        });
    }
    
    // return nothing if OK.
}

And let's try it out:

const { asVerificable, isValid, getErrors } = require("verifica");

const value = 14;
const verificable = asVerificable(value);

isValid(verificable, isSmallNumber); // false
getErrors(verificable, isSmallNumber);
// [{ type: "isSmallnumber", path: [] }]

Using multiple ensure() functions:

ensure() function can be called multiple times in the same Predicate function, e.g. checking if properties of an object matches required conditions as well. If any of those calls throw an VerificaException, the whole predicate is considered as not matching, with error(s) taken from the VerificaException.

const { ensure, isObject, isString, isNumber } = require("verifica");

// We expect object in this format:
// {
//     name: string;
//     address: {
//         street: string;
//         zipCode: number;
//     }
// }

function isUser(verificable) {
    ensure(verificable, isObject);
    ensure(verificable.name, isString);
    
    ensure(verificable.address, isObject);
    ensure(verificable.address.street, isString);
    ensure(verificable.address.zipCode, isNumber);
    
    // If none of above ensure threw an exception, we consider object valid.
    // return nothing if OK.
}

And let's try it out:

const { asVerificable, isValid, getErrors } = require("verifica");

const value = {
    name: "john Smith",
    address: {
        street: null,
        zipCode: null,
    }
};
const verificable = asVerificable(value);

isValid(verificable, isUser); // false
getErrors(verificable, isUser);
// [{ type: "isString", path: ["address", "street"] }]

Using VerificaChunk:

VerificaChunk can be also used to perform multiple checks on multiple values at the same time.

The difference between using VerificaChunk and just multiple ensure() calls in a row is that VerificaChunk will perform all the checks when chunk.ensureAll() is called, and throw VerificaException containing all aggregated errors from all the checks. Calling ensure() multiple times will throw on first error and only that error will be returned.

const { VerificaChunk, isObject, isNumber, isString } = require("verifica");

// We expect object in this format:
// {
//     id: number;
//     name: string;
//     price: number;
// }

function isProduct(verificable) {
    const chunk = new VerificaChunk();
    
    chunk.add(verificable, isObject);
    chunk.add(verificable.id, isNumber);
    chunk.add(verificable.name, isString);
    chunk.add(verificable.price, isNumber);
    
    chunk.ensureAll();
    
    // If chunk.ensureAll() didn't threw an exception, we consider object valid.
    // return nothing if OK.
}

And let's try it out:

const { asVerificable, isValid, getErrors } = require("verifica");

const value = {
    id: ["a", "b"],
    name: null,
    price: 88,
};
const verificable = asVerificable(value);

isValid(verificable, isProduct); // false
getErrors(verificable, isProduct);
// [
//     { type: "isNumber", path: ["id"] },
//     { type: "isString", path: ["name"] }
// ]

Last updated