AZaviruha/react-form-generator

View on GitHub
src/validation/index.js

Summary

Maintainability
B
4 hrs
Test Coverage
var t          = require( '../tools' )
  , reduce     = t.reduce
  , getOrNull  = t.getOrNull;



/**
 * Provides validation for GeneratedForm.
 * Can be configured by `conf` arguments:
 * @param {Object} conf - settings of validation. 
 * @param {Object[]} conf.validators - array of custom validators.
 */
module.exports = function ( conf ) {
    conf = conf || {};
    var VALIDATORS = t.merge( require( './validators' ), 
                              conf.validators );


    VALIDATORS.or  = composite( t.or, false );
    VALIDATORS.and = composite( t.and, true );
    
    /**
     * Build composite validator, which 
     * combines result of all nested validators 
     * by `comb` function.
     *
     * @param {Function} comp - `and`, `or`, etc.
     * @param {Object} zero - intital value of composite
     *                        validator result. 
     * @returns {Function)
     */
    function composite ( comb, zero ) {
        return function ( conf, value, fieldMeta ) {
            var validators = conf.value;
            return reduce(function ( acc, v ) {
                var f = VALIDATORS[ v.rule ];
                checkRuleExistence( f, v.rule );
                return comb( acc, f( v, value, fieldMeta ) );
            }, zero, validators ); 
        };
    }


    /**
     * Validates field's value against of validation rule.
     * Returs sublist of validateion rules that was not sutisfied 
     * by value.
     * @param {Object} ruleInfo - validation rules.
     * @param {Object} value - value to validate.
     * @param {Object} fieldMeta - for some validation rules 
     * field meta is required.
     * @returns {Object}
     */
    function checkByRule ( ruleInfo, value, fieldMeta ) {
        var rule    = ruleInfo.rule
          , isValid = VALIDATORS[ rule ];

        checkRuleExistence( isValid, rule );
        return isValid( ruleInfo, value, fieldMeta ) ? null : ruleInfo;
    }

    
    function checkRuleExistence ( validate, rule ) {
        if ( !validate ) 
            throw new Error( 'Valdiation rule does not exist: ' + rule );
    }

    /**
     * Validates field's value against list of validation rules.
     * Returs sublist of validation rules that was not sutisfied 
     * by value.
     * @param {Object[]} rules - list of validation rules.
     * @param {Object} value - value to validate.
     * @returns {Object[]}
     */
    function checkByAll ( rules, value, fieldMeta ) {
        return (rules || [])
            .map(function ( rule ) { 
                return checkByRule( rule, value, fieldMeta );
            })
            .filter(function ( el ) { return null !== el; });
    }


    /**
     * Calculates field's validity by it's meta and value.
     * @param {Object} fieldMeta - metadata of one field 
     * in FormGenerator format.
     * @param {Object} value - { %fieldID%: %fieldValue% }
     * @return {Object[]} - [ %failed_validators% ]
     */
    function validateField ( fldID, fieldMeta, value ) {
        var res  = {}
          , errs = checkByAll( fieldMeta.validators, value, fieldMeta );
        res[ fldID ] = errs.length ? errs : null;
        return res;
    }
    

    /**
     * Calculates validity of the array of fields.
     * @param {Object[]} fldsMeta - array of metadata.
     * @param {Object[]} fldsValue - map with fields value.
     * @returns {Object} - { 
     *     %fieldID%: [ %failed_validators% ],
     *     ...
     * }
     */
    function validateFields ( fldsMeta, fldsValue ) {
        return reduce(function ( acc, fldMeta, fldID ) {
            var value  = getOrNull( fldsValue, fldID )
              , errors = validateField( fldID, fldMeta, value );
            return t.merge( acc, errors );
        }, {}, fldsMeta );
    }

    
    /**
     * Calculates form's validity by it's meta and value.
     * Wraps `validateFields` and filters array of fields
     * before validation.
     * @param {Object} formMeta - metadata in GeneratedForm format.
     * @param {Object} formValue - data in GeneratedForm format.
     * @returns {Object} - { 
     *     %fieldID%: [ %failed_validators% ],
     *     ...
     * }
     */
    function validateForm ( formMeta, formValue ) {
        var fields    = getOrNull( formMeta, 'fields' )
          , validable = reduce(function ( acc, fld, fldID ) {
                var noValidate = !t.isDefined( fld.validators ) 
                                 || fld.isHidden
                                 || fld.isReadOnly

                  , fldMeta  = {};

                if ( noValidate ) return acc;
                else {
                    fldMeta[ fldID ] = fld;
                    return t.merge( acc, fldMeta );
                }
            }, {}, fields );

        return validateFields( validable, formValue );
    }

    
    /**
     * Accepts result of `validateForm` and returns true
     * if `formErrors` contains only null-values.
     * @param {Object} formErrors - { 
     *     %fieldID%: [ %failed_validators% ],
     *     ...
     * }
     * @returns {Boolean}
     */
    function isFormValid ( formErrors ) {
        return t.reduce(function ( acc, v, k ) {
            return acc && !(v && v.length);
        }, true, formErrors );
    }


    return {
        validateField: validateField,
        validateForm:  validateForm,
        isFormValid:   isFormValid
    };
};