tsa v3.1.0
tsa
Guard your REST API with a bit of fascism.
TSA is a node.js library designed to take JSON input and:
- filter it against a whitelist
- validate it
- transform it
- provide default values
It has been designed with usage in an Express-based JSON REST API in mind, and allows you to easily pass it into your route as middleware.
Table of Contents
- Installation
- Usage
- Usage : Using TSA Directly
- Usage : Using TSA via Express Middleware
- Creating Guards
- Creating Guards : Nested Guards
- Creating Guards : Required Properties
- Creating Guards : Optional Properties
- Creating Guards : Whitelisting
- Creating Guards : Default Values
- Creating Guards : Built-In Validations
- Creating Guards : Custom Validations
- Creating Guards : Transformations
- Creating Guards : Sanitization
- Creating Guards : Rename Properties
- Creating Guards : Combinations
- Creating Guards : Error Handling
- Test
- License
- Author
Installation (via npm)
$ npm install tsa
Usage
Using TSA Directly
Create a guard:
var tsa = require('tsa');
var guard = tsa({
property1: tsa.required()
, property2: tsa.optional()
, property3: tsa.default('blah')
});
Validate input against guard:
var input = {
property1: 'foo'
, property4: 'bar'
};
guard().frisk(input, function(err, result){
// err === null
// result.property1 === 'foo'
// result.property2 === undefined
// result.property3 === 'blah'
// result.property4 === undefined
});
Using TSA via Express Middleware
Create a guard:
var tsa = require('tsa');
var guard = tsa({
property1: tsa.required()
, property2: tsa.optional()
});
Ensure you're using express's body parser:
app.use(express.bodyParser());
Add that guard's middleware to your route:
app.post('/foo', guard().middleware(), function(req, res){
// req.body is the whitelisted, validated, transformed version of the input from req.body
});
app.error(function(err, req, res, next){
// err is an array of errors generated by the guard
});
Alternatively you can handle the errors on a per-route basis instead of globally:
app.post('/foo', guard().middleware(function(err, req, res, next){
// return a 400, show an error page, ignore by calling next, whatever
}), function(req, res){
// req.body is the whitelisted, validated, transformed version of the input from req.body
});
Creating Guards
Nested Guards
var address = tsa({
street1: tsa.required()
, street2: tsa.optional()
});
var person = tsa({
name: tsa.required()
, address: address()
});
Nested guards can also be created inline:
var person = tsa({
name: tsa.required()
, address: tsa({
street1: tsa.required()
, street2: tsa.optional()
})()
});
You can validate/transform/etc nested guards either at the definition level, or the usage level:
// example of adding validations to guard definition
var address = tsa({
street1: tsa.required()
, street2: tsa.optional()
}, {validate: someValidationFunction});
// example of adding validations to guard usage
var person = tsa({
name: tsa.required()
, address: address({validate: aDifferentValidationFunction})
});
Required Properties
var guard = tsa({
property1: tsa.property({ required: true }) // or: tsa.required()
});
var input = {};
guard().frisk(input, function(err, result){
// err === instanceof Array
// err[0] === {key: 'property1', error: 'Required property property1 not supplied.'}
});
You can provide a custom error message like so:
var guard = tsa({
example1: tsa.property({ required: 'fail!' })
, example2: tsa.required('fail!')
});
Optional Properties
var guard = tsa({
property1: tsa.property() // or: tsa.optional()
});
var input = {};
guard().frisk(input, function(err, result){
// err === null
// result === {}
});
Whitelisting
var guard = tsa({
property1: tsa.required()
});
var input = {
property1: 'foo'
, property2: 'bar'
};
guard().frisk(input, function(err, result){
// result.property1 === 'foo'
// result has no property2 key
});
Default Values
var guard = tsa({
foo: tsa.property({ default: 'bar' }) // or: tsa.default('bar')
});
var input = {};
guard().frisk(input, function(err, result){
// err === null
// result.foo === 'bar'
});
Optionally, the default value can be a function which will be executed by tsa:
var now = function(){
return new Date();
};
var guard = tsa({
foo: tsa.property({ default: now }) // or: tsa.default(now)
});
var input = {};
guard().frisk(input, function(err, result){
// err === null
// result.foo === a Date object
});
Built-In Validations
TSA ships with a few validations built-in. Here are some examples:
var guard = tsa({
foo: tsa.require({ validate: tsa.validate.boolean() })
, bar: tsa.require({ validate: tsa.validate.boolean('The value "%1" is not a boolean.') }) // <- custom error message
, baz: tsa.require({ validate: tsa.validate.true() })
, boo: tsa.require({ validate: tsa.validate.false() })
});
var guard = tsa({
foo: tsa.require({ validate: tsa.validate.numeric() })
, foo2: tsa.require({ validate: tsa.validate.numeric('fail!') }) // <- custom error message
, bar: tsa.require({ validate: tsa.validate.range(0, 10) })
, bar2: tsa.require({ validate: tsa.validate.range(0, 10, {
invalid: 'custom error message'
, below: 'custom error message'
, above: 'custom error message'
}) })
, baz: tsa.require({ validate: tsa.validate.min(0) })
, baz2: tsa.require({ validate: tsa.validate.min(0, {
invalid: 'custom error message'
, below: 'custom error message'
}) })
, boo: tsa.require({ validate: tsa.validate.max(10) })
, boo2: tsa.require({ validate: tsa.validate.max(10,
invalid: 'custom error message'
, above: 'custom error message'
}) })
});
var guard = tsa({
foo: tsa.require({ validate: tsa.validate.regex(/^bar$/g) })
, foo: tsa.require({ validate: tsa.validate.regex(/^bar$/g, 'fail!') }) // <- custom error message
});
Custom Validations
var mustBeUpper = function(input, cb){
if(input.toUpperCase() === input){
cb(); // yes, this is uppercase
}else{
cb('not uppercase!'); // oh noes!
}
};
var guard = tsa({
foo: tsa.property({ validate: mustBeUpper }) // or: tsa.validate(mustBeUpper)
});
var input = { foo: 'bar' };
guard().frisk(input, function(err, result){
// err[0] === {key: 'foo', error: 'not uppercase!'}
// result === null
});
Your custom validations can return multiple errors, if necessary:
var myValidationFunction = function(input, cb){
if(...){
cb(); // passed!
}else{
cb(['error message 1', 'error message 2']); // failed...
}
};
Transformations
var toUpper = function(input, cb){
cb(null, input.toUpperCase());
};
var guard = tsa({
foo: tsa.property({ transform: toUpper }) // or: tsa.transform(toUpper)
});
var input = { foo: 'bar' };
guard().frisk(input, function(err, result){
// err === null
// result.foo === 'BAR'
});
Sanitization
Sanitizing a property runs a validation function against it, but rather than failing the guard if an error is reported that property is simply thrown away in the case of an error:
var mustBeUpper = function(input, cb){
if(input.toUpperCase() === input){
cb(); // yes, this is uppercase
}else{
cb('not uppercase!'); // oh noes!
}
};
var guard = tsa({
foo: tsa.property({ sanitize: mustBeUpper }) // or: tsa.sanitize(mustBeUpper)
, fizz: tsa.sanitize(mustBeUpper)
});
var input = { foo: 'bar', fizz: 'BANG' };
guard().frisk(input, function(err, result){
// err === null
// result.foo === undefined
// result.fizz === 'BANG'
});
Note that you can run TSA's built-in validations through sanitize:
var guard = tsa({
foo: tsa.sanitize(tsa.validate.regex(/^bar$/g))
});
Rename Properties
var guard = tsa({
foo: tsa.property({ rename: 'bar' }) // or: tsa.rename('bar')
});
var input = { foo: 'blah' };
guard().frisk(input, function(err, result){
// result.foo === undefined
// result.bar === 'blah'
});
Combinations
You can combine any and all of the above like so:
var toUpper = function(input, cb){
cb(null, input.toUpperCase());
};
var guard = tsa({
foo: tsa.property({ required: true, transform: toUpper })
// or: tsa.required({ transform: toUpper })
// or: tsa.transform(toUpper, {required: true})
});
var input = { foo: 'bar' };
guard().frisk(input, function(err, result){
// err === null
// result.foo === 'BAR'
});
Error Handling
Errors for nested structures are returned like so:
[
{key: 'first', error: 'Required property not provided.'}
, {key: 'address', error: [
{key: 'street1', error: 'Required property not provided.'}
, {key: 'zip', error: 'Required property not provided.'}
]}
]
While this is a very structured format, it isn't always the easiest for
doing things like highlighting form fields that have errors. In those
situations you can pass the error structure into the tsa.flattenErrors
method to get back something like this:
[
{key: 'first', error: 'Required property not provided.'}
, {key: 'address[street1]', error: 'Required property not provided.'}
, {key: 'address[zip]', error: 'Required property not provided.'}
]
Passing {hash: true}
into tsa.flattenErrors
as the second argument results in:
{
'first': ['Required property not provided.']
, 'address[street1]': ['Required property not provided.']
, 'address[zip]': ['Required property not provided.']
}
Test
Run tests via mocha:
$ npm install -g mocha
$ git clone git://github.com/TroyGoode/node-tsa.git tsa
$ cd tsa/
$ npm install
$ mocha
Run example web app:
$ git clone git://github.com/TroyGoode/node-tsa.git tsa
$ cd tsa/
$ npm install
$ cd example/
$ npm install
$ npm start
$ open http://localhost:3000