Home Reference Source Repository

src/validation/validators.js

import 'source-map-support/register';

import {
  isArray as lodashIsArray,
  isString as lodashIsString,
  isBoolean as lodashIsBoolean,
  isPlainObject as lodashIsPlainObject,
  isFunction as lodashIsFunction
} from 'lodash';

import { isValid } from './index.js';

/**
 * Validates an array using a validator.
 *
 * @param {function|RegExp} validator - The validator to use on the elements in the array.
 * @return {function} - A function that takes a value and that returns true or false if valid or not.
 */
export function isArray(validator) {
    return (input, info) => {
        if (info) {
            return infoObject(validator, (wrap) => (`[ ${wrap} ]`));
        }

        if (!lodashIsArray(input)) {
            return 'Was not an array!';
        }

        return isArrayOrSingle(validator)(input);
    };
}

/**
 * Validates an object using a validator.
 *
 * @param {function|RegExp} validator - The validator to use on the elements in the object
 * @return {function} - A function that takes a value and that returns true or false if valid or not.
 */
export function isObject(validator) {
    return (input, info) => {
        if (info) {
            return infoObject(validator, (wrap) => `{ ${wrap} }`);
        }

        if (!lodashIsPlainObject(input)) {
            return 'Was not an object!';
        }

        if (!validator) {
            return true;
        }

        return Object.keys(input).map((key) => isValid(input[key], validator))
            .reduce((a, b) => a === true && b === true, true);
    };
}

/**
 * Validates an pontential array using a validator.
 *
 * Allows that a single value is given.
 *
 * @param {function|RegExp} validator - The validator to use on the elements in the array.
 * @return {function} - A function that takes a value and that returns true or false if valid or not.
 */
export function isArrayOrSingle(validator) {
    return (input, info) => {
        if (info) {
            return infoObject(validator, (wrap) => (`${wrap} / [ ${wrap} ]`));
        }

        const array = [].concat(input);
        for (const value of array) {
            const result = isValid(value, validator);
            if (result !== true) {
                return result;
            }
        }

        return true;
    };
}

/**
 * Validates an string.
 *
 * @param {object} value - Something to validate.
 * @param {boolean} info - If type information should be returned.
 * @return {infoObject|boolean|string} - Type information or if it is valid.
 */
export function isString(value, info) {
    if (info) {
        return infoObject('String');
    }

    if (!lodashIsString(value)) {
        return 'Was not a string!';
    }

    return true;
}

/**
 * Validates an string.
 *
 * @param {object} value - Something to validate.
 * @param {boolean} info - If type information should be returned.
 * @return {infoObject|boolean|string} - Type information or if it is valid.
 */
export function isBoolean(value, info) {
    if (info) {
        return infoObject('Boolean');
    }

    if (!lodashIsBoolean(value)) {
        return 'Was not a boolean!';
    }

    return true;
}

/**
 * Validates an string.
 *
 * @param {object} value - Something to validate.
 * @param {boolean} info - If type information should be returned.
 * @return {infoObject|boolean|string} - Type information or if it is valid.
 */
export function isInteger(value, info) {
    if (info) {
        return infoObject('Integer');
    }

    if (!Number.isInteger(value)) {
        return 'Was not an integer!';
    }

    return true;
}

/**
 * Validates an string.
 *
 * @param {object} value - Something to validate.
 * @param {boolean} info - If type information should be returned.
 * @return {infoObject|boolean|string} - Type information or if it is valid.
 */
export function isPath(value, info) {
    if (info) {
        return infoObject('Filepath');
    }

    if (isString(value) !== true) {
        return 'Was not a filepath!';
    }

    return true;
}

/**
 * Validates against a list of validators.
 *
 * @param {...function} validators - Validators to validate against.
 * @return {function} - A function that takes a value and that returns true or false if valid or not.
 */
export function oneOf(...validators) {
    if (!validators.length) {
        throw new Error('You need to use at least one validator.');
    }

    return (input, info) => {
        if (info) {
            let types = [];
            for (const validator of validators) {
                types.push(infoObject(validator).type);
            }
            return infoObject(types.join(' / '));
        }

        const invalid = [];
        for (const validator of validators) {
            const result = isValid(input, validator);
            if (result === true) {
                return true;
            }
            invalid.push(validator(null, true).type);
        }

        return 'Was not any of the possible types:\n' +
            invalid.reduce((prev, next) => prev + '\n' + next, '');
    };
}

/**
 * Marks that the value is required
 *
 * @param {function} validator - Validator to validate against
 * @return {function} - A function that takes a value and that returns true or false if valid or not.
 */
export function required(validator) {
    return (input, info) => {
        if (info) {
            return infoObject(validator, null, true);
        }

        if (
            !input && input !== false ||
            (lodashIsArray(input) || lodashIsString(input)) && input.length === 0 ||
            lodashIsPlainObject(input) && Object.keys(input).length === 0
        ) {
            return 'A value was required but none was given!';
        }

        if (!validator) {
            return true;
        }

        return validator(input);
    };
}

function infoObject(validator, wrapper, req = false) {
    const info = lodashIsFunction(validator) ? validator(null, true).type : validator.toString();
    const type = wrapper ? wrapper(info) : info;
    return {
        type,
        required: req
    };
}