View on GitHub

Schema-inspector

A powerful tool to sanitize and validate JS objects.

download .ZIPdownload .TGZ

Schema-Inspector

Schema-Inspector is a powerful tool to sanitize and validate JS objects. It's designed to work both client-side and server-side and to be scalable with allowing asynchronous and synchronous calls.

Try it by yourself

Update the data, sanitization and validation tabs to see in real-time the result of the Schema-inspector.

Data

- See the sanitization doc

- See the validation doc

Installation

Node.js

npm install schema-inspector

Browser

bower install schema-inspector

(Or download async.js and schema-inspetor.js manually).

<script type="text/javascript" src="bower_components/async/lib/async.js"></script>
<script type="text/javascript" src="bower_components/schema-inspector/lib/schema-inspector.js"></script>

Usage

Synchronous call

var inspector = require('schema-inspector');

var schema = {
    type: 'object',
	properties: {
		lorem: { type: 'string', eq: 'ipsum' },
		dolor: {
			type: 'array',
			items: { type: 'number' }
		}
	}
};

var candidate = {
	lorem: 'not_ipsum',
	dolor: [ 12, 34, 'ERROR', 45, 'INVALID' ]
};
var result = inspector.validate(schema, candidate); // Candidate is not valid
console.log(result.format());
/*
    Property @.lorem: must be equal to "ipsum", but is equal to "not_ipsum"
    Property @.dolor[2]: must be number, but is string
    Property @.dolor[4]: must be number, but is string
*/

Asynchronous call

var inspector = require('schema-inspector');

var schema = { ...  };

var candidate = { ... };

inspector.validate(schema, candidate, function (err, result) {
console.log(result.format());
/*
    Property @.lorem: must be equal to "ipsum", but is equal to "not_ipsum"
    Property @.dolor[2]: must be number, but is string
    Property @.dolor[4]: must be number, but is string
*/
});

Custom fields

var inspector = require('schema-inspector');

var schema = {
	type: 'array',
	items: { type: 'number', $divisibleBy: 5 }
};

var custom = {
	divisibleBy: function (schema, candidate) {
		var dvb = schema.$divisibleBy;
		if (candidate % dvb !== 0) {
			this.report('must be divisible by ' + dvb);
		}
	}
};

var candidate = [ 5, 10, 15, 16 ];
var result = inspector.validate(schema, candidate, custom);
console.log(result.format());
/*
    Property @[3]: must be divisible by 5
*/

In the browser

<script type="text/javascript" src="async.js"></script>
<script type="text/javascript" src="schema-inspetor.js"></script>
<script type="text/javascript">
var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'string', eq: 'ipsum' },
		dolor: {
			type: 'array',
			items: { type: 'number' }
		}
	}
};

var candidate = {
	lorem: 'not_ipsum',
	dolor: [ 12, 34, 'ERROR', 45, 'INVALID' ]
};
SchemaInspector.validate(schema, candidate, function (err, result) {
	alert(result.format());
});
</script>

In the example below, the inspector variable will be used, for the client-side, use SchemaInspector instead of inspector.

Documentation

Validation

Sanitization

Custom fields

Asynchronous call

Thanks to:

Validation

type

Allow to check property type. If the given value is incorrect, then type is not checked.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: {  type: 'number' },
		ipsum: { type: 'any' },
		dolor: { type: ['number' 'string', 'null'] }
	}
};

var c1 = {
	lorem: 12,
	ipsum: 'sit amet',
	dolor: 23
};
var c2 = {
	lorem: 12,
	ipsum: 34,
	dolor: 'sit amet'
};
var c3 = {
	lorem: 12,
	ipsum: [ 'sit amet' ],
	dolor: null
};
var c4 = {
	lorem: '12',
	ipsum: 'sit amet',
	dolor: new Date()
};
inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Valid
inspector.validate(schema, c3); // Valid
inspector.validate(schema, c4); // Invalid: @.lorem must be a number, @dolor must be a number, a string or null

optional

This field indicates whether or not property has to exist.

Example

var inspector = require('schema-inspector');

var schema1 = {
	type: 'object',
	properties: {
		lorem: { type: 'any', optional: true }
	}
};

var schema2 = {
	type: 'object',
	properties: {
		lorem: { type: 'any', optional: false } // default value
	}
};

var c1 = { lorem: 'ipsum' };
var c2 = { };

inspector.validate(schema1, c1); // Valid
inspector.validate(schema1, c2); // Valid
inspector.validate(schema2, c1); // Valid
inspector.validate(schema2, c2); // Invalid: "@.lorem" is missing and not optional

uniqueness

If true, then we ensure no element in candidate exists more than once.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'array',
	uniqueness: true
};

var c1 = [12, 23, 34, 45];
var c2 = [12, 23, 34, 12];

inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Invalid: 12 exists twice in @.

pattern

Ask Schema-Inspector to check whether or not a given matches provided patterns. When a pattern is a RegExp, it directly test the string with it. When it's a string, it's an alias of a RegExp.

Example

var inspector = require('schema-inspector');

var schema1 = {
	type: 'array',
	items: { type: 'string', pattern: /^[A-C]/ }
};

var c1 = ['Alorem', 'Bipsum', 'Cdolor', 'DSit amet'];

var schema2 = {
	type: 'array',
	items: { type: 'string', pattern: 'email' }
};

var c2 = ['lorem@ipsum.com', 'dolor@sit.com', 'amet@consectetur'];

inspector.validate(schema1, c1); // Invalid: @[3] ('DSit amet') does not match /^[A-C]/
inspector.validate(schema2, c2); // Invalid: @[2] ('amet@consectetur') does not match "email" pattern.

minLength, maxLength, exactLength

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'string', minLength: 4, maxLength: 8 },
		ipsum: { type: 'array', exactLength: 6 },
	}
};
var c1 = {
	lorem: '12345',
	ipsum: [1, 2, 3, 4, 5, 6]
};

var c2 = {
	lorem: '123456789',
	ipsum: [1, 2, 3, 4, 5]
};

inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Invalid: @.lorem must have a length between 4 and 8 (here 9)
// and @.ipsum must have a length of 6 (here 5)

lt, lte, gt, gte, eq, ne

Check whether comparison is true:

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'number', gt: 0, lt: 5 }, // Between ]0; 5[
		ipsum: { type: 'number', gte: 0, lte: 5 }, // Between [0; 5]
		dolor: { type: 'number', eq: [0, 3, 6, 9] }, // Equal to 0, 3, 6 or 9
		sit: { type: 'number', ne: [0, 3, 6, 9] } // Not equal to 0, 3, 6 nor 9
	}
};

var c1 = { lorem: 3, ipsum: 0, dolor: 6, sit: 2 };
var c2 = { lorem: 0, ipsum: -1, dolor: 5, sit: 3 };

inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Invalid

someKeys

Check whether one of the given keys exists in object (useful when they are optional).

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	someKeys: ['lorem', 'ipsum']
	properties: {
		lorem: { type: 'any', optional: true },
		ipsum: { type: 'any', optional: true },
		dolor: { type: 'any' }
	}
};

var c1 = { lorem: 0, ipsum: 1, dolor: 2  };
var c2 = { lorem: 0, dolor: 2  };
var c3 = { dolor: 2  };

inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Valid
inspector.validate(schema, c3); // Invalid: Neither @.lorem nor @.ipsum is in c3.

strict

Only keys provided in field "properties" may exist in object.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	strict: true,
	properties: {
		lorem: { type: 'any' },
		ipsum: { type: 'any' },
		dolor: { type: 'any' }
	}
};

var c1 = { lorem: 0, ipsum: 1, dolor: 2  };
var c2 = { lorem: 0, ipsum: 1, dolor: 2, sit: 3  };

inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Invalid: @.sit should not exist.

exec

