Skip to content
&y edited this page Nov 21, 2016 · 6 revisions

Blueprint is a validation tool. It can be use to:

  • Validate or require properties
  • Validate or require arguments

Usage

Node

Blueprint is part of the polyn package. To install it:

npm install --save polyn

Then you can require it like so:

var Blueprint = require('polyn').Blueprint;

Browser

Immutable is part of the polyn package. To install it, download the release folder, or:

bower install --save polyn

Then add a script tag:

<script src="polyn.min.js"></script>

Then it will be available on the window:

var Blueprint = window.polyn.Blueprint;

Types

Blueprint supports several type validations, in addition to regular expressions, and custom validation. The following example demonstrates all of the types that are supported in Blueprint.

var fooBlueprint = new Blueprint({
    num: 'number',
    str: 'string',
    arr: 'array',
    currency: 'money',
    bool: 'bool',
    date: 'datetime',
    obj: 'object',
    regex: 'regexp',
    expression1: /^[a-zA-Z]+$/,
    expression2: {
        type: 'expression',
        expression: /^[a-zA-Z]+$/
    },
    func1: 'function',
    func2: {
        type: 'function',
        args: ['arg1', 'arg2']
    },
    dec: {
        type: 'decimal',
        places: 2
    },
    nullable: {
        type: 'string',
        required: false
    },
    custom: {
        validate: function (val, errors, self) {
            if (val !== 42) {
                errors.push('custom must be 42');
            }            
        }
    }
});

With the Blueprint above, we can check to see if a given object's signature matches the blueprint:

var foo = {
    num: 42,
    str: 'string',
    arr: [],
    currency: '42.42',
    bool: true,
    date: new Date(),
    obj: {
        foo: 'bar'
    },
    regex: /[A-B]/,
    expression1: 'ABCabc',
    expression2: 'ABCabc',
    func1: function () {},
    func2: function (arg1, arg2) {},
    dec: 42.42,
    custom: 42
};

Blueprint.validate(fooBlueprint, foo, function (errors, result) {
    if (errors) {
        throw new Error('foo does not implement fooBlueprint!');
    }

    // do something with foo
});

Validation

Blueprint supports both synchronous and asynchronous validation.

This example validates an object against a blueprint, asynchronously:

var blueprint = new Blueprint({
    name: 'string'
});

Blueprint.validate(blueprint, { name: 'Andy' }, function (errors, result) {
    console.log('errors:', result1.errors); // prints errors: undefined
    console.log('result:', result1.result); // prints result: true
});

Blueprint.validate(blueprint, { title: 'error' }, function (errors, result) {
    console.log('errors:', result2.errors); // prints errors: ['...']
    console.log('result:', result2.result); // prints result: false
});

This example validates an object against a blueprint, synchronously:

var blueprint = new Blueprint({
    name: 'string'
});

var result1 = Blueprint.validate(blueprint, { name: 'Andy' });
console.log('errors:', result1.errors); // prints errors: undefined
console.log('result:', result1.result); // prints result: true

var result2 = Blueprint.validate(blueprint, { title: 'error' });
console.log('errors:', result2.errors); // prints errors: ['...']
console.log('result:', result2.result); // prints result: false

Inline validation

Blueprints also support inline validation.

This example validates an object against a blueprint, asynchronously:

var myBlueprint = new Blueprint({
    name: 'string'
});

myBlueprint.validate({ name: 'Andy' }, function (errors, result) {
    console.log('errors:', result1.errors); // prints errors: undefined
    console.log('result:', result1.result); // prints result: true
});

myBlueprint.validate({ title: 'error' }, function (errors, result) {
    console.log('errors:', result2.errors); // prints errors: ['...']
    console.log('result:', result2.result); // prints result: false
});

This example validates an object against a blueprint, synchronously:

var myBlueprint = new Blueprint({
    name: 'string'
});

var result1 = myBlueprint.validate({ name: 'Andy' });
console.log('errors:', result1.errors); // prints errors: undefined
console.log('result:', result1.result); // prints result: true

var result2 = myBlueprint.validate({ title: 'error' });
console.log('errors:', result2.errors); // prints errors: ['...']
console.log('result:', result2.result); // prints result: false

Blueprint Merging and Inheritance

Blueprints can inherit other Blueprints

var IFoo,
    IFooBar;

// IFoo requires the "name" property, which must be a string
IFoo = new Blueprint({
    name: 'string'
});

// IFooBar requires the "description" property, which must be a string
IFooBar = new Blueprint({
    description: 'string'
});

// IFooBar now requires the "name" and "description" properties, which must be strings
IFooBar.inherits(IFoo);

You can also merge several blueprints. When merging, the order of precedence is from left to right. So if properties exist in multiple blueprints, the properties from blueprint with the lower index in the array will be used.

var IFoo,
    IBar,
    IDescribe,
    IAll;

// IFoo requires the "name" property, which must be a string
IFoo = new Blueprint({
    name: 'string'
});

// IBar requires the "title" property, which must be a string
IBar = new Blueprint({
    title: 'string'
});

// IDescribe requires the "description" property, which must be a string
IDescribe = new Blueprint({
    description: 'string'
});

// IAll now requires the "name","title", and "description" properties, which must be strings
IAll = Blueprint.merge([IFoo, IBar, IDescribe]);

// OR Asynchronously
Blueprint.merge([IFoo, IBar, IDescribe], function (err, blueprint) {
    IAll = blueprint;
});

Custom Validation

You can override the Blueprint validation for any property by setting the value of that property to an object literal with a validation function on it. The validation function receives three arguments:

  • propertyValue: The value of the implementation property that matches on property name
  • errorArray (array): If your validation encounters any errors, they need to be pushed into this array. If the array is empty, validation passes, if it has any values, validation returns false.
  • implementation: The implementation that is being validated against the blueprint. This is supplied so you can validate across properties if necessary.
var ICustomFoo = new Blueprint({
    name: 'string',
    doSomething: {
        type: 'function',
        args: ['arg1', 'arg2']
    },
    meaningOfLife: {
        validate: function (meaningOfLife, errorArray, superComputer) {
            if (superComputer.isReady && meaningOfLife !== 42) {
                errorArray.push('Sorry, that is not the answer to the meaning of life, the universe and everything');
            }
        }
    }
});

The validate function accepts two arguments. The first argument is the property on the implementation that matches the name of the property that is doing the validation. The second argument is the error array. If you push a message into the error array, it will be passed to the callback of the validate function, in the first parameter, and the result of validate will be false. If your validation is truthy, take no action.

Nullable Properties

One great use for Blueprints is to validate a payload coming from a client, or to validate options being passed into a constructor. Sometimes, we don't require a property but would like to validate the property if a value is assigned. That's where the required argument comes in.

Any property can be nullable by setting required to false. When this is so, the argument will be ignored when it is null or undefined. When the argument has a value, that value will be validated appropriately.

Nested Blueprints

If a property of your Blueprint should implement another Blueprint, you can register the blueprint as such:

var INestedFoo = new Blueprint({
    name: 'string',
    doSomething: {
        type: 'function',
        args: ['arg1', 'arg2']
    },
    implementsSomething: {
        type: 'blueprint',
        blueprint: new Blueprint({
            name: 'string',
            description: 'string'
        })
    }
});

Single Property Validation

You can also check validation on single properties, using validateProperty, for when it doesn't make sense to validate an entire object.

These examples validate a value against a specific blueprint property, asynchronously:

var schema = new Blueprint({
    name: 'string'
});

Blueprint.validateProperty(schema, 'name', 'Trillian', function (errors, result) {
    console.log('errors:', result1.errors); // prints errors: undefined
    console.log('result:', result1.result); // prints result: true
});

Blueprint.validateProperty(schema, 'name', 123, function (errors, result) {
    console.log('errors:', result2.errors); // prints errors: ['...']
    console.log('result:', result2.result); // prints result: false
});

schema.validateProperty('name', 'Trillian', function (errors, result) {
    console.log('errors:', result1.errors); // prints errors: undefined
    console.log('result:', result1.result); // prints result: true
});

These examples validate a value against a specific blueprint property, synchronously:

var schema = new Blueprint({
    name: 'string'
});

var result1 = Blueprint.validate(schema, 'name', 'Trillian');
console.log('errors:', result1.errors); // prints errors: undefined
console.log('result:', result1.result); // prints result: true

var result2 = Blueprint.validate(schema, 'name', 123);
console.log('errors:', result2.errors); // prints errors: ['...']
console.log('result:', result2.result); // prints result: false

var result3 = schema.validate('name', 'Trillian');
console.log('errors:', result3.errors); // prints errors: undefined
console.log('result:', result3.result); // prints result: true

This can also be used to validate without creating a Blueprint:

Blueprint.validateProperty({ name: 'string' }, 'name', 'Trillian', function (errors, result) {
    console.log('errors:', result1.errors); // prints errors: undefined
    console.log('result:', result1.result); // prints result: true
});

var result1 = Blueprint.validate({ name: 'string' }, 'name', 'Trillian');
console.log('errors:', result1.errors); // prints errors: undefined
console.log('result:', result1.result); // prints result: true

Debugging

It can be difficult to tell where our error messages are coming from, if the property names aren't unique within our app. We can use the __blueprintId to help with that. By setting it to something we understand, all validation error messages will indicate what Blueprint they were generated by.

var Person = new Blueprint({
    __blueprintId: 'Person',
    name: 'string'
});

Configuration

You can configure Blueprint to change some of it's behaviors, with Blueprint.configure.

Compatibility

We'll do our best to keep Blueprint backwards compatible, so you can update it without changing behaviors. To do this, Blueprint uses dates with yyyy-MM-dd formatting for compatibility. This keeps it simple for you. When you first start using Blueprint, configure it with the date you started developing:

Blueprint.configure({
    compatibility: '2016-11-21' // put todays date in yyyy-MM-dd format
});

Remember Validation

By default (for backwards compatibility), implementations keep track of being audited by a blueprint. Future validation passes assume no changes were made. This is no longer the recommended usage, because it can cause false-positives.

To change this behavior:

Blueprint.configure({
    rememberValidation: false
});

OR

Blueprint.configure({
    compatibility: '2016-11-20' // anything after 2016-11-19
});