express-form
Advanced tools
Comparing version 0.8.1 to 0.10.0
# express-form changelog | ||
## v0.10.0 (2013/10/23) | ||
* add support for asynchronous custom validators/filters | ||
## v0.8.1 (2013/02/21) | ||
* cast to string for string-specific filters to prevent errors | ||
## v0.8.0 (2013/02/16) | ||
@@ -4,0 +10,0 @@ * use express 3.x for peer and dev dependencies |
@@ -7,2 +7,3 @@ var validator = require("validator") | ||
, object = require("object-additions").object | ||
, async = require("async") | ||
, utils = require("./utils"); | ||
@@ -28,3 +29,3 @@ | ||
this.run = function (source, form, options) { | ||
this.run = function (source, form, options, cb) { | ||
var self = this | ||
@@ -43,17 +44,40 @@ , errors = [] | ||
function runStack(foo) { | ||
function runStack(foo, cb) { | ||
stack.forEach(function (proc) { | ||
var result = proc(foo, source); // Pass source for "equals" proc. | ||
if (result.valid) return; | ||
async.eachSeries(stack, function(proc, cb) { | ||
if (proc.length == 3) { | ||
// run the async validator/filter | ||
return proc(foo, source, function(err, result) { | ||
if (err) { | ||
errors.push(err.message.replace("%s", fieldLabel)); | ||
return cb(null); | ||
} | ||
// filters return values | ||
if (result != null) { | ||
foo = result | ||
} | ||
cb(null); | ||
}); | ||
} | ||
// run the sync validator/filter | ||
var result = proc(foo, source); | ||
if (result.valid) return cb(null); | ||
if (result.error) { | ||
// If this field is not required and it doesn't have a value, ignore error. | ||
if (!utils.hasValue(value) && !self.__required) return; | ||
if (!utils.hasValue(value) && !self.__required) return cb(null); | ||
return errors.push(result.error.replace("%s", fieldLabel)); | ||
errors.push(result.error.replace("%s", fieldLabel)); | ||
return cb(null); | ||
} | ||
foo = result; | ||
cb(null); | ||
}, function(err) { | ||
cb(null, foo); | ||
}); | ||
return foo; | ||
} | ||
@@ -64,12 +88,15 @@ | ||
if (!Array.isArray(value)) value = [value]; | ||
value = value.map(runStack); | ||
async.mapSeries(value, runStack, function(err, value) { | ||
utils.setProp(property, form, value); | ||
cb(null, errors); | ||
}); | ||
} else { | ||
if (Array.isArray(value)) value = value[0]; | ||
value = runStack(value); | ||
runStack(value, function(err, value) { | ||
utils.setProp(property, form, value); | ||
cb(null, errors); | ||
}); | ||
} | ||
utils.setProp(property, form, value); | ||
if (errors.length) return errors; | ||
}; | ||
@@ -99,3 +126,21 @@ } | ||
Field.prototype.custom = function(func, message) { | ||
// custom function is async | ||
if (func.length == 3) { | ||
return this.add(function(value, source, cb) { | ||
func(value, source, function(err, result) { | ||
if (err) return cb(new Error(message || err.message || "%s is invalid")); | ||
// functions that return values are filters | ||
if (result != null) return cb(null, result); | ||
// value passed validator | ||
cb(null, null); | ||
}); | ||
}); | ||
} | ||
// custom function is sync | ||
return this.add(function (value, source) { | ||
try { | ||
@@ -109,3 +154,5 @@ var result = func(value, source); | ||
// value passed validator | ||
return { valid: true }; | ||
}); | ||
@@ -112,0 +159,0 @@ }; |
@@ -7,3 +7,4 @@ /*! | ||
var utils = require("./utils") | ||
var async = require("async") | ||
, utils = require("./utils") | ||
, Field = require("./field"); | ||
@@ -86,21 +87,28 @@ | ||
routines.forEach(function (routine) { | ||
var result = routine.run(mergedSource, req.form, options); | ||
//routines.forEach(function (routine) { | ||
async.each(routines, function(routine, cb) { | ||
routine.run(mergedSource, req.form, options, function(err, result) { | ||
// return early if no errors | ||
if (!Array.isArray(result) || !result.length) return cb(null); | ||
var errors = req.form.errors = req.form.errors || [] | ||
, name = routine.name; | ||
if (!Array.isArray(result) || !result.length) return; | ||
map[name] = map[name] || []; | ||
result.forEach(function (error) { | ||
errors.push(error); | ||
map[name].push(error); | ||
}); | ||
var errors = req.form.errors = req.form.errors || [] | ||
, name = routine.name; | ||
cb(null); | ||
}); | ||
}, function(err) { | ||
map[name] = map[name] || []; | ||
if (options.flashErrors) req.form.flashErrors(); | ||
if (next) next(); | ||
result.forEach(function (error) { | ||
errors.push(error); | ||
map[name].push(error); | ||
}); | ||
}); | ||
if (options.flashErrors) req.form.flashErrors(); | ||
if (next) next(); | ||
} | ||
@@ -107,0 +115,0 @@ } |
@@ -5,3 +5,3 @@ { | ||
"description": "Form validation and data filtering for Express", | ||
"version": "0.8.1", | ||
"version": "0.10.0", | ||
"homepage": "http://dandean.github.com/express-form", | ||
@@ -25,3 +25,4 @@ "repository": { | ||
"validator": "0.4.x", | ||
"object-additions": ">= 0.5.0" | ||
"object-additions": ">= 0.5.0", | ||
"async": "~0.2.9" | ||
}, | ||
@@ -32,4 +33,5 @@ "peerDependencies": { | ||
"devDependencies": { | ||
"expresso": "~0.9.2", | ||
"express": "3.x" | ||
"mocha": "~1.13.0", | ||
"express": "3.x", | ||
"request": "~2.27.0" | ||
}, | ||
@@ -41,6 +43,6 @@ "main": "index", | ||
"scripts": { | ||
"test": "expresso" | ||
"test": "mocha" | ||
}, | ||
"engines": { | ||
"node": ">=0.4.0" | ||
"node": ">=0.8.0" | ||
}, | ||
@@ -47,0 +49,0 @@ "licenses": [ |
177
README.md
@@ -0,1 +1,3 @@ | ||
# express-form | ||
Express Form provides data filtering and validation as route middleware to your Express applications. | ||
@@ -5,67 +7,75 @@ | ||
**express 2.x: use express-form 0.7.x** | ||
## Install | ||
**express 3.x: use express-form 0.8.x** | ||
* **Express 2.x** `npm install express-form@0.9.x` | ||
Usage: | ||
------ | ||
* **Express 3.x** `npm install express-form@0.10.x` | ||
var form = require("express-form"), | ||
field = form.field; | ||
## Usage | ||
var app = express.createServer(); | ||
```js | ||
var express = require('express'), | ||
form = require('express-form'), | ||
field = form.field; | ||
var app = express(); | ||
app.configure(function() { | ||
app.use(express.bodyDecoder()); | ||
app.use(app.router); | ||
}); | ||
app.configure(function() { | ||
app.use(express.bodyDecoder()); | ||
app.use(app.router); | ||
}); | ||
app.post( | ||
app.post( | ||
// Route | ||
'/user', | ||
// Route | ||
'/user', | ||
// Form filter and validation middleware | ||
form( | ||
field("username").trim().required().is(/^[a-z]+$/), | ||
field("password").trim().required().is(/^[0-9]+$/), | ||
field("email").trim().isEmail() | ||
), | ||
// Form filter and validation middleware | ||
form( | ||
field("username").trim().required().is(/^[a-z]+$/), | ||
field("password").trim().required().is(/^[0-9]+$/), | ||
field("email").trim().isEmail() | ||
), | ||
// Express request-handler now receives filtered and validated data | ||
function(req, res){ | ||
if (!req.form.isValid) { | ||
// Handle errors | ||
console.log(req.form.errors); | ||
// Express request-handler now receives filtered and validated data | ||
function(req, res){ | ||
if (!req.form.isValid) { | ||
// Handle errors | ||
console.log(req.form.errors); | ||
} else { | ||
// Or, use filtered form data from the form object: | ||
console.log("Username:", req.form.username); | ||
console.log("Password:", req.form.password); | ||
console.log("Email:", req.form.email); | ||
} | ||
} | ||
); | ||
} else { | ||
// Or, use filtered form data from the form object: | ||
console.log("Username:", req.form.username); | ||
console.log("Password:", req.form.password); | ||
console.log("Email:", req.form.email); | ||
} | ||
} | ||
); | ||
Documentation: | ||
-------------- | ||
app.listen(3000); | ||
``` | ||
## Documentation | ||
### Module | ||
The Express Form **module** returns an Express [Route Middleware](http://expressjs.com/guide.html#Route-Middleware) function. You specify filtering and validation by passing filters and validators as arguments to the main module function. For example: | ||
`express-form` returns an `express` [Route Middleware](http://expressjs.com/guide.html#Route-Middleware) function. | ||
You specify filtering and validation by passing filters and validators as | ||
arguments to the main module function. For example: | ||
var form = require("express-form"); | ||
```js | ||
var form = require("express-form"); | ||
app.post('/user', | ||
// Express Form Route Middleware: trims whitespace off of | ||
// the `username` field. | ||
form(form.field("username").trim()), | ||
// standard Express handler | ||
function(req, res) { | ||
// ... | ||
} | ||
); | ||
app.post('/user', | ||
// Express Form Route Middleware: trims whitespace off of | ||
// the `username` field. | ||
form(form.field("username").trim()), | ||
// standard Express handler | ||
function(req, res) { | ||
// ... | ||
} | ||
); | ||
``` | ||
@@ -76,17 +86,29 @@ ### Fields | ||
field(fieldname[, label]); | ||
``` | ||
field(fieldname[, label]); | ||
``` | ||
You can access nested properties with either dot or square-bracket notation. | ||
field("post.content").minLength(50), | ||
field("post[user][id]").isInt(), | ||
field("post.super.nested.property").required() | ||
Simply specifying a property like this, makes sure it exists. So, even if `req.body.post` was undefined, `req.form.post.content` would be defined. This helps avoid any unwanted errors in your code. | ||
```js | ||
field("post.content").minLength(50), | ||
field("post[user][id]").isInt(), | ||
field("post.super.nested.property").required() | ||
``` | ||
Simply specifying a property like this, makes sure it exists. So, even if `req.body.post` was undefined, | ||
`req.form.post.content` would be defined. This helps avoid any unwanted errors in your code. | ||
The API is chainable, so you can keep calling filter/validator methods one after the other: | ||
filter("username").trim().toLower().truncate(5).required().isAlphanumeric() | ||
```js | ||
filter("username") | ||
.required() | ||
.trim() | ||
.toLower() | ||
.truncate(5) | ||
.isAlphanumeric() | ||
``` | ||
#### Filter API: | ||
### Filter API: | ||
@@ -128,3 +150,3 @@ Type Coercion | ||
#### Validator API: | ||
### Validator API: | ||
@@ -299,2 +321,34 @@ **Validation messages**: each validator has its own default validation message. | ||
}); | ||
Validator based value on another field of the incoming source being validated | ||
field("sport", "favorite sport").custom(function(value, source) { | ||
if (!source.country) { | ||
throw new Error('unable to validate %s'); | ||
} | ||
switch (source.country) { | ||
case 'US': | ||
if (value !=== 'baseball') { | ||
throw new Error('America likes baseball'); | ||
} | ||
break; | ||
case 'UK': | ||
if (value !=== 'football') { | ||
throw new Error('UK likes football'); | ||
} | ||
break; | ||
} | ||
}); | ||
Asynchronous custom validator (3 argument function signature) | ||
form.field('username').custom(function(value, source, callback) { | ||
username.check(value, function(err) { | ||
if (err) return callback(new Error('Invalid %s')); | ||
callback(null); | ||
}); | ||
}); | ||
@@ -351,11 +405,4 @@ | ||
Installation: | ||
------------- | ||
### Credits | ||
npm install express-form | ||
Credits | ||
------- | ||
Currently, Express Form uses many of the validation and filtering functions provided by Chris O'Hara's [node-validator](https://github.com/chriso/node-validator). |
@@ -7,2 +7,3 @@ var assert = require("assert"), | ||
http = require("http"), | ||
request = require("request"), | ||
app = express(); | ||
@@ -20,3 +21,2 @@ | ||
app.configure(function() { | ||
@@ -27,3 +27,3 @@ app.use(express.bodyParser()); | ||
module.exports = { | ||
'express : middleware : valid-form': function() { | ||
'express : middleware : valid-form': function(done) { | ||
app.post( | ||
@@ -44,19 +44,20 @@ '/user', | ||
} | ||
); | ||
assert.response(app, | ||
{ | ||
url: '/user', | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
username: " dandean \n\n\t", | ||
password: " 12345 " | ||
}), | ||
headers: { 'Content-Type': 'application/json' } | ||
}, | ||
{ status: 200 } | ||
); | ||
request.post({ | ||
url: 'http://localhost:3000/user', | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
username: " dandean \n\n\t", | ||
password: " 12345 " | ||
}), | ||
headers: { 'Content-Type': 'application/json' } | ||
}, function(err, res, body) { | ||
assert.ifError(err); | ||
assert.strictEqual(res.statusCode, 200); | ||
done(); | ||
}); | ||
}, | ||
'express : middleware : merged-data': function() { | ||
'express : middleware : merged-data': function(done) { | ||
app.post( | ||
@@ -82,17 +83,18 @@ '/user/:id', | ||
} | ||
); | ||
assert.response(app, | ||
{ | ||
url: '/user/5?stuff=things&id=overridden', | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
id: "overridden by url param", | ||
stuff: "overridden by query param", | ||
rad: "cool" | ||
}), | ||
headers: { 'Content-Type': 'application/json' } | ||
}, | ||
{ status: 200 } | ||
); | ||
request({ | ||
url: 'http://localhost:3000/user/5?stuff=things&id=overridden', | ||
method: 'POST', | ||
body: JSON.stringify({ | ||
id: "overridden by url param", | ||
stuff: "overridden by query param", | ||
rad: "cool" | ||
}), | ||
headers: { 'Content-Type': 'application/json' } | ||
}, function(err, res, body) { | ||
assert.ifError(err); | ||
assert.strictEqual(res.statusCode, 200); | ||
done(); | ||
}); | ||
} | ||
@@ -99,0 +101,0 @@ |
@@ -571,2 +571,97 @@ var assert = require("assert"), | ||
"validation: custom : async": function(done) { | ||
var request = { body: { field1: "value1", field2: "value2" }}; | ||
var next = function next() { | ||
assert.strictEqual(request.form.isValid, false); | ||
assert.strictEqual(request.form.errors.length, 1); | ||
assert.strictEqual(request.form.errors[0], 'Invalid field1'); | ||
done(); | ||
}; | ||
form(validate("field1").custom(function(value, source, callback) { | ||
process.nextTick(function() { | ||
assert.strictEqual(value, 'value1'); | ||
callback(new Error("Invalid %s")); | ||
}); | ||
}))(request, {}, next); | ||
}, | ||
"validation : custom : async : success": function(done) { | ||
var request = { body: { field1: "value1", field2: "value2" }}; | ||
var callbackCalled = false; | ||
var next = function next() { | ||
assert.strictEqual(callbackCalled, true); | ||
assert.strictEqual(request.form.isValid, true); | ||
assert.strictEqual(request.form.errors.length, 0); | ||
done(); | ||
}; | ||
form(validate("field1").custom(function(value, source, callback) { | ||
process.nextTick(function() { | ||
assert.strictEqual(value, 'value1'); | ||
callbackCalled = true; | ||
callback(null); | ||
}); | ||
}))(request, {}, next); | ||
}, | ||
"validation : custom : async : chaining": function(done) { | ||
var request = { body: { field1: "value1", field2: "value2" }}; | ||
var callbackCalled = 0; | ||
var next = function next() { | ||
assert.strictEqual(callbackCalled, 2); | ||
assert.strictEqual(request.form.isValid, false); | ||
assert.strictEqual(request.form.errors.length, 2); | ||
assert.strictEqual(request.form.errors[0], 'Fail! field1'); | ||
assert.strictEqual(request.form.errors[1], 'yes sync custom funcs still work !! field1'); | ||
done(); | ||
}; | ||
form(validate("field1") | ||
.custom(function(value, source, callback) { | ||
process.nextTick(function() { | ||
++callbackCalled; | ||
callback(null); | ||
}); | ||
}) | ||
.custom(function(value, source, callback) { | ||
process.nextTick(function() { | ||
++callbackCalled; | ||
callback(new Error('Fail! %s')); | ||
}); | ||
}) | ||
.custom(function(value, source) { | ||
throw new Error('yes sync custom funcs still work !! %s'); | ||
}) | ||
)(request, {}, next); | ||
}, | ||
"validation : custom : async : multiple fields": function(done) { | ||
var request = { body: { field1: "value1", field2: "value2" }}; | ||
var callbackCalled = 0; | ||
var next = function next() { | ||
assert.strictEqual(callbackCalled, 2); | ||
assert.strictEqual(request.form.isValid, false); | ||
assert.strictEqual(request.form.errors.length, 2); | ||
assert.strictEqual(request.form.errors[0], 'field1 error'); | ||
assert.strictEqual(request.form.errors[1], 'field2 error'); | ||
done(); | ||
}; | ||
form( | ||
validate("field1").custom(function(value, source, callback) { | ||
process.nextTick(function() { | ||
++callbackCalled; | ||
assert.strictEqual(value, 'value1') | ||
callback(new Error('%s error')); | ||
}); | ||
}), | ||
validate("field2").custom(function(value, source, callback) { | ||
process.nextTick(function() { | ||
++callbackCalled; | ||
assert.strictEqual(value, 'value2'); | ||
callback(new Error('%s error')); | ||
}); | ||
}) | ||
)(request, {}, next); | ||
}, | ||
"validation : request.form property-pollution": function() { | ||
@@ -573,0 +668,0 @@ var request = { body: { }}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
77771
18
1704
404
4
3
+ Addedasync@~0.2.9
+ Addedasync@0.2.10(transitive)