Custom checker =). "exec" functions take two three parameter (schema, post [, callback]). To report an error, use this.report([message]). Very useful to make some custom validation.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: {
			type: 'number',
			exec: function (schema, post) {
				// here scheme === schema.properties.lorem and post === @.lorem
				if (post === 3) {
					// As soon as `this.report()` is called, candidate is not valid.
					this.report('must not equal 3 =('); // Ok...it's exactly like "ne: 3"
				}
			}
		}
	}
};

var c1 = { lorem: 2 };
var c2 = { lorem: 3 };

inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Invalid: "@.lorem must not equal 3 =(".

properties

For each property in the field "properties", whose value must be a schema, validation is called deeper in object.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: {
			type: 'object',
			properties: {
				ipsum: {
					type: 'object',
					properties: {
						dolor: { type: 'string' }
					}
				}
			}
		},
		consectetur: { type: 'string' }
	}
};

var c1 = {
	lorem: {
		ipsum: {
			dolor: 'sit amet'
		}
	},
	consectetur: 'adipiscing elit'
};
var c2 = {
	lorem: {
		ipsum: {
			dolor: 12
		}
	},
	consectetur: 'adipiscing elit'
};

inspector.validate(schema, c1); // Valid
inspector.validate(schema, c2); // Invalid: @.lorem.ipsum.dolor must be a string.

items

Allow to apply schema validation for each element in an array. If it's an object, then it's a schema which will be used for all the element. If it's an array of object, then it's an array of schema and each element in an array will be checked with the schema which has the same position in the array.

Example

var inspector = require('schema-inspector');

var schema1 = {
	type: 'array',
	items: { type: 'number' }
};

var schema2 = {
	type: 'array',
	items: [
		{ type: 'number' },
		{ type: 'number' },
		{ type: 'string' }
	]
};

var c1 = [1, 2, 3];
var c2 = [1, 2, 'string!'];


inspector.validate(schema1, c1); // Valid
inspector.validate(schema1, c2); // Invalid: @[2] must be a number.
inspector.validate(schema2, c1); // Invalid: @[2] must be a string.
inspector.validate(schema2, c2); // Valid

alias

Allow to display a more explicit property name if an error is encounted.

Example

var inspector = require('schema-inspector');

var schema1 = {
	type: 'object',
	properties: {
		_id: { type: 'string'}
	}
};

var schema2 = {
	type: 'object',
	properties: {
		_id: { alias: 'id', type: 'string'}
	}
};

var c1 = { _id: 1234567890 };

var r1 = inspector.validate(schema1, c1);
var r2 = inspector.validate(schema2, c1);
console.log(r1.format()); // Property @._id: must be string, but is number
console.log(r2.format()); // Property id (@._id): must be string, but is number

error

This field contains a user sentence for displaying a more explicit message if an error is encounted.

Example

var inspector = require('schema-inspector');

var schema1 = {
	type: 'object',
	properties: {
		_id: { type: 'string' }
	}
};

var schema2 = {
	type: 'object',
	properties: {
		_id: { type: 'string', error: 'must be a valid ID.' }
	}
};

var c1 = { _id: 1234567890 };

var r1 = inspector.validate(schema1, c1);
var r2 = inspector.validate(schema2, c1);
console.log(r1.format()); // Property @._id: must be string, but is number.
console.log(r2.format()); // Property @._id: must be a valid ID.

Sanitization

type

Cast property to the given type according to the following description:

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'array',
	items: { type: 'string' }
};

var c = [12.23, -34, true, false, 'true', 'false', [123, 234, 345]];

var r = inspector.sanitize(schema, c);
/*
    c: ['12.23', '-34', 'true', 'false', 'true', 'false', '123,234,345']
*/

def

Define default value if property does not exist, or if type casting is to fail because entry type is not valid (cf type).

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'number', def: 10 },
		ipsum: { type: 'string', def: 'NikitaJS', optional: false },
		dolor: { type: 'string' }
	}
};

