Make numItems work more like isOptional validator #32

Merged
merged 4 commits into from May 7, 2013
View
25 lib/util.js
@@ -42,5 +42,30 @@ function merge(a, b) {
}
+/**
+ * A "better" typeof-like function that can distinguish between array and null
+ * objects. NOTE: This is a function, not an operator like "typeof".
+ *
+ * @private
+ * @param {value} value an object.
+ * @return {String} 'array' or 'null'.
+ */
+function typeOf(value) {
+ var t = typeof(value);
+ if (t === 'object') {
+ if (value) {
+ if (value instanceof Array) {
+ t = 'array';
+ }
+ }
+ else {
+ t = 'null';
+ }
+ }
+ return t;
+}
+
+
exports.trim = trim;
exports.merge = merge;
+exports.typeOf = typeOf;
View
29 lib/validators.js
@@ -1,5 +1,7 @@
var check = require('validator').check;
+var utils = require('./util');
+
exports.isPort = function(value, baton) {
value = parseInt(value, 10);
@@ -34,3 +36,30 @@ exports.isHostname = function(value) {
return true;
};
+
+/**
+ * Verify that the provided array or object has between min and max number of
+ * elements.
+ * @param {Object} value Object or array to validate.
+ * @param {Number} min Minimum number of elements.
+ * @param {Number} max Maximum number of elements.
+ */
+exports.numItems = function(value, min, max) {
+ var len, type = utils.typeOf(value);
+
+ if (type === 'array') {
+ len = value.length;
+ }
+ else if (type === 'object') {
+ len = Object.keys(value).length;
+ }
+ else {
+ throw new Error('value must either be a array or an object');
+ }
+
+ if (len < min || len > max) {
+ throw new Error('Object needs to have between ' + min + ' and ' + max + ' items');
+ }
+
+ return value;
+};
View
86 lib/valve.js
@@ -60,6 +60,7 @@ var async = require('async'),
sanitize = require('validator').sanitize,
check = require('validator').check,
validators = require('./validators'),
+ utils = require('./util'),
net = require('net'),
ipv6 = require('ipv6').v6,
ipv4 = require('ipv6').v4,
@@ -140,30 +141,6 @@ function chainHelp(chain) {
/**
- * A "better" typeof-like function that can distinguish between array and null
- * objects. NOTE: This is a function, not an operator like "typeof".
- *
- * @private
- * @param {value} value an object.
- * @return {String} 'array' or 'null'.
- */
-function typeOf(value) {
- var t = typeof(value);
- if (t === 'object') {
- if (value) {
- if (value instanceof Array) {
- t = 'array';
- }
- }
- else {
- t = 'null';
- }
- }
- return t;
-}
-
-
-/**
* Normalize an IP address. Expands "::" notation in IPv6 addresses and
* zero-prefixes each component number (see ipv6.canonical_form()).
*
@@ -213,12 +190,14 @@ var Chain = function() {
if (! (this instanceof Chain)) {
return new Chain();
}
+
this.validators = [];
this.target = null;
this.isOptional = false;
this.isImmutable = false;
this.isUpdateRequired = false;
this._validatorCount = 0;
+ this._numItemsValidator = null;
};
@@ -1171,7 +1150,7 @@ Chain.prototype.notIn = function(values, caseSensitive) {
this._pushValidator({
name: 'notIn',
func: function(value, baton, callback) {
- var type = typeOf(value), keys, key, i;
+ var type = utils.typeOf(value), keys, key, i;
if (type === 'string' || type === 'number') {
keys = [value];
@@ -1332,7 +1311,6 @@ Chain.prototype.len = function(min, max) {
return this;
};
-
/**
* Verify that the provided array or object has between min and max number of
* elements.
@@ -1350,21 +1328,16 @@ Chain.prototype.numItems = function(min, max) {
max = Infinity;
}
- v.func = function(value, baton, callback) {
- var len;
-
- if (value instanceof Array) {
- len = value.length;
- }
- else {
- len = Object.keys(value).length;
- }
@russellhaering
russellhaering added a line comment May 7, 2013

Oh, I guess you were just using what was here. I'd still add the extra validation though.

@Kami
Kami added a line comment May 7, 2013

Yeah, it shouldn't never get called with an invalid value from isHash / isArray, but I agree and it doesn't hurt to add extra validation to isNumber itself - aeb0b31

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ if (this._numItemsValidator) {
+ throw new Error('Chain can only have a single numItems validator');
+ }
- if (len < min || len > max) {
- callback('Object needs to have between ' + min + ' and ' + max + ' items');
- return;
- }
+ this._numItemsValidator = {
+ 'min': min,
+ 'max': max
+ }
+ v.func = function(value, baton, callback) {
callback(null, value);
};
@@ -1640,7 +1613,7 @@ Chain.prototype.isString = function() {
this._pushValidator({
name: 'isString',
func: function(value, baton, callback) {
- if (typeOf(value) !== 'string') {
+ if (utils.typeOf(value) !== 'string') {
callback('Not a string');
} else {
callback(null, value);
@@ -1824,10 +1797,19 @@ Chain.prototype.isArray = function(chain) {
this._pushValidator({
name: 'isArray',
func: function(value, baton, callback) {
- if (typeOf(value) !== 'array') {
+ if (utils.typeOf(value) !== 'array') {
callback('Not an array');
return;
}
+
+ try {
+ self._validateNumItems(value);
+ }
+ catch (e) {
+ callback(e.message);
+ return;
+ }
+
async.map(value,
function(item, itercb) {
checkChain(item, chain, baton, itercb);
@@ -1883,15 +1865,26 @@ Chain.prototype.isHash = function(keyChain, valueChain) {
func: function(value, baton, callback) {
var key,
kvpairs = [];
- if (typeOf(value) !== 'object') {
+
+ if (utils.typeOf(value) !== 'object') {
callback('Not a hash');
return;
}
+
+ try {
+ self._validateNumItems(value);
+ }
+ catch (e) {
+ callback(e.message);
+ return;
+ }
+
for (key in value) {
if (value.hasOwnProperty(key)) {
kvpairs.push([key, value[key]]);
}
}
+
async.reduce(kvpairs, {}, iter(baton), callback);
},
help: 'Hash [' + chainHelp(keyChain).join(',') + ':' +
@@ -1900,6 +1893,15 @@ Chain.prototype.isHash = function(keyChain, valueChain) {
return this;
};
+Chain.prototype._validateNumItems = function(value) {
+ if (!this._numItemsValidator) {
+ return;
+ }
+
+ validators.numItems(value, this._numItemsValidator.min,
+ this._numItemsValidator.max);
+};
+
/**
* Adds a validator to the chain that reassigns the value for the
View
2 package.json
@@ -12,7 +12,7 @@
],
"name": "swiz",
"description": "Serialization and Validation Framework for objects in RESTful APIs",
- "version": "0.4.55",
+ "version": "0.4.56",
"homepage": "https://github.com/racker/node-swiz",
"repository": {
"type": "git",
View
16 tests/test-valve.js
@@ -124,7 +124,7 @@ var badExampleNode1 = {
exports['test_validate_numItems'] = function(test, assert) {
- var v1, v2, v3;
+ var v1, v2, v3, thrown = false;
v1 = new V({
a: C().isArray(C().isInt()).numItems(1, 5)
@@ -138,6 +138,20 @@ exports['test_validate_numItems'] = function(test, assert) {
a: C().isArray(C().isInt()).numItems(2)
});
+ try {
+ new V({
+ a: C().isArray(C().isInt()).numItems(2).numItems(2)
+ });
+ }
+ catch (e) {
+ thrown = true;
+ assert.match(e.message, /single numItems validator/i);
+ }
+
+ if (!thrown) {
+ assert.fail('numItems added multiple times, but exception wasnt thrown');
+ }
+
// Negative test cases (array)
v1.check({'a': [1]}, function(err, cleaned) {
assert.ifError(err);