Comparing version 1.0.2 to 2.0.1
module.exports = { | ||
validate: require('./lib/validate'), | ||
validateStrict: require('./lib/validate-strict'), | ||
coerce: require('./lib/coerce'), | ||
infer: require('./lib/infer') | ||
}; |
@@ -8,2 +8,3 @@ /** | ||
var validateRecursive = require('./helpers/validate-recursive'); | ||
var consolidateErrors = require('./helpers/consolidate-errors'); | ||
@@ -27,2 +28,3 @@ /** | ||
var result = validateRecursive(expected, actual, errors, true); | ||
// (the true => `ensureSerializable` -- i.e. ensure the thingĀ is JSON-serializable) | ||
@@ -35,13 +37,12 @@ // Strip out "E_INVALID_TYPE" errors- they are ok if we're just coercing. | ||
if (errors.length) { | ||
// TODO: | ||
// Note that it would be more efficient to pass in a list of error codes | ||
// to ignore when calling `validateRecursive`, rather than iterating through | ||
// the list of errors afterwards and stripping them out. | ||
throw (function (){ | ||
var err = new Error(util.format('%d error(s) coercing value:\n', errors.length, errors)); | ||
err.code = errors[0].code; | ||
err.errors = errors; | ||
return err; | ||
})(); | ||
} | ||
return result; | ||
// If there are still errors, coallesce the remaining list of errors into a single | ||
// Error object we can throw. | ||
var err = consolidateErrors(errors, 'coercing value'); | ||
if (err) throw err; | ||
else return result; | ||
}; |
@@ -25,3 +25,2 @@ /** | ||
to: function(v) { | ||
if (_.isUndefined(v)){ return false; } | ||
if(_.isBoolean(v)) return v; | ||
@@ -51,13 +50,10 @@ if (_.isNumber(v)){ | ||
if (_.isUndefined(v)){ | ||
return ''; | ||
} | ||
if(_.isString(v)) return v; | ||
if(v instanceof Function) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
// Convert date into zone-independent (UTC) ISO-8601 timestamp | ||
if(_.isDate(v)) { | ||
return v.toJSON(); | ||
} | ||
if(_.isDate(v)) { | ||
if (_.isUndefined(v)){ | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
@@ -119,3 +115,15 @@ } | ||
} | ||
// Don't tolerate regexps or dates. | ||
if (_.isDate(v) || _.isRegExp(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
// But otherwise, if this is NOT a "plain object", determine whether | ||
// it's JSON-compatible and if so, coerce it to that. | ||
try { | ||
return JSON.parse(JSON.stringify(v)); | ||
} | ||
catch (e) {} | ||
// If that doesn't work, give up | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
@@ -143,3 +151,3 @@ }, | ||
// Nuber | ||
// Number | ||
number: { | ||
@@ -151,19 +159,9 @@ is: function(v) { | ||
if (_.isUndefined(v)){ | ||
return 0; | ||
} | ||
// Check for Infinity and NaN | ||
if(v === Infinity || v === -Infinity) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
if (_.isNaN(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
if (_.isNull(v)){ | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
if (v === Infinity || v === -Infinity) throw new Error('E_runtimeInputTypeCoercionError'); | ||
if (_.isNaN(v)) throw new Error('E_runtimeInputTypeCoercionError'); | ||
if (_.isNull(v)) throw new Error('E_runtimeInputTypeCoercionError'); | ||
if (_.isUndefined(v)) throw new Error('E_runtimeInputTypeCoercionError'); | ||
if (_.isObject(v)) throw new Error('E_runtimeInputTypeCoercionError'); | ||
if (_.isObject(v)) return 0; | ||
if(type.number.is(v)) return v; | ||
@@ -192,3 +190,2 @@ if(type.boolean.is(v)) return v ? 1 : 0; | ||
// Check for Infinity | ||
@@ -202,3 +199,2 @@ if (v === 'Infinity' || v === '-Infinity') { | ||
var num = v * 1; | ||
@@ -208,9 +204,3 @@ if(!_.isNumber(num)) { | ||
} | ||
if (_.isNaN(num)) { | ||
return 0; | ||
} | ||
return (num === 0 && !v.match(/^0+$/)) ? 0 : num; | ||
}, | ||
@@ -217,0 +207,0 @@ getBase: function (){ |
@@ -20,3 +20,3 @@ /** | ||
*/ | ||
module.exports = function _validateRecursive (expected, actual, errors, ensureSerializable, meta){ | ||
module.exports = function _validateRecursive (expected, actual, errors, ensureSerializable, meta, strict){ | ||
@@ -31,3 +31,3 @@ // Look up expected type from `types` object using `expected`. | ||
// console.log('\n'); | ||
// console.log('expecting:',expected, util.format('(a %s)', getDisplayType(actual))); | ||
// console.log('expecting:',expected); | ||
// console.log('actual:',require('util').inspect(actual, false, null), require('util').format('(a %s)', getDisplayType(actual))); | ||
@@ -46,8 +46,7 @@ function getDisplayType(x){ | ||
if (expected === '*'){ | ||
// TODO: reconsider this-- might be confusing... probably better just to pass through `undefined` | ||
return _.isUndefined(actual) ? '' : actual; | ||
return actual; | ||
} | ||
// Normalize: ["*"] or [] or "array" (allow any array) | ||
if (expected === 'array' || (_.isArray(expected) && (expected[0] === '*' || expected.length === 0))) { | ||
// Normalize: [] or ['*'] or "array" (allow any array) | ||
if (expected === 'array' || (_.isArray(expected) && (expected.length === 0 || expected[0] === '*'))) { | ||
expected = ['*']; | ||
@@ -62,3 +61,2 @@ allowAnyArray = true; | ||
// Arrays | ||
@@ -90,62 +88,77 @@ if (_.isArray(expected)) { | ||
// console.log('expected = %s || actual = %s (%s)',expected, actual, typeof actual); | ||
// 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' | ||
) { | ||
// // For most actual values, we want to ensure it is pseudo-JSON-serializable. | ||
// // We say "pseudo" because we would rather not allow `null` for now, | ||
// // which is technically allowed in JSON. TODO: expand this comment to reflect reality | ||
// // | ||
// // | ||
// // So specifically, we'll ensure that `actual` is pseudo-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) | ||
// // console.log('ensuring actual value is serializable as JSON...'); | ||
// ) { | ||
try { | ||
actual = JSON.parse(JSON.stringify(actual)); | ||
} | ||
catch (e){ | ||
// Push an E_INVALID_TYPE error | ||
errors.push((function (){ | ||
var msg = 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) | ||
); | ||
if (meta && meta.keyName) { | ||
msg = "For key `"+meta.keyName+"`: " + msg; | ||
} | ||
var err = new Error(msg); | ||
if (meta && meta.keyName) { | ||
err.inputKey = meta.keyName; | ||
} | ||
err.actual = util.inspect(actual, false, null); | ||
err.expected = util.inspect(expected, false, null); | ||
err.code = 'E_INVALID_TYPE'; | ||
// // Formerly we did this: (but not anymore) | ||
// // ================================================================ | ||
// // If we're not expecting a buffer, but it looks like we got one actually, | ||
// // 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){ | ||
// // Build an E_INVALID_TYPE error | ||
// var newErr = (function (){ | ||
// var msg = 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) | ||
// ); | ||
// if (meta && meta.keyName) { | ||
// msg = "For key `"+meta.keyName+"`: " + msg; | ||
// } | ||
// var err = new Error(msg); | ||
// if (meta && meta.keyName) { | ||
// err.inputKey = meta.keyName; | ||
// } | ||
// err.actual = util.inspect(actual, false, null); | ||
// err.expected = 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. | ||
// // 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; | ||
// })(); | ||
// // Don't bother tracking minor errors unless we're in `strict` mode. | ||
// if (!newErr.minor || (strict && newErr.minor)) { | ||
// errors.push(newErr); | ||
// } | ||
// actual = {}; | ||
// } | ||
// } | ||
// // TODO: probably should clone machine defs as well. | ||
// // console.log('(4) actual =',actual); | ||
// Default the coercedValue to the actual value. | ||
@@ -162,6 +175,6 @@ var coercedValue = actual; | ||
if (!expectedType.is(actual)){ | ||
// console.log('.is() failed!'); | ||
// Push an E_INVALID_TYPE error | ||
errors.push((function (){ | ||
// Build an E_INVALID_TYPE error | ||
var newErr = (function (){ | ||
var msg = util.format( | ||
@@ -172,3 +185,3 @@ 'An invalid value was specified: \n' + util.inspect(actual, false, null) + '\n\n' + | ||
if (meta && meta.keyName) { | ||
msg = "For key `"+meta.keyName+"`: " + msg; | ||
msg = 'For key `'+meta.keyName+'`: ' + msg; | ||
} | ||
@@ -183,11 +196,17 @@ var err = new Error(msg); | ||
// 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)) { | ||
// This is considered a "minor" error if it can be coerced without | ||
// causing any errors. | ||
try { | ||
expectedType.to(actual); | ||
err.minor = true; | ||
} | ||
catch (e) {} | ||
return err; | ||
})()); | ||
})(); | ||
// Don't bother tracking minor errors unless we're in `strict` mode. | ||
if (!newErr.minor || (strict && newErr.minor)) { | ||
errors.push(newErr); | ||
} | ||
// Invalid expected type. Try to coerce: | ||
@@ -209,3 +228,3 @@ try { | ||
if (meta && meta.keyName) { | ||
msg = "For key `"+meta.keyName+"`: " + msg; | ||
msg = 'For key `'+meta.keyName+'`: ' + msg; | ||
} | ||
@@ -224,13 +243,2 @@ var err = new Error(msg); | ||
// 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 | ||
@@ -244,3 +252,3 @@ // (taking recursive step if necessary) | ||
return _.reduce(coercedValue, function (memo, coercedVal){ | ||
memo.push(_validateRecursive(arrayItemTpl, coercedVal, errors, ensureSerializable)); | ||
memo.push(_validateRecursive(arrayItemTpl, coercedVal, errors, ensureSerializable, undefined, strict)); | ||
return memo; | ||
@@ -254,4 +262,4 @@ }, []); | ||
return _.reduce(expected, function (memo, expectedVal, expectedKey) { | ||
var keyName = (meta && meta.keyName ? (meta.keyName + ".") : "") + expectedKey; | ||
memo[expectedKey] = _validateRecursive(expected[expectedKey], coercedValue[expectedKey], errors, ensureSerializable, {keyName: keyName}); | ||
var keyName = (meta && meta.keyName ? (meta.keyName + '.') : '') + expectedKey; | ||
memo[expectedKey] = _validateRecursive(expected[expectedKey], coercedValue[expectedKey], errors, ensureSerializable, {keyName: keyName}, strict); | ||
return memo; | ||
@@ -258,0 +266,0 @@ }, {}); |
@@ -31,2 +31,7 @@ /** | ||
// Infer type:'*' if `'*'` or `undefined` is provided | ||
if (val === '*' || val === undefined) { | ||
type = '*'; | ||
} | ||
return type; | ||
@@ -94,6 +99,2 @@ } | ||
} | ||
// Handle special case of [*] | ||
else if (example.length === 1 && example[0] === '*') { | ||
return []; | ||
} | ||
@@ -100,0 +101,0 @@ // Parse arrays of arrays |
@@ -8,3 +8,5 @@ /** | ||
var validateRecursive = require('./helpers/validate-recursive'); | ||
var consolidateErrors = require('./helpers/consolidate-errors'); | ||
/** | ||
@@ -22,27 +24,17 @@ * Validate a bit of mystery meat (`actual`) against an `expected` type schema, | ||
// Avoid damaging the provided parameters. | ||
// Avoid damaging `expected` | ||
expected = _.cloneDeep(expected); | ||
var errors = []; | ||
var result = validateRecursive(expected, actual, errors, true); | ||
var result = validateRecursive(expected, actual, errors, true, undefined, false); | ||
// (the `true` => `ensureSerializable` -- i.e. ensure the thingĀ is JSON-serializable, if possible) | ||
// (the `false` => `strict` -- i.e. ignore minor type errors) | ||
if (errors.length) { | ||
throw (function (){ | ||
var err = new Error(util.format('%d error(s) validating value:\n', errors.length, errors)); | ||
err.code = errors[0].code; | ||
// 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; | ||
return err; | ||
})(); | ||
} | ||
return result; | ||
// If there are still errors, coallesce the remaining list of errors into a single | ||
// Error object we can throw. | ||
var err = consolidateErrors(errors, 'validating value'); | ||
if (err) throw err; | ||
else return result; | ||
}; | ||
{ | ||
"name": "rttc", | ||
"version": "1.0.2", | ||
"version": "2.0.1", | ||
"description": "Runtime type-checking for JavaScript.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "node ./node_modules/mocha/bin/mocha --recursive" | ||
"test": "node ./node_modules/mocha/bin/mocha --recursive -R dot" | ||
}, | ||
@@ -9,0 +9,0 @@ "keywords": [ |
@@ -31,2 +31,4 @@ # rttc | ||
<!-- | ||
@@ -67,2 +69,5 @@ TODO: | ||
When validating against a dictionary schema with at least one key, extra keys in the actual value will be stripped out. If the dictionary schema is empty, all actual keys will be left alone. | ||
#### Arrays | ||
@@ -69,0 +74,0 @@ |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
26612
11
616
269
0