var c = {
	lorem: [12, 23],    // convertion to number is about to fail
										// (array -> number is not possible)
										// ipsum is not privided
	dolor: 'sit amet' // "dolor" is already a string
};

var r = inspector.sanitize(schema, c);
/*
    c: {
        lorem: 10,
        ipsum: 'NikitaJS',
        dolor: 'sit amet'
    }
*/

optional

Property is set to schema.def if not provided and if optional is false.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'number', optional: false, def: 12 },
		ipsum: { type: 'string', optional: true, def: 23 },
		dolor: { type: 'string', def: 'NikitaJS, def: 34 } // (optional: true)
	}
};

var c = { };

var r = inspector.sanitize(schema, c);
/*
    c: {
        lorem: 12 // Only lorem is set to 12 because it is not optional.
    }
*/

rules

Apply the given rule to a string. If several rules are given (array), then they are applied in the same order than in the array.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'string', rules: 'upper' },
		ipsum: { type: 'string', rules: [ 'trim', 'title'] }
	}
};

var c = {
	lorem: ' tHiS is sParTa! ',
	ipsum: '   tHiS is sParTa!    '
};

var r = inspector.sanitize(schema, c);
/*
    c: {
        lorem: ' THIS IS SPARTA! ',
        ipsum: 'This Is Sparta!' // has been trimed, then titled
    }
*/

min, max

Define minimum and maximum value for a property. If it's less than minimum, then it's set to minimum. If it's greater than maximum, then it's set to maximum.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'array',
	items: { type: 'number', min: 10, max: 20 }
};

var c = [5, 10, 15, 20, 25];

var r = inspector.sanitize(schema, c);
/*
    c: [10, 10, 15, 20, 20]
    c[0] (5) was less than min (10), so it's been set to 10.
    c[4] (25) was greater than max (20), so it's been set to 20.
*/

minLength, maxLength

Adjust string length to the given number.

TODO: We must be able to choose which character we want to fill the string with.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'array',
	items: { type: 'string', minLength: 8, maxLength: 11 }
};

var c = ['short', 'mediumSize', 'tooLongForThisSchema'];

var r = inspector.sanitize(schema, c);
/*
    c: ['short---', 'mediumSize', 'tooLongForT']
*/

strict

Only keys provided in field "properties" will exist in object, others will be deleted.

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	strict: true,
	properties: {
		good: { type: 'string' }
	}
};

var c = { good: 'yes', bad: 'nope' };

var r = inspector.sanitize(schema, c);
/*
    c: { good: 'yes' }
*/

exec

Custom checker =). "exec" functions take two three parameter (schema, post [, callback]), and must return the new value. To report an sanitization, use this.report([message]). Very useful to make some custom sanitization.

NB: If you don't want to return a differant value, simply return post, do not return nothing (if you do so, the new value will be undefined).

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'array',
	items: {
		type: 'string',
		exec: function (schema, post) {
			if (typeof post === 'string' && !/^nikita$/i.test(post)) {
				this.report();
				return '_INVALID_';
			}
			return post;
		}
	}
};

var c = [ 'Nikita', 'lol', 'NIKITA', 'thisIsGonnaBeSanitized!' ];

var r = inspector.sanitize(schema, c);
/*
    c: [ 'Nikita', '_INVALID_', 'NIKITA', '_INVALID_' ]
*/

properties

Work the same way as validation "properties".


items

Work the same way as validation "items".

Custom fields

punctual use

When you need to use the same function in exec field several time, instead of saving the function and declaring exec several times, just use custom field. First you have to provide a hash containing a function for each custom field you want to inject. Then you can call them in your schema with $"your field name". For example if you provide a custom field called "superiorMod", you can access it with name "$superiorMod".

Example

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'number', $divisibleBy: 5 },
		ipsum: { type: 'number', $divisibleBy: 3 }
	}
};

var custom = {
	divisibleBy: function (schema, candidate) {
		var dvb = schema.$divisibleBy;
		if (cndidate % dvb !== 0) {
			this.report('must be divisible by ' + dvb);
		}
	}
};

var c = {
	lorem: 10,
	ipsum: 8
};
inspector.validate(schema, candidate, custom); // Invalid: "@.ipsum must be divisible by 3"

extension

Sometime you want to use a custom field everywhere in your program, so you may extend Schema-Inspector to do so. Just call the method inspector.Validation.extend(customFieldObject) or inspector.Sanitization.extend(customFieldObject). If you want to reset, simply call inspector.Validation.reset() or inspector.Sanitization.reset(). You also can remove a specific field by calling inspector.Validation.remove(field) or inspector.Sanitization.remove(field).

Example

var inspector = require('schema-inspector');

var custom = {
	divisibleBy: function (schema, candidate) {
		var dvb = schema.$divisibleBy;
		if (cndidate % dvb !== 0) {
			this.report('must be divisible by ' + dvb);
		}
	}
};

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'number', $divisibleBy: 5 },
		ipsum: { type: 'number', $divisibleBy: 3 }
	}
};

inspector.Validation.extend(custom);

var candidate = {
	lorem: 10,
	ipsum: 8
};

inspector.validate(schema, candidate);
/*
    As you can see, no more object than schema and candidate has been provided.
    Therefore we can use `$divisibleBy` everywhere in all schemas, for each
    inspector.validate() call.
*/

Context

Every function you declare as a custom parameter, or with exec field will be called with a context. This context allows you to access properties, like this.report() function, but also this.origin, which is equal to the object sent to inspector.validate() or inspector.sanitize().

Example

// ...
var schema = { ... };
var custom = {
	divisibleBy: function (schema, candidate) {
		// this.origin === [12, 23, 34, 45]
		// ...
	}
};
var candidate = [12, 23, 34, 45];
var result = inspector.validate(schema, candidate, custom);
// ...

Asynchronous call

How to

All of the examples above used synchronous call (the simplest). But sometimes you want to call validation or sanitization asynchronously, in particular with exec and custom fields. It's pretty simple: To do so, just send a callback as extra parameter. It takes 2 parameters: error and result. Actually Schema-Inspector should send back no error as it should not throw any if called synchronously. But if you want to send back and error in your custom function, inspection will be interrupted, and you will be able to retrieve it in your callback.

You also have to declare a callback in your exec or custom function to make Schema-Inspector call it asynchronously, else it will be call synchronously. That means you may use exec synchronous function normally even during and asynchronous call.

Example

var inspector = require('schema-inspector');

var schema = { ...  };
var candidate = { ... };

inspector.validate(schema, candidate, function (err, result) {
	console.log(result.format());
});

Example with custom field

var inspector = require('schema-inspector');

var schema = { ...  };
var candidate = { ... };
var custom = { ... };

inspector.validate(schema, candidate, custom, function (err, result) {
	console.log(result.format());
});

Here is a full example where you may have to use it:

var inspector = require('schema-inspector');

var schema = {
	type: 'object',
	properties: {
		lorem: { type: 'number', $divisibleBy: 4 },
		ipsum: { type: 'number', $divisibleBy: 5 },
		dolor: { type: 'number', $divisibleBy: 0, optional: true }
	}
};

var custom = {
	divisibleBy: function (schema, candidate, callback) { // Third parameter is declared:
		// Schema-Inspector will wait this function to call this `callback` to keep running.
		var dvb = schema.$divisibleBy;
		if (typeof dvb !== 'number' || typeof candidate !== 'number') {
			return callback();
		}
		var self = this;
		process.nextTick(function () {
			if (dvb === 0) {
				return callback(new Error('Schema error: Divisor must not equal 0'));
			}
			var r = candidate / dvb;
			if ((r | 0) !== r)  {
				self.report('should be divisible by ' + dvb);
			}
			callback();
		});
	}
};

var candidate = {
	lorem: 12,
	ipsum: 25
};

inspector.validate(schema, candidate, custom, function (err, result) {
	console.log(result.format());
});