🚀 Big News:Socket Has Acquired Secure Annex.Learn More
Socket
Book a DemoSign in
Socket

fvalid

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fvalid - npm Package Compare versions

Comparing version
0.0.0-prerelease-2
to
0.0.0-prerelease-3
+240
-146
fvalid.js

@@ -5,26 +5,17 @@ /* globals define, module */

// Functional validation for arbitrarily nested JavaScript data
var moduleName = 'fvalid';
// Functional validation for arbitrarily nested JavaScript data
// Universal Module Definition
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(moduleName, [], factory());
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root[moduleName] = factory();
}
})(this, function() {
var fvalid = function() {
// The object to be exported
var exports = {
name: moduleName,
version: '0.0.0-prerelease-2'
version: '0.0.0-prerelease-3'
};
// # Vocubulary Used in Comments
// # Vocabulary Used in Comments
//
// A _validator function_ is a plain JavaScript closure that take a
// value to validate as its sole argument and returns the result of
// either this.ok or this.expected(String).
// either true or a string.
//

@@ -34,56 +25,62 @@ // A _path_ is an array of String and Number indices identifying

//
// [ 'phoneNumbers', 2 ]
// [ 'addresses', 2 ]
//
// Is the path for the third item of the array value of the
// 'phoneNumbers' property of an object being validated.
// 'addresses' property of an object being validated.
// Bind a validator function to a particular context. Provide
// appropriate this.ok and this.expected functions that are aware of
// the path of the value being validated.
var isObject = function(input) {
return Object.prototype.toString(input) === '[object Object]' &&
Boolean(input) &&
!Array.isArray(input);
};
var contextualize = function(path, validator) {
return function(x) {
var context = {
// Store the path so that validators that descend to object
// properties or array items can appropriately set the context
// path of other validators they invoke.
path: path,
return function(input) {
var returned = validator(input, path);
// Used by the validator function to indicate validity.
ok: [],
var errorWithExpectation = function(expected) {
return {
path: path,
found: input,
expected: [ expected ]
};
};
// Used by the validator function to indicate what was
// expected, but not found.
expected: function(expected) {
// Error messages indicate:
return [ {
// 1. where in the data the problem was found,
path: path,
// `input` is valid input, so return an array of no errors.
if (returned === true) {
return [];
// 2. what was expected to be there, and ...
expected: expected,
// `input` is not valid input.
} else if (
// `returned` is a string description of what was expected.
typeof returned === 'string' ||
// `returned` lists one of several alternatives, or a value
// matching several expectations.
isObject(returned)
) {
return [ errorWithExpectation(returned) ];
// 3. what was found instead.
found: x
} ];
}
};
// `returned` is a list of errors.
} else if (Array.isArray(returned)) {
return returned.map(function(error) {
if (typeof error === 'string') {
throw new Error(
'validator returned more than one expectation'
);
} else {
return error;
}
});
var errors = validator.call(context, x);
// Forgot to return something?
if (!Array.isArray(errors)) {
// The validator returned some other value.
} else {
throw new Error(
'validator function failed to return ' +
'this.ok or this.expected()'
'validator function failed to return true or string'
);
}
// TODO: Check that validator functions return valid errors.
return errors;
};
};
var ensureValidatorArg = function(functionName, arg) {
if (typeof arg !== 'function') {
var ensureValidatorArgument = function(functionName, argument) {
if (typeof argument !== 'function') {
throw new Error(

@@ -94,11 +91,11 @@ moduleName + '.' + functionName + ' requires ' +

} else {
return arg;
return argument;
}
};
var ensureValidatorArgs = function(functionName, args) {
var ensureValidatorArguments = function(functionName, args) {
// Flatten ([ function, ... ]) and (function, ...)
var validators = Array.prototype.slice.call(args, 0)
.reduce(function(mem, i) {
return mem.concat(i);
.reduce(function(output, i) {
return output.concat(i);
}, []);

@@ -115,3 +112,2 @@ if (validators.length < 1) {

// TODO: Add asynchronous validator function support.

@@ -122,3 +118,3 @@

exports.validate = function(value, validator) {
validator = ensureValidatorArg('validate', validator);
validator = ensureValidatorArgument('validate', validator);
return contextualize([], validator)(value);

@@ -139,11 +135,11 @@ };

exports.ownProperty = function(name, validator) {
validator = ensureValidatorArg('ownProperty', validator);
return function(x) {
if (typeof x !== 'object') {
return this.expected('object');
} else if (!x.hasOwnProperty(name)) {
return this.expected('own property ' + JSON.stringify(name));
validator = ensureValidatorArgument('ownProperty', validator);
return function(input, path) {
if (!isObject(input)) {
return 'object with property ' + JSON.stringify(name);
} else if (!input.hasOwnProperty(name)) {
return 'own property ' + JSON.stringify(name);
} else {
var propertyPath = this.path.concat(name);
return contextualize(propertyPath, validator)(x[name]);
var propertyPath = path.concat(name);
return contextualize(propertyPath, validator)(input[name]);
}

@@ -153,12 +149,63 @@ };

// Build a validator function that validates the value of a property
// if the object has one.
exports.optionalProperty = function(name, validator) {
validator = ensureValidatorArgument(
'optionalProperty', validator
);
return function(input, path) {
if (!isObject(input)) {
return 'object with property ' + JSON.stringify(name);
} else if (!input.hasOwnProperty(name)) {
return true;
} else {
var propertyPath = path.concat(name);
return contextualize(propertyPath, validator)(input[name]);
}
};
};
var plural = function(list, singular, plural) {
var length = list.length;
// istanbul ignore if
if (length === 0) {
throw new Error('list has no elements');
} else if (length === 1) {
return singular;
} else {
return plural;
}
};
// Creates "A, C, and|or|then C" lists from arrays
var conjunctionList = (function() {
var COMMA = ',';
return function(conjunction, array) {
conjunction = ' ' + conjunction + ' ';
var length = array.length;
// istanbul ignore if
if (length === 0) {
throw new Error('cannot create a list of no elements');
} else if (length === 1) {
return array;
} else if (length === 2) {
return array[0] + conjunction + array[1];
} else {
var head = array.slice(0, array.length - 1).join(COMMA + ' ');
return head + COMMA + conjunction + array[array.length - 1];
}
};
})();
// Build a validator function that rejects any object properties not
// provided in a given whitelist. (That validator will _not_ ensure
// that the whitelisted properties exist.)
// provided in a given white list. (That validator will _not_ ensure
// that the white-listed properties exist.)
exports.onlyProperties = function() {
var onlyNames = Array.prototype.slice.call(arguments, 0)
.reduce(function(mem, i) {
return mem.concat(i);
var allowedProperties = Array.prototype.slice.call(arguments, 0)
.reduce(function(output, argument) {
return output.concat(argument);
}, []);
if (onlyNames.length === 0) {
if (allowedProperties.length === 0) {
throw new Error(

@@ -170,18 +217,24 @@ moduleName + '.onlyProperties requires ' +

return function(x) {
var path = this.path;
var names = Object.getOwnPropertyNames(x);
return names.reduce(function(mem, name) {
var allowed = onlyNames.indexOf(name) > -1;
if (allowed) {
return mem;
} else {
var propertyPath = path.concat(name);
return mem.concat(
contextualize(propertyPath, function() {
return this.expected('no property "' + name + '"');
})(x[name])
);
}
}, []);
return function(input, path) {
if (!isObject(input)) {
var quoted = allowedProperties.map(JSON.stringify);
return 'object with only the ' +
plural(allowedProperties, 'property', 'properties') + ' ' +
conjunctionList('and', quoted);
} else {
var names = Object.keys(input);
return names.reduce(function(output, name) {
var allowed = allowedProperties.indexOf(name) > -1;
if (allowed) {
return output;
} else {
var propertyPath = path.concat(name);
return output.concat(
contextualize(propertyPath, function() {
return 'no property "' + name + '"';
})(input[name])
);
}
}, []);
}
};

@@ -193,13 +246,11 @@ };

exports.eachItem = function(validator) {
validator = ensureValidatorArg('eachItem', validator);
validator = ensureValidatorArgument('eachItem', validator);
return function(x) {
var path = this.path;
if (!Array.isArray(x)) {
return this.expected('array');
return function(input, path) {
if (!Array.isArray(input)) {
return 'array';
} else {
return x.reduce(function(mem, item, index) {
return input.reduce(function(output, item, index) {
// Collect errors from application to each array item.
return mem.concat(
return output.concat(
// Invoke the validator in the context of each array item.

@@ -216,12 +267,10 @@ contextualize(path.concat(index), validator)(item)

exports.someItem = function(validator) {
validator = ensureValidatorArg('someItem', validator);
validator = ensureValidatorArgument('someItem', validator);
return function(x) {
var path = this.path;
if (!Array.isArray(x) || x.length === 0) {
return this.expected('non-empty array');
return function(input, path) {
if (!Array.isArray(input) || input.length === 0) {
return 'non-empty array';
} else {
var lastErrors = null;
var match = x.some(function(item, index) {
var match = input.some(function(item, index) {
// Invoke the validator in the context of each array item.

@@ -239,5 +288,5 @@ var errors = contextualize(

if (match) {
return this.ok;
return true;
} else {
return this.expected('some ' + lastErrors[0].expected);
return 'some ' + lastErrors[0].expected;
}

@@ -248,19 +297,61 @@ }

// Return the first element of an array matching a given predicate.
var find = function(predicate) {
var array = Object(this);
var length = this.length;
for (var index = 0; index < length; index++) {
var value = array[index];
if (predicate(value, index, array)) {
return value;
}
}
return undefined;
};
// Are two paths the same?
var samePath = function(firstPath, secondPath) {
// Perform a shallow comparison of two arrays that can contain
// numbers and strings.
if (firstPath.length !== secondPath.length) {
return false;
}
return !firstPath.some(function(element, index) {
return element !== secondPath[index];
});
};
// Conjoins an array or arguments list of validator functions into a
// single validator function.
exports.and = function() {
var validators = ensureValidatorArgs('and', arguments);
exports.all = function() {
var validators = ensureValidatorArguments('all', arguments);
return function(x) {
var path = this.path;
return function(input, path) {
// Bind all the validator functions to the context where `.and`
// is invoked.
return validators.map(function(v) {
return contextualize(path, v);
var errors = validators.map(function(validator) {
return contextualize(path, validator);
})
// Collect errors from invoking the validator functions.
.reduce(function(errors, v) {
return errors.concat(v(x));
.reduce(function(output, validator) {
return output.concat(validator(input));
}, []);
if (errors.length === 0) {
return [];
} else {
return errors.reduce(function(output, error) {
var errorAtSamePath = find.call(output, function(existing) {
return samePath(existing.path, error.path);
});
if (errorAtSamePath === undefined) {
return output.concat(error);
} else {
var allExpected = errorAtSamePath.expected.concat(
error.expected
);
errorAtSamePath.expected = allExpected;
return output;
}
}, []);
}
};

@@ -278,30 +369,8 @@ };

// Creates "A, C, and|or|then C" lists from arrays
var conjunctionList = (function() {
var COMMA = ',';
return function(conjunction, array) {
conjunction = ' ' + conjunction + ' ';
var length = array.length;
if (length === 0) {
throw new Error('cannot create a list of no elements');
} else if (length === 1) {
return array;
} else if (length === 2) {
return array[0] + conjunction + array[1];
} else {
var head = array.slice(0, array.length - 2).join(COMMA);
return head + COMMA + conjunction + array[array.length];
}
};
})();
// Disjoins an array or arguments list of validator functions into a
// single validator function.
exports.or = function() {
var validators = ensureValidatorArgs('or', arguments);
exports.any = function() {
var validators = ensureValidatorArguments('any', arguments);
return function(x) {
var path = this.path;
return function(input, path) {
// Used to accumulate all of the errors from all of the

@@ -314,4 +383,4 @@ // validator functions. If none of them match, `.or` will create

// Enumerate validator functions until we find a match.
var valid = validators.some(function(v) {
var errors = contextualize(path, v)(x);
var valid = validators.some(function(validator) {
var errors = contextualize(path, validator)(input);

@@ -323,2 +392,3 @@ // Valid input. Break out of `.some`, since there is no need

return true;
// Not valid input per this validation function.

@@ -335,3 +405,4 @@ } else {

if (valid) {
return this.ok;
return true;
// No validation function matched.

@@ -341,6 +412,17 @@ } else {

// validator functions.
var expectations = allErrors.map(returnProperty('expected'));
var expectations = allErrors
.map(returnProperty('expected'))
.reduce(function(output, expectation) {
// A single expectation
if (expectation.length === 1) {
return output.concat(expectation);
// A conjunction
} else {
return output.concat([ expectation ]);
}
});
// Join those expectation messages into one.
return this.expected(conjunctionList('or', expectations));
return { any: expectations };
}

@@ -351,3 +433,15 @@ };

return exports;
});
};
// Universal Module Definition
// istanbul ignore next
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(moduleName, [], factory());
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root[moduleName] = factory();
}
})(this, fvalid);
})();
{
"name": "fvalid",
"version": "0.0.0-prerelease-2",
"version": "0.0.0-prerelease-3",
"description": "validate arbitrarily nested objects with functions",
"keywords": [
"contracts",
"documents",
"drafting",
"law"
"validation",
"validate"
],

@@ -24,10 +22,9 @@ "license": "Apache-2.0",

"scripts": {
"pre-commit": "npm run lint && npm test && npm run prepublish",
"docs": "docco fvalid.js",
"lint": "jshint fvalid.js && jscs fvalid.js",
"build": "uglifyjs -cm -o fvalid.min.js fvalid.js",
"prepublish": "npm run build",
"pre-commit": "npm run lint && npm test && npm run coverage",
"coverage": "istanbul cover _mocha -- --require should && istanbul check-coverage coverage/coverage.json",
"lint": "jshint . && jscs fvalid.js test/",
"test": "mocha --require should"
},
"devDependencies": {
"istanbul": "^0.3.5",
"jscs": "^1.8.1",

@@ -37,4 +34,3 @@ "jshint": "^2.5.10",

"semver": "^4.1.0",
"should": "^4.3.1",
"uglify-js": "^2.4.16"
"should": "^4.3.1"
},

@@ -41,0 +37,0 @@ "testling": {

fvalid.js
=========
[![NPM verson](https://img.shields.io/npm/v/fvalid.svg)](https://www.npmjs.com/package/fvalid)
[![build status](https://travis-ci.org/kemitchell/fvalid.svg)](http://travis-ci.org/kemitchell/fvalid)
[![NPM version](https://img.shields.io/npm/v/fvalid.svg)](https://www.npmjs.com/package/fvalid)
[![build status](https://travis-ci.org/kemitchell/fvalid.js.svg)](http://travis-ci.org/kemitchell/fvalid.js)
[![browser support](https://ci.testling.com/kemitchell/fvalid.png)](https://ci.testling.com/kemitchell/fvalid)
[![browser support](https://ci.testling.com/kemitchell/fvalid.js.png)](https://ci.testling.com/kemitchell/fvalid.js)

@@ -16,25 +16,4 @@ Validate arbitrarily nested objects with functions

For example:
The module has no external dependencies. It utilizes ECMA-262 5th edition functions like `reduce`.
```javascript
var good = { name: 'John' };
var bad = { name: '' };
var validator = fvalid.ownProperty('name', function(x) {
return x.length > 0 ?
this.ok :
this.expected('non-empty string');
});
fvalid.validate(good, validator);
// => []
fvalid.valid(good, validator);
// => true
fvalid.validate(bad, validator);
// => [ { path: [ 'name' ], found: '', expected: 'non-empty string' } ]
fvalid.valid(bad, validator);
// => false
```
See also various examples in the [test suite](./test), including for a toy [micro-blog post format](./test/blog.js).
The [test suite](./test) has usage examples, including for a toy [micro-blog post format](./test/blog.js).