-
Notifications
You must be signed in to change notification settings - Fork 1
Blueprint
Blueprint is a validation tool. It can be use to:
- Validate or require properties
- Validate or require arguments
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;
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;
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
});
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
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
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;
});
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.
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.
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'
})
}
});
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
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'
});
You can configure Blueprint to change some of it's behaviors, with Blueprint.configure
.
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
});
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
});