Skip to content

Instantly share code, notes, and snippets.

@weaver
Created August 9, 2010 23:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weaver/516316 to your computer and use it in GitHub Desktop.
Save weaver/516316 to your computer and use it in GitHub Desktop.
Express form validation #nodejs
//// form -- form validation
///
/// Represent the structure of a form and use it to validate form
/// data. Here's a Form that describes a create-account form:
///
/// var SignupForm = form.Form('Sign Up')
/// .text('account', 'Account Name', form.required(/[\w-\.]+/))
/// .email('email', 'Email', form.required())
/// .password('password', 'Password', form.required())
/// .password('confirm', 'Confirm', [form.required(), form.equal('password')]);
///
/// Each field has a name, title, and validator or sequence of
/// validators. It's easy to make custom validators; see the
/// "Validators" section at the end of this file.
///
/// The form above ius used by this view to process request data
/// before creating an account:
///
/// app.post('/account', function(req, res) {
/// var result = SignupForm.validate(req.body);
/// if (!result.isValid())
/// return res.send(result.errors(), 400);
///
/// var data = result.data,
/// profile = { email: data.email };
///
/// db.newAccount(data.account, data.password, profile, function(err, key) {
/// if (err)
/// res.send(err.toString(), 500);
/// else if (!key)
/// res.send([{ account: "This account already exists" }], 400);
/// else {
/// var uri = '/account/' + key.id;
/// res.send({ uri: uri }, { Location: uri }, 201);
/// }
/// });
/// });
///
exports.Form = Form;
exports.Field = Field;
exports.required = required;
exports.equal = equal;
/// --- Form
// A form is a sequence of fields. Fields participate in the
// validation process.
function Form(title) {
if (!(this instanceof Form))
return new Form(title);
this.title = title;
this.fields = {};
this.fieldList = [];
return this;
}
Form.prototype.text = field;
Form.prototype.password = field;
Form.prototype.email = field;
function field(name, title, valid) {
var field = new Field(name, title, wrapArray(valid));
this.fields[name] = field;
this.fieldList.push(field);
return this;
}
Form.prototype.validate = function validate(data) {
var form = this,
valid = new Validation(this, data),
value;
this.fieldList.forEach(function(field) {
try {
field.validate(valid);
} catch (exn) {
if (!(exn instanceof Invalid))
throw exn;
valid.errorList.push(exn);
}
});
return valid;
};
/// --- Fields
// A field has a name, title, and sequence of validators.
function Field(name, title, valid) {
this.name = name;
this.title = title;
this.valid = valid;
}
Field.prototype.validate = function validate(valid) {
valid.data[this.name] = this.validValue(valid.input[this.name], valid);
};
Field.prototype.validValue = function _validate(value, valid) {
var field = this,
result;
this.valid.forEach(function(validator) {
result = validator.call(field, value, valid);
if (result !== undefined)
value = result;
});
return value;
};
/// --- Validation
// When Form.validate() is called, a validation state is created.
// This state is used to track the valid data and any errors.
function Validation(form, input) {
this.form = form;
this.fields = form.fields;
this.input = input;
this.data = {};
this.errorList = [];
}
Validation.prototype.isValid = function isValid() {
return this.errorList.length == 0;
};
Validation.prototype.fail = function fail(field, reason) {
throw new Invalid(field, reason);
};
Validation.prototype.errors = function errors(fn) {
return this.errorList.map(fn || function(item) {
return { name: item.field.name, reason: item.toString() };
});
};
function Invalid(field, reason) {
this.field = field;
this.reason = reason;
}
Invalid.prototype.toString = function() {
return '"' + this.field.title + '" ' + this.reason + '.';
};
/// --- Validators
// A validator is called in the context of a field and is passed the
// field's value and the validation state. If a validator returns a
// value, that value is used as the new field value. If validation
// fails, valid.fail(this, "reason") should be returned.
function required(pattern) {
pattern = pattern || /\S+/;
return function required(value, valid) {
if (!value)
return valid.fail(this, 'is required');
else if (!pattern.test(value))
return valid.fail(this, "isn't formatted correctly.");
};
}
function equal(name) {
return function equal(value, valid) {
if (value != valid.input[name])
return valid.fail(this, 'must match "' + valid.fields[name].title + '"');
};
}
/// --- Aux
function wrapArray(value) {
if (value instanceof Array)
return value;
return (value === undefined) ? [] : [value];
}
//// Test form.js
///
/// Install Vows <http://vowsjs.org/>, and form.js, then run:
///
/// vows test-form.js
///
var assert = require('assert'),
sys = require('sys'),
form = require('form'),
vows = require('vows');
var SignupForm = form.Form('Sign Up')
.text('account', 'Account Name', form.required(/[\w-]+/))
.email('email', 'Email', form.required())
.password('password', 'Password', form.required())
.password('confirm', 'Confirm', [form.required(), form.equal('password')]);
vows.describe('Form').addBatch({
'With valid data,': {
topic: function() {
return SignupForm.validate({
account: 'some-user',
email: 'name@example.net',
password: 'foo',
confirm: 'foo',
junk: 'bar'
});
},
'the result': {
'is valid': function(topic) {
assert.ok(topic.isValid());
},
'has no errors': function(topic) {
assert.deepEqual(topic.errors(), []);
},
'contains valid data': function(topic) {
assert.deepEqual(topic.data, {
account: 'some-user',
email: 'name@example.net',
password: 'foo',
confirm: 'foo'
});
}
}
},
'But for invalid data,': {
topic: function() {
return SignupForm.validate({
account: 'me@example.net',
password: 'foo',
confirm: 'goo'
});
},
'the result': {
'is not valid': function(topic) {
assert.ok(!topic.isValid());
},
'has errors': function(topic) {
assert.deepEqual(
topic.errors(message),
['"Email" is required.', '"Confirm" must match "Password".']
);
}
}
}
}).export(module);
function message(invalid) {
return invalid.toString();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment