1.0.15 • Published 4 years ago

composite-validation v1.0.15

Weekly downloads
3
License
MIT
Repository
github
Last release
4 years ago

composite-validation

Composite validation API for JS data models. Based on this project idea. This library is designed to check fields values in some data model. It supports multiple validity conditions.

Build Status Coverage Status npm version install size License: MIT

Install

npm install composite-validation

Use

For some data model...

const dataModel = {
    name: 'Leonardo',
    age: 35
};

...describe validation map that folows the structure of the original object using operators.

import { ValidationMap, Conditions, required, equals } from 'composite-validation';

const map = ValidationMap({
    name: Conditions(v => required(v)),
    age: Conditions([
        v => required(v),
        v => equals(v, 21)
    ])
});

Call function.

const result = map(dataModel);

Function applies validation map to data model, checks all validity conditions and returns object with validity states for each field.

{
    "name": {
        "value": "Leonardo",
        "isRequired": true
    },
    "age": {
        "error": "Value should be equal to 21",
        "isRequired": true
    }
}

Fine-tuning of validation

You can also apply conditions not permanently. If you pass a function to the operator, the condition will be checked only if the function returns true.

const map = ValidationMap({
    name: Conditions(v => required(v)),
    age: Conditions([
        v => equals(v, 21, () => !!v)
    ])
});

For different datasets our function map() will return different results:

{
    name: 'Leonardo',
    age: null
};
// ...
{
    "name": {
        "value": "Leonardo",
        "isRequired": true
    },
    "age": {
        "value": null,
        "isRequired": false
    }
}
{
    name: 'Leonardo',
    age: 35
};
// ...
{
    "name": {
        "value": "Leonardo",
        "isRequired": true
    },
    "age": {
        "error": "Value should be equal to 21",
        "isRequired": false
    }
}

And another example:

// map.
ValidationMap({
    name: Conditions(v => required(v)),
    age: Conditions([
        v => required(v),
        (v, model) => equals(v, 18, () => model.gender === 'male')
    ])
});


// data 1.
{
    name: 'Leonardo',
    age: 35,
    gender: 'male'
};

// result 1.
{
    "name": {
        "value": "Leonardo",
        "isRequired": true
    },
    "age": {
        "error": "Value should be equal to 18",
        "isRequired": true
    }
}


// data 2.
{
    name: 'Ellen',
    age: 23,
    gender: 'female'
};

// result 2.
{
    "name": {
        "value": "Ellen",
        "isRequired": true
    },
    "age": {
        "value": 23,
        "isRequired": true
    }
}

Get access to other data object levels

As you see in previous example it's possible to get access to the another levels of the data object hierarchy. If you need to go deeper... below is more detailed example.

// data.
{
    name: 'Leonardo',
    age: 35,
    gender: 'male',
    character: {
        name: 'Cobb',
        eyes: 'blue',
        family: [
            {
                relationType: 'wife',
                gender: 'female'
            },
            {
                relationType: 'son',
                gender: 'male'
            }
        ]
    }
};

// map.
ValidationMap({
    name: Conditions(v => required(v)),
    age: Conditions([
        v => required(v),
        (v, model) => equals(v, 18, () => model.gender === 'male')
    ]),
    character: ValidationMap({
        eyes: Conditions([
            (v, char, model) => equals(v, 'blue', () => !!(!char.name.includes('_') && model.gender === 'male')))
        ]),
        // Apply ValidationMap to each element of array.
        family: Each(ValidationMap({
            relationType: Conditions(v => required(v)),
            gender: Conditions(v => equals(v, 'female'))
        }))
    })
});

// result.
{
    "name": {
        "value": "Leonardo",
        "isRequired": true
    },
    "age": {
        "error": "Value should be equal to 18",
        "isRequired": true
    },
    "character": {
        "eyes": {
            "value": "blue",
            "isRequired": false
        },
        "family": {
            "item0": {
                "relationType": {
                    "value": "wife",
                    "isRequired": true
                },
                "gender": {
                    "value": "female",
                    "isRequired": false
                }
            },
            "item1": {
                "relationType": {
                    "value": "son",
                    "isRequired": true
                },
                "gender": {
                    "error": "Value should be equal to female",
                    "isRequired": false
                }
            }
        }
    }
}

You can use function Each() to apply ValidationMap or Conditions to each element of array. In addition, you can set custom checks in operator() and use its in Conditions.

// data.
{
    arr: [1, 3, 5, 6],
    entities: [
        {  
            id: 1,
            type: 2,
        },
        {  
            id: 2,
            type: 1,
        },
        {  
            id: 3,
            type: 2,
        }
    ]
}

// map.
ValidationMap({
    arr: Each(Conditions([
        v => operator(v, () => v % 2 === 0)
    ])),
    entities: Each(ValidationMap({
        type: Conditions(v => equals(v, 2))
    }))
});

// result.
{
    "arr": {
        "item0": {
            "error": "Value is not valid",
            "isRequired": false
        },
        "item1": {
            "error": "Value is not valid",
            "isRequired": false
        },
        "item2": {
            "error": "Value is not valid",
            "isRequired": false
        },
        "item3": {
            "value": 6,
            "isRequired": false
        }
    },
    "entities": {
        "item0": {
            "type": {
                "value": 2,
                "isRequired": false
            } 
        },
        "item1": {
            "type": {
                "error": "Value should be equal to 2",
                "isRequired": false
            } 
        },
        "item2": {
            "type": {
                "value": 2,
                "isRequired": false
            } 
        }
    }
}

Custom operators

The library contains a limited number of operators, but you can create custom operator functions. They must match the specified signature and return instance of ValueValidationError in invalid case or instance of WrappedValue in other cases.

(data, ...args) => any;
// ...

export function myTypeCheckOperator(value: any) {
    if (typeof value !== 'string') {
        return new ValueValidationError('Invalid value type');
    }
    
    return new WrappedValue(value, false);
}

For convenience, it is possible to use helper functions from Utils class.

export function myTypeCheckOperator(value: any) {
    const t = typeof value;
    if (t !== 'string') {
        return Utils.getErrorObject('Invalid value type: {$0}. Valid type is string.', false, [t]);
    }
    
    return Utils.getWrappedValue(value);
}

Custom errors

All validation error messages are reassignable. Call static function CompositeValidation.setErrorMatches() and pass your object with error mapping to set errors match globally. Use {$0}, {$1} tokens to replace them with value passed from inside the operator.

CompositeValidation.setErrorMatches({
    equals: 'Not equals!', // default library operator.
})

// or...

CompositeValidation.setErrorMatches({
    required: 'This value {$0} is not valid!', // default library operator.
    equals: 'Not equals!', // default library operator.
    myTypeCheckOperator: 'Invalid type' // your custom operator
})

Then you can call Utils.errorMatch('') inside the operator.

export function myTypeCheckOperator(value: any) {
    const t = typeof value;
    if (t !== 'string') {
        return Utils.getErrorObject(CompositeValidationOptions.errorMatch('myTypeCheckOperator'), false, [t]);
    }
    
    return Utils.getWrappedValue(value);
}
1.0.16

4 years ago

1.0.15

4 years ago

1.0.14

4 years ago

1.0.13

4 years ago

1.0.12

4 years ago

1.0.11

4 years ago

1.0.9

4 years ago

1.0.8

4 years ago

1.0.7

4 years ago

1.0.6

4 years ago

1.0.5

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago