Home Identifier Source Repository

src/index.js

import {chunk, some} from 'lodash';
import {Map, List} from 'immutable';


/**
 * @private
 * Checks a key and value against callables given by a tuple of functions
 *
 * @param {Array} pair
 * @param {*} key any value to test
 * @param {*} value any value to test
 * @returns {Boolean} whether the key/value match the pair
 */
function checkPair(pair, key, value) {
    const [key_check, value_check] = pair;
    return key_check(key) && value_check(value);
}


/**
 * Creates a validator function based on passed validator 2-tuples.
 *
 * Key-value entries in the map must satisfy at least one of the validator tuples.
 *
 * TODO: Optional and required key/values
 *
 * @param {function} ...validators functions in pairs to match with each key-value pair
 * @returns {function} function returns true if passed a valid Map
 * @example
 * var isValid = MapSchema(isNumber, Exactly('hi'), isString, isNumber);
 *
 * isValid(Map().set(5, 'hi')); // true
 * isValid(Map().set('hi', 'hi')); // false
 * isValid(Map().set(5, 'hi').set('foo', 3)); // true
 */
export function MapSchema(...validators) {
    const pairs = chunk(validators, 2);
    if (validators.length % 2 === 1) {
        throw 'Must have even length of validators';
    }

    function validatePair(value, key) {
        return some(pairs, pair => checkPair(pair, key, value));
    }

    return function(candidate) {
        if (Map.isMap(candidate)) {
            return candidate.every(validatePair);
        } else {
            return false;
        }
    };
}


/**
 * Generates a function that checks each value matches the validators passed,
 * respective per index.
 *
 * @param {function} ...validators functions, in order, to check each value in the passed list
 * @returns {function} which returns true if each element in list matches in order
 * @example
 * var schema = FixedListSchema(Exactly('roar'), isNumber);
 *
 * schema(List.of(3));  // false
 * schema(List.of('roar'));  // false
 * schema(List.of('fake roar'));  // false
 * schema(List.of('roar', 15));  // true
 */
export function FixedListSchema(...validators) {
    return function(candidate) {
        if (List.isList(candidate) && candidate.size === validators.length) {
            return candidate.every((value, index) => validators[index](value));
        } else {
            return false;
        }
    };
}


/**
 * Generates a function that checks that each element of a List matches at least
 * one of the passed validators.
 *
 * @param {function} ...validators functions to check each value in the passed list
 * @returns {function} which returns true if each element in list matches at least one validator
 * @example
 * var schema = ListSchema(Exactly('roar'), isNumber);
 *
 * schema(List.of(3));  // true
 * schema(List.of('roar'));  // true
 * schema(List.of(3, 'fake roar'));  // false
 */
export function ListSchema(...validators) {
    return function(candidate) {
        if (List.isList(candidate)) {
            return candidate.every(
                value => some(validators, validator => validator(value))
            );
        } else {
            return false;
        }
    };
}


/**
 * Convenience function that returns a function that validates strict equality.
 *
 * @param {*} value value to test equality
 * @returns {function} evaluates strict equality of passed value
 * @example
 * var isExactly = Exactly('3');
 *
 * isExactly('hi');  // false
 * isExactly(3);  // false
 * isExactly('3');  // true
 */
export function Exactly(value) {
    return candidate => value === candidate;
}


/**
 * Convenience function that returns a function that validates strict membership
 * in passed arguments.
 *
 * @param {*} ...values collection of values to test membership upon
 * @returns {function} evaluates membership of passed value
 * @example
 * var membership = OneOf('hi', '3', 19);
 *
 * membership('hi');  // true
 * membership(3);  // false
 * membership('not in there');  // false
 * membership(19);  // true
 */
export function OneOf(...values) {
    return candidate => some(values, Exactly);
}