anchor
IMPORTANT: This is an unfinished work in progress! Please follow/star and keep up to date as the project develops.
If you're interested in contributing, drop me a note at @mikermcneil. I welcome your feedback, ideas, and pull requests :)
Anchor is a javascript library that lets you define strict types. It also helps you validate and normalize the usage of command line scripts and even individual functions.
This makes it really useful for things like:
- Form validation on the client or server
- Ensuring compliance with an API
- Ensuring that the proper arguments were passed into a function or command-line script
- Validating objects before storing them in a data store
- Normalizing polymorphic behaviors
Adds support for strongly typed arguments, like http://lea.verou.me/2011/05/strongly-typed-javascript/, but goes a step further by adding support for array and object sub-validation.
It's also the core validation library for the Sails ecosystem.
(Built on top of the great work with https://github.com/chriso/node-validator)
Installation
Client-side
<script type='text/javscript' src="/js/anchor.js"></script>
node.js
npm install anchor
Basic Usage
var anchor = require('anchor');
var userData = 'some string';
userData = anchor(userData).to('string');
anchor('something').to("string").error(function (err) {
});
Objects
var requirements = anchor({
name: 'string',
avatar: {
path: 'string'
name: 'string',
size: 'int',
type: 'string'
}
});
var userData = {
name: 'Elvis',
avatar: {
path: '/tmp/2Gf8ahagjg42.jpg',
name: '2Gf8ahagjg42.jpg',
size: 382944
type: 'image/jpeg'
}
};
anchor(userData).to(requirements);
Custom rules
Anchor also supports custom validation rules.
anchor.define('file').as({
name: 'string',
type: 'string',
size: 'int',
type: 'int'
});
anchor.define('supportedFruit').as(function (fruit) {
return fruit === 'orange' || fruit === 'apple' || fruit === 'grape';
});
anchor(someUserData).to({
name: 'string',
avatar: 'file'
});
anchor(someUserData).to({
fruit: 'supportedFruit'
});
We bundled a handful of useful defaults:
anchor(someUserData).to({
id: 'int',
name: 'string',
phone: 'phone',
creditcard: 'creditcard',
joinDate: 'date',
email: 'email',
twitterHandle: 'twitter'
});
The example below demonstrates the complete list of supported default data types:
anchor(userData).to({
id: 'int',
name: 'string',
phone: 'phone',
creditcard: 'creditcard',
joinDate: 'date',
email: 'email',
twitterHandle: 'twitter',
homepage: 'url',
someData: {},
favoriteColors: ['htmlcolor'],
badges: [{
name: 'string',
privileges: [{
id: 'int'
permission: 'string'
}]
}]
});
Functions
TODO: Support for functions is incomplete. If you'd like to contribute, please reach out at @balderdashy!
It also has built-in usage to verify the arguments of a function.
This lets you be confident that the arguments are what you expect.
$.get = anchor($.get).usage(
['urlish',{}, 'function'],
['urlish','function'],
['urlish',{}],
['urlish']
);
$.get('agasdg', {}, function (){})
$.get = anchor($.get).usage(
['urlish',{}, 'function'],
['urlish','function'],
['urlish',{}],
['urlish']
).error(function (err) {
});
Multiple usages and Argument normalization
But sometimes you want to support several different argument structures.
And to do that, you have to, either explicitly or implicitly, name those arguments so your function can know which one was which, irrespective of how the arguments are specified.
Here's how you specify multiple usages:
var getById = anchor(
function (args) {
$.get(args.endpoint, {
id: args.id
}, args.done);
})
.args({
endpoint: 'urlish',
id: 'int'
done: 'function'
})
.usage(
['endpoint', 'id', 'done'],
['endpoint', 'id']
);
Call it any way you want
Now the cool part. You can call your new function any of the following ways:
$.getById('/user',3,cb);
$.getById('/user',3);
Default values
You can also specify default values. If it's not required, if a value listed in defaults is undefined, the default value will be substituted. A value should not have a default AND be required-- one or the other.
Here's an example for an object's keys:
anchor(myObj)
.to({
id: 'int'
name: 'string',
friends: [{
id: 'int'
}]
})
.defaults({
name: 'Anonymous',
friends: [{
id: 'int',
name: 'Anonymous'
}]
})
And here's an example for a function's arguments:
anchor(myFunction)
.args({
id: 'int',
options: {}
})
.defaults({
}),
.usage(
['id'],
['options']
['id', 'options']
);
Asynchronous Usage / Promises
Anchor can also help you normalize your synchronous and asynchronous functions into a uniform api. It allows you to support both last-argument-callback (Node standard) and promise usage out of the box.
TODO
Express/Connect Usage
app.use(require('anchor'));
function createUser (req,res,next) {
var params = req.anchor.to({
id: 'int',
name: 'string'
});
async.auto([
user: User.create(params).done,
permission: Permission.create({
targetModel : 'user',
targetId : params.id,
UserId : params.id,
}).done
], function (err, results) {
var user = res.anchor(results.user).to({
id: 'int',
name: 'string',
email: 'email',
friends: [{
id: 'int',
name: 'string',
email: 'string'
}]
});
res.json({
user_id : user.id,
user_full_name : user.name,
user_email_address: user.email,
friends : user.friends
});
});
}
Sails Usage
Anchor is bundled with Sails. You can use anchor in Sails just like you would with the connect/express middleware integration, but you can also take an advantage of the extended functionality as seen below.
Here's an example of how you might right your create()
action to comply with a predefined API in Sails using built-in Anchor integration:
Note: In practice, you'd want to pull the function that creates the user and the permission out and put it in your model file, overriding the default User.create() behaviour.
var UserController = {
create: {
request : {
id : 'int',
name : 'string'
},
response : {
user_id : 'user.id'
user_full_name : 'user.name'
user_email_address : 'user.email'
friends : 'user.friends'
},
user : User.create
}
};
module.exports = UserController;
The model validation also uses anchor:
var User = {
adapter: 'mongo',
attributes: {
id: 'int',
name: 'string',
email: 'email',
friends: [{
id: 'int',
name: 'string',
email: 'string'
}]
},
create: function (values,cb) {
async.auto({
user: User.create(values).done,
permission: Permission.create({
targetModel : 'user',
targetId : values.id,
UserId : values.id,
}).done
], function (err, results) {
cb(err, results.user);
});
}
};
module.exports = User;
Tests
npm test
Who Built This?
Anchor was developed by @mikermcneil and is supported by Balderdash Co (@balderdashy).
Hopefully, it makes your life a little bit easier!
The MIT License (MIT)
Copyright © 2012-2013 Mike McNeil
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.