Comparing version 0.5.0 to 0.6.0
@@ -9,3 +9,2 @@ /** | ||
/** | ||
@@ -21,8 +20,9 @@ * Coerce value to type schema | ||
// Avoid damaging the provided parameters. | ||
var errors = []; | ||
// Avoid damaging `expected` | ||
expected = _.cloneDeep(expected); | ||
actual = _.cloneDeep(actual); | ||
var errors = []; | ||
var result = validateRecursive(expected, actual, errors); | ||
// Jump into recursive validation | ||
var result = validateRecursive(expected, actual, errors, true); | ||
@@ -29,0 +29,0 @@ // Strip out "E_INVALID_TYPE" errors- they are ok if we're just coercing. |
@@ -21,22 +21,4 @@ /** | ||
// NaN | ||
nan: { | ||
is: _.isNaN, | ||
to: function () { return NaN; } | ||
}, | ||
// Null | ||
'null': { | ||
is: _.isNull, | ||
to: function () { return null; } | ||
}, | ||
// Undefined | ||
'undefined': { | ||
is: _.isUndefined, | ||
to: function () { return undefined; } | ||
}, | ||
// Boolean | ||
bool: { | ||
boolean: { | ||
is: _.isBoolean, | ||
@@ -59,3 +41,2 @@ to: function(v) { | ||
}, | ||
base: false, | ||
getBase: function (){ | ||
@@ -66,28 +47,4 @@ return false; | ||
// Defined | ||
defined: { | ||
is: function(v) { | ||
return !( type.nan.is(v) || type.undefined.is(v) || type.null.is(v) ); | ||
}, | ||
to: function(v) { | ||
return (type.defined.is(v)) ? v : true; | ||
} | ||
}, | ||
// Integer | ||
'int': { | ||
is: function(v) { return (v == v + 0 && v == ~~v); }, | ||
to: function(v) { | ||
var value = parseInt(v, 10); | ||
if (!isNaN(value)) return value; | ||
return 1; | ||
}, | ||
base: 0, | ||
getBase: function (){ | ||
return 0; | ||
} | ||
}, | ||
// String | ||
str: { | ||
string: { | ||
is: _.isString, | ||
@@ -134,10 +91,8 @@ to: function(v) { | ||
if(v === null) { | ||
if(_.isNull(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
if(type.defined.is(v)) return String(v); | ||
return ''; | ||
return String(v); | ||
}, | ||
base: '', | ||
getBase: function (){ | ||
@@ -148,6 +103,9 @@ return ''; | ||
// Object | ||
obj: { | ||
// Dictionary | ||
dictionary: { | ||
is: function(v) { | ||
return _.isObject(v) && !type.arr.is(v); | ||
if (!_.isObject(v)) return false; | ||
if (type.arr.is(v)) return false; | ||
if (!_.isPlainObject(v)) return false; | ||
return true; | ||
}, | ||
@@ -161,5 +119,8 @@ to: function(v) { | ||
} | ||
return {}; | ||
if (_.isPlainObject(v)){ | ||
return v; | ||
} | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
}, | ||
base: {}, | ||
getBase: function (){ | ||
@@ -170,9 +131,12 @@ return {}; | ||
// Array | ||
arr: { | ||
array: { | ||
is: _.isArray, | ||
to: function(v) { | ||
return _.toArray(v); | ||
if (!_.isArray(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
return v; | ||
}, | ||
base: [], | ||
getBase: function (){ | ||
@@ -183,18 +147,6 @@ return []; | ||
// Date | ||
'date': { | ||
is: _.isDate, | ||
to: function(v) { | ||
return new Date(v); | ||
}, | ||
base: new Date(), | ||
getBase: function (){ | ||
return new Date(); | ||
} | ||
}, | ||
// Numeric | ||
'number': { | ||
// Nuber | ||
number: { | ||
is: function(v) { | ||
return _.isNumber(v) && !type.nan.is(parseFloat(v)) && v!==Infinity && v!==-Infinity; | ||
return _.isNumber(v) && !_.isNaN(parseFloat(v)) && v!==Infinity && v!==-Infinity; | ||
}, | ||
@@ -218,6 +170,7 @@ to: function(v) { | ||
if (_.isObject(v)) return 0; | ||
if(type.number.is(v)) return v; | ||
if(type.bool.is(v)) return v ? 1 : 0; | ||
if(type.str.is(v)) { | ||
if(type.boolean.is(v)) return v ? 1 : 0; | ||
if(type.string.is(v)) { | ||
@@ -243,19 +196,24 @@ // Is this a string that appears to be a number? | ||
// Check for Infinity | ||
if (v === 'Infinity' || v === '-Infinity') { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
return +v; | ||
} | ||
// // Check for Infinity | ||
// if(v === 'Infinity' || v === '-Infinity') { | ||
// throw new Error('E_runtimeInputTypeCoercionError'); | ||
// } | ||
// var num = v * 1; | ||
// if(!_.isNumber(num)) { | ||
// throw new Error('E_runtimeInputTypeCoercionError'); | ||
// } | ||
var num = v * 1; | ||
if(!_.isNumber(num)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
// return (num === 0 && !v.match(/^0+$/)) ? 0 : num; | ||
if (_.isNaN(num)) { | ||
return 0; | ||
} | ||
return (num === 0 && !v.match(/^0+$/)) ? 0 : num; | ||
}, | ||
base: 0, | ||
getBase: function (){ | ||
@@ -271,6 +229,8 @@ return 0; | ||
// Aliases | ||
type.string = type.email = type.url = type.str; | ||
type.boolean = type.bool; | ||
type.str = type.email = type.url = type.string; | ||
type.bool = type.boolean; | ||
type.arr = type.array; | ||
type.integer = type.int; | ||
type.float = type.number; | ||
type.object = type.obj = type.dictionary; | ||
@@ -277,0 +237,0 @@ module.exports = type; |
@@ -6,2 +6,3 @@ /** | ||
var util = require('util'); | ||
var Stream = require('stream').Stream; | ||
var _ = require('lodash'); | ||
@@ -12,3 +13,11 @@ var types = require('./types'); | ||
module.exports = function _validateRecursive (expected, actual, errors){ | ||
/** | ||
* | ||
* @param {*} expected | ||
* @param {*} actual | ||
* @param {Array} errors [errors encountered along the way are pushed here] | ||
* @param {Boolean} ensureSerializable | ||
* @return {*} coerced value | ||
*/ | ||
module.exports = function _validateRecursive (expected, actual, errors, ensureSerializable){ | ||
@@ -19,17 +28,36 @@ // Look up expected type from `types` object using `expected`. | ||
var isExpectingDictionary; | ||
var allowAnyArray; | ||
var allowAnyDictionary; | ||
// console.log('\n'); | ||
// console.log('expecting:',expected, util.format('(a %s)', getDisplayType(actual))); | ||
// console.log('actual:',require('util').inspect(actual, false, null), require('util').format('(a %s)', getDisplayType(actual))); | ||
function getDisplayType(x){ | ||
var displayType; | ||
displayType = typeof x; | ||
try { | ||
displayType = x.constructor.name; | ||
} | ||
catch (e){} | ||
return displayType; | ||
} | ||
// "*" (allow anything) | ||
if (expected === '*'){ | ||
// TODO: reconsider this-- might be confusing... probably better just to pass through `undefined` | ||
return _.isUndefined(actual) ? '' : actual; | ||
} | ||
// ["*"] (allow any array) | ||
if (_.isArray(expected) && expected[0] === '*') { | ||
return _.isUndefined(actual) ? [] : actual; | ||
// Normalize: ["*"] or [] or "array" (allow any array) | ||
if (expected === 'array' || (_.isArray(expected) && (expected[0] === '*' || expected.length === 0))) { | ||
expected = ['*']; | ||
allowAnyArray = true; | ||
} | ||
// {} (allow any dictionary) | ||
if (_.isPlainObject(expected) && _.keys(expected).length === 0) { | ||
return _.isUndefined(actual) ? {} : actual; | ||
// Normalize: "dictionary" or {} (allow any dictionary) | ||
if (expected === 'dictionary' || (_.isPlainObject(expected) && _.keys(expected).length === 0)) { | ||
expected = {}; | ||
allowAnyDictionary = true; | ||
} | ||
// Arrays | ||
@@ -59,6 +87,55 @@ if (_.isArray(expected)) { | ||
} | ||
} | ||
// Ensure that `actual` is JSON-serializable, unless one of the following is true: | ||
// - the `ensureSerializable` flag is disabled | ||
// - we're expecting a "machine", "buffer", or "stream" and the mystery meat looks right enough | ||
// | ||
// Notes: | ||
// + this can be disabled by setting the `ensureSerializable` argument to false (i.e. for performance) | ||
// + this will strip all functions | ||
// + cloning buffers breaks them-- also it is potentially a huge waste of RAM) | ||
// + cloning streams is a bad idea because it disregards the state they're in, event listeners, etc.) | ||
if (ensureSerializable && | ||
expected !== 'machine' && | ||
expected !== 'buffer' && | ||
expected !== 'stream' | ||
) { | ||
// if ( | ||
// // If we're not expecting a buffer, but it looks like we got one, don't serialize it to JSON and back | ||
// // (it will end up looking like an array, which is confusing) | ||
// // !(expected !== 'buffer' && _.isObject(actual) && actual instanceof Buffer) | ||
// ) { | ||
try { | ||
actual = JSON.parse(JSON.stringify(actual)); | ||
} | ||
catch (e){ | ||
// Push an E_INVALID_TYPE error | ||
errors.push((function (){ | ||
var err = new Error(util.format( | ||
'An invalid value was specified: \n' + util.inspect(actual, false, null) + '\n\n' + | ||
'This doesn\'t match the specified type: \n' + util.inspect(expected, false, null) | ||
)); | ||
err.code = 'E_INVALID_TYPE'; | ||
// if not expecting a dictionary or an array, and actual value is not | ||
// a dictionary or array, then this is just a "minor" thing. | ||
// No big deal, you know. | ||
if (!isExpectingDictionary && !isExpectingArray && !_.isObject(actual)) { | ||
err.minor = true; | ||
} | ||
return err; | ||
})()); | ||
actual = {}; | ||
} | ||
// } | ||
} | ||
// TODO: probably should clone machine defs as well. | ||
// Default the coercedValue to the actual value. | ||
@@ -69,9 +146,10 @@ var coercedValue = actual; | ||
// appropriate base type. | ||
if(types.undefined.is(actual)) { | ||
if(_.isUndefined(actual)) { | ||
coercedValue = expectedType.getBase(); | ||
} | ||
// Check `actual` value against expectedType | ||
// Check `actual` value using `expectedType.is()` | ||
if (!expectedType.is(actual)){ | ||
// Push an E_INVALID_TYPE error | ||
@@ -99,2 +177,3 @@ errors.push((function (){ | ||
catch (e) { | ||
// If that doesn't work, use the base type: | ||
@@ -115,12 +194,28 @@ coercedValue = expectedType.getBase(); | ||
// console.log('coercedValue:',util.inspect(coercedValue, false, null), util.format('(a %s)', (function(){ | ||
// var displayType; | ||
// displayType = typeof coercedValue; | ||
// try { | ||
// displayType = coercedValue.constructor.name; | ||
// } | ||
// catch (e){} | ||
// return displayType; | ||
// })())); | ||
// Build partial result | ||
// (taking recursive step if necessary) | ||
if (isExpectingArray) { | ||
if (allowAnyArray){ | ||
return coercedValue; | ||
} | ||
var arrayItemTpl = expected[0]; | ||
return [_validateRecursive(arrayItemTpl, coercedValue[0], errors)]; | ||
return [_validateRecursive(arrayItemTpl, coercedValue[0], errors, ensureSerializable)]; | ||
} | ||
if (isExpectingDictionary) { | ||
if (allowAnyDictionary){ | ||
return coercedValue; | ||
} | ||
return _.reduce(expected, function (memo, expectedVal, expectedKey) { | ||
console.log('recursively diving into dictionary at key %s...', expectedKey); | ||
memo[expectedKey] = _validateRecursive(expected[expectedKey], coercedValue[expectedKey], errors); | ||
memo[expectedKey] = _validateRecursive(expected[expectedKey], coercedValue[expectedKey], errors, ensureSerializable); | ||
return memo; | ||
@@ -127,0 +222,0 @@ }, {}); |
@@ -89,2 +89,11 @@ /** | ||
// Ensure empty arrays are not recursively parsed. | ||
if (example.length === 0) { | ||
return []; | ||
} | ||
// Handle special case of [*] | ||
else if (example.length === 1 && example[0] === '*') { | ||
return []; | ||
} | ||
// Parse arrays of arrays | ||
@@ -91,0 +100,0 @@ if (_.isArray(example[0])) { |
@@ -79,3 +79,3 @@ /** | ||
// Check if the input is required and missing | ||
if(types.undefined.is(value)) { | ||
if(_.isUndefined(value)) { | ||
@@ -82,0 +82,0 @@ if (input.required) { |
@@ -23,6 +23,5 @@ /** | ||
expected = _.cloneDeep(expected); | ||
actual = _.cloneDeep(actual); | ||
var errors = []; | ||
var result = validateRecursive(expected, actual, errors); | ||
var result = validateRecursive(expected, actual, errors, true); | ||
@@ -33,3 +32,9 @@ if (errors.length) { | ||
err.code = errors[0].code; | ||
err.minor = errors[0].minor; | ||
// If any of the errors are not "minor", then this is not a "minor" error. | ||
err.minor = _.reduce(errors, function(memo, subError) { | ||
if (!memo || !subError.minor) { | ||
return false; | ||
} | ||
return true; | ||
}, true); | ||
err.errors = errors; | ||
@@ -36,0 +41,0 @@ return err; |
{ | ||
"name": "rttc", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Runtime type-checking for JavaScript.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
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
35411
854