Skip to content
master
Go to file
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
log
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Build Status npm version codecov NpmLicense

Table of Contents

(TOC generated using markdown-toc)

Motivation

I haven't found good enough implementation of JSR-303 Bean Validation for javascript, so here we go:

Main goals during implementation of this library was:

  • simple and robust architecture
  • asynchronous behaviour (due to asynchronous nature of javascript)
  • extendability (custom asynchronous validator)
  • validation of any data structure and easyness in use (guaranteed by following JSR-303)
  • well tested (different node versions and browsers - done with "jest" and "karma") for polymorphic use on server and in the browser
  • no dependencies

Feel free to contribute.



Loosely inspired by:

Live example:

https://codesandbox.io/s/ymwky9603j

Simple example:

import validator, {
    Required,
    Optional,
    Collection,
    All,
    Blank,
    Callback,
    Choice,
    Count,
    Email,
    IsFalse,
    IsNull,
    IsTrue,
    Length,
    NotBlank,
    NotNull,
    Regex,
    Type,
} from '@stopsopa/validator';

(async () => {

    const errors = await validator({
        name            : '',
        surname         : 'doe',
        email           : '',
        terms           : false,
        comments        : [
            {
                comment: "What an ugly library"
            },
            {
                comment: 'empty'
            }
        ]
    }, new Collection({
        name            : new Required([
            new NotBlank(),
            new Length({min: 3, max: 255})
        ]),
        surname         : new Required([
            new NotBlank(),
            new Length({min: 10, max: 255})
        ]),
        email           : new Required(new Email()),
        terms           : new Optional(new IsTrue()),
        comments        : new All(new Collection({
            comment: new Required(new Length({min: 10}))
        }))
    }));

    if ( errors.count() ) {
    
        // ... handle errors

        console.log(JSON.stringify(errors.getFlat(), null, 4));
        // {
        //     "name": "This value should not be blank.",
        //     "surname": "This value is too short. It should have 10 characters or more.",
        //     "email": "This value is not a valid email address.",
        //     "terms": "This value should be true.",
        //     "comments.1.comment": "This value is too short. It should have 10 characters or more."
        // }

        console.log(JSON.stringify(errors.getTree(), null, 4));
        // {
        //     "name": "This value should not be blank.",
        //     "surname": "This value is too short. It should have 10 characters or more.",
        //     "email": "This value is not a valid email address.",
        //     "terms": "This value should be true.",
        //     "comments": {
        //         "1": {
        //             "comment": "This value is too short. It should have 10 characters or more."
        //         }
        //     }
        // }
    }

})();

Example

Entity manager

const abstract          = require('@stopsopa/knex-abstract');

const extend            = abstract.extend;

const prototype         = abstract.prototype;

const log               = require('inspc');

const a                 = prototype.a;

const {
    Collection,
    All,
    Required,
    Optional,
    NotBlank,
    Length,
    Email,
    Type,
    IsTrue,
    Callback,
    Regex,
} = require('@stopsopa/validator');

const ext = {
    initial: async function () {
        return {
            updated     : this.now(),
            created     : this.now(),
            port        : 80,
        }
    },
    toDb: row => {

        return row;
    },
    update: function (...args) {

        let [debug, trx, entity, id] = a(args);

        delete entity.created;

        entity.updated = this.now();

        return prototype.prototype.update.call(this, debug, trx, entity, id);
    },
    insert: async function (...args) {

        let [debug, trx, entity] = a(args);

        entity.created = this.now();

        delete entity.updated;
        
        const id = await prototype.prototype.insert.call(this, debug, trx, entity);

        return id;
    },
    prepareToValidate: function (data = {}, mode) {

        delete data.created;

        delete data.updated;

        return data;
    },
    getValidators: function (mode = null, id, entity) {

        const validators = {
            id: new Optional(),
            cluster: new Required([
                new NotBlank(),
                new Length({max: 50}),
                new Callback(
                    (value, context, path, extra) =>
                        new Promise(async (resolve, reject) => {

                            const {
                                cluster,
                                node,
                                id,
                            } = context.rootData;

                            const condition = (node === null) ? 'is' : '=';

                            let c;

                            log(mode);

                            if (mode === 'create') {

                                c = await this.queryColumn(true, `select count(*) c from :table: where cluster = :cluster and node ${condition} :node`, {
                                    cluster,
                                    node,
                                });
                            }
                            else {

                                c = await this.queryColumn(true, `select count(*) c from :table: where cluster = :cluster and node ${condition} :node and id != :id`, {
                                    cluster,
                                    node,
                                    id,
                                });
                            }


                            log.dump(c);

                            const code = "CALLBACK-NOTUNIQUE";

                            if (c > 0) {

                                context
                                    .buildViolation('Not unique')
                                    .atPath(path)
                                    .setParameter('{{ callback }}', 'not equal')
                                    .setCode(code)
                                    .setInvalidValue(`cluster: '${cluster}' and node: '${node}'`)
                                    .addViolation()
                                ;

                                if (extra && extra.stop) {

                                    return reject('reject ' + code);
                                }
                            }

                            resolve('resolve ' + code);
                        })
                )
            ]),
            domain: new Required([
                new NotBlank(),
                new Length({max: 50}),
            ]),
            port: new Required([
                new NotBlank(),
                new Length({max: 8}),
                new Regex(/^\d+$/),
            ]),
        };

        if (typeof entity.node !== 'undefined') {

            if (entity.node === null) {

                validators.node = new Optional();
            }
            else {

                validators.node = new Required([
                    new NotBlank(),
                    new Length({max: 50}),
                ]);
            }
        }

        return new Collection(validators);
    },
};    

module.exports = knex => extend(
    knex,
    prototype,
    Object.assign({}, require('./abstract'), ext),
    'clusters',
    'id',
);

Controller

const knex          = require('@stopsopa/knex-abstract');

const log           = require('inspc');

const validator     = require('@stopsopa/validator');
    ...
    app.all('/register', async (req, res) => {

        let entity              = req.body;

        let id                  = entity.id;

        const mode              = id ? 'edit' : 'create';

        const man               = knex().model.clusters;

        const validators        = man.getValidators(mode, id);

        if (mode === 'create') {

            entity = {
                ...man.initial(),
                ...entity,
            };
        }

        const entityPrepared    = man.prepareToValidate(entity, mode);
        
        const errors            = await validator(entityPrepared, validators);

        if ( ! errors.count() ) {

            try {

                if (mode === 'edit') {

                    await man.update(entityPrepared, id);
                }
                else {

                    id = await man.insert(entityPrepared);
                }

                entity = await man.find(id);

                if ( ! entity ) {

                    return res.jsonError("Database state conflict: updated/created entity doesn't exist");
                }
            }
            catch (e) {

                log.dump(e);

                return res.jsonError(`Can't register: ` + JSON.stringify(req.body));
            }
        }

        return res.jsonNoCache({
            entity: entity,
            errors: errors.getTree(),
        });

    });
    ...

For further examples please follow test cases

Validators references

Blank

Source code Blank.js

new Blank({
    message    : 'This value should be blank.',    
});

Callback

Source code Callback.js

See test example Callback.test.js

new Callback((value, context, path, extra) => {...}); // function required

Choice

Source code Choice.js

new Choice({
    choices         : ['...'], // required

    multiple        : false, 
    min             : 0, // only if multiple=true
    max             : 0, // only if multiple=true
    
    message         : 'The value you selected is not a valid choice.',
    multipleMessage : 'One or more of the given values is invalid.',
    minMessage      : 'You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.',
    maxMessage      : 'You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.',
});

// or shorter syntax if ony choices are given:

new Choice(['...']); // just choices

Collection

Source code Collection.js

new Collection({
    fields                  : { // required type: non empty object
        a: new Require(),
        b: new Optional(),
    }, 
    allowExtraFields        : false,
    allowMissingFields      : false,
    extraFieldsMessage      : 'This field was not expected.',
    missingFieldsMessage    : 'This field is missing.',   
});

// or shorter syntax if only fields are given:

new Collection({ // required type: non empty object
    a: new Require(),
    b: new Optional(),
});

Count

Source code Count.js

new Count({
    // min; // min or max required (or both) - if min given then have to be > 0
    // max, // min or max required (or both) - if max given then have to be > 0
    
    minMessage: 'This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.',
    maxMessage: 'This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.',
    exactMessage: 'This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.',
});

// or shorter syntax if ony min and max given and min = max:

new Count(5);

Email

Source code Email.js

new Email({
    message    : 'This value is not a valid email address.',  
});

IsFalse

Source code IsFalse.js

new IsFalse({
    message    : 'This value should be false.',
});

IsTrue

Source code IsTrue.js

new IsTrue({
    message    : 'This value should be true.',
});

IsNull

Source code IsNull.js

new IsNull({
    message    : 'This value should be null.',
});

Length

Source code Length.js

new Length({
    // min; // min or max required (or both) 
    // max, // min or max required (or both) 
    
    maxMessage: 'This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.',
    minMessage: 'This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.',
    exactMessage: 'This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.',
});

NotBlank

Source code NotBlank.js

new NotBlank({
    message    : 'This value should not be blank.',
});

NotNull

Source code NotNull.js

new NotNull({
    message    : 'This value should not be blank.',
});

Regex

Source code Regex.js

new Regex({
    pattern     : /abc/gi, // required, type regex
    message     : 'This value is not valid.',
    match       : true, // true     - if value match regex then validation passed
                        // false    - if value NOT match regex then validation passed
});

Type

Source code Type.js

// available values for field 'type' are:
// 'undefined', 'object', 'boolean', 'bool', 'number', 'str', 'string', 
// 'symbol', 'function', 'integer', 'int', 'array'
new Type({
    type       : '...', // required
    message    : `This value should be of type '{{ type }}'.`,
});

// or shorter syntax if ony type is given:

new Type('str'); 

Addidional tools

require('@stopsopa/validator/set')
require('@stopsopa/validator/get')
require('@stopsopa/validator/delay')
require('@stopsopa/validator/each')
require('@stopsopa/validator/size')

Other similar libraries:

next generation

  • or validator
  • condition validator
  • respecting order of validators - executing in the same order as declared