Comparing version 3.0.0 to 3.0.1
@@ -221,19 +221,16 @@ /** | ||
// Don't tolerate non-objects, or arrays, or regexps, or dates. | ||
if (!_.isObject(v) || _.isArray(v) || _.isDate(v) || _.isRegExp(v)) { | ||
if (!_.isObject(v) || _.isArray(v) || _.isDate(v) || _.isRegExp(v) || _.isError(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
// Plain objects are ok, but we'll clone them. | ||
// (TODO: this should be configurable) | ||
// Plain objects are ok, pass them on through | ||
// | ||
// (we used to clone them here, but it's not actually necessary, since we | ||
// rebuild those which have explicit nested `example`s, and if this is | ||
// `example: {}`, we're rebuilding the dictionary and all of its contents | ||
// recursively anyways as the last step in `validate-recursive`.) | ||
if (_.isPlainObject(v)){ | ||
return _.clone(v); | ||
return v; | ||
} | ||
// If this is NOT a "plain object" (i.e. some kind of prototypal thing) | ||
// 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 | ||
@@ -240,0 +237,0 @@ throw new Error('E_runtimeInputTypeCoercionError'); |
@@ -179,3 +179,3 @@ /** | ||
if (allowAnyArray) { | ||
return recursivelyCloneAndStripUndefinedKeysFromDictionaries(coercedValue); | ||
return rebuildSanitized(coercedValue); | ||
} | ||
@@ -195,3 +195,3 @@ | ||
if (allowAnyDictionary){ | ||
return recursivelyCloneAndStripUndefinedKeysFromDictionaries(coercedValue); | ||
return rebuildSanitized(coercedValue); | ||
} | ||
@@ -216,77 +216,79 @@ // ...expecting a specific dictionary example | ||
/** | ||
* | ||
* Rebuild a value to make it JSON-compatible (plus some extra affordances) | ||
* This is used when validating/coercing an array or dictionary (and its contents) | ||
* against `example: {}` or `example: []`. | ||
*/ | ||
function recursivelyCloneAndStripUndefinedKeysFromDictionaries(val) { | ||
function rebuildSanitized(val) { | ||
// (note that the recursive validation will not penetrate deeper into this | ||
// object, so we don't have to worry about this function running more than once | ||
// and doing unnecessary extra deep copies at each successive level) | ||
// First, prevent against endless circular recursion: | ||
// (this should never throw, but if it does, it needs to be handled | ||
// by the caller of `rebuildSanitized`) | ||
val = JSON.parse(stringifySafe(val)); | ||
// So doing that parse/stringify thing will remove keys that have undefined values on its own. | ||
// BUT, we still have to worry about removing array items which are undefined. | ||
// And the above operation actually converts these undefined items into `null` items. | ||
// But since we aren't really OK with `null` items either, we can just go ahead and strip | ||
// them out. So we do that in `_recursivelyRebuildAndSanitize`. | ||
// If dictionary: | ||
// ============================================================================== | ||
// Sanitize a dictionary provided for a wildcard dictionary example (`example: {}`) | ||
// The main recursive validation function will not descend into this dictionary because | ||
// it's already met the minimum requirement of being an object. So we need to deep clone | ||
// the provided value for safety; and in the process ensure that it meets the basic minimum | ||
// quality requirements (i.e. no dictionaries within have any undefined keys) | ||
// | ||
// At this point, we can be sure that the provided value is a plain object | ||
// (otherwise it would have been caught by the coercion step, since in order to | ||
// be considered a "dictionary", a value has to pass a _.isPlainObject() check) | ||
// Then build a deep copy | ||
// (in the process, remove keys with undefined values from nested dictionaries recursively) | ||
return _recursivelyRebuildAndSanitize(val); | ||
// TODO: | ||
// could consolidate this cloning + stripping undefined keys + prevention against | ||
// ∞-recursion into a single tree traversal, which would triple the efficiency, | ||
// because then instead of doing "stringify", then "parse", then "rebuild", it could | ||
// all be accomplished in just one iteration instead of three. | ||
} | ||
// If array: | ||
// ============================================================================== | ||
// Sanitize an array provided for a wildcard array example (`example: []`) | ||
// The main recursive validation function will not descend into this array because | ||
// it's already met the minimum requirement of being `_.isArray()`. So we need to | ||
// deep clone the provided value for safety; and in the process ensure that it meets | ||
// the basic minimum quality requirements (i.e. no dictionaries within have any undefined | ||
// keys) | ||
// | ||
// (NOTE: `example: ['*']` won't make it here because it will be picked up | ||
// by the recursive validation. And so it's different-- it will contain | ||
// the original items, and therefore may contain dictionaries w/ undefined keys) | ||
// | ||
// At this point, we can be sure that the provided value is an array | ||
// (otherwise it would have been caught by the coercion step, since in order to | ||
// be considered an "array", a value has to pass a _.isArray() check) | ||
// (note that the recursive validation will not penetrate deeper into this | ||
// object, so we don't have to worry about this function running more than once | ||
// and doing unnecessary extra deep copies at each successive level) | ||
function _recursivelyCloneAndStripUndefinedKeys (val) { | ||
if (_.isArray(val)) { | ||
return _.reduce(val,function (memo, item, i) { | ||
memo.push(_recursivelyCloneAndStripUndefinedKeys(item)); | ||
return memo; | ||
}, []); | ||
} | ||
else if (_.isObject(val)) { | ||
return _.reduce(val,function (memo, subVal, key) { | ||
if (!_.isUndefined(subVal)) { | ||
memo[key] = _recursivelyCloneAndStripUndefinedKeys(subVal); | ||
} | ||
return memo; | ||
}, {}); | ||
} | ||
else { | ||
return val; | ||
} | ||
} | ||
// If dictionary: | ||
// ============================================================================== | ||
// Sanitize a dictionary provided for a wildcard dictionary example (`example: {}`) | ||
// The main recursive validation function will not descend into this dictionary because | ||
// it's already met the minimum requirement of being an object. So we need to deep clone | ||
// the provided value for safety; and in the process ensure that it meets the basic minimum | ||
// quality requirements (i.e. no dictionaries within have any keys w/ invalid values) | ||
// If array: | ||
// ============================================================================== | ||
// Sanitize an array provided for a wildcard array example (`example: []`) | ||
// The main recursive validation function will not descend into this array because | ||
// it's already met the minimum requirement of being `_.isArray()`. So we need to | ||
// deep clone the provided value for safety; and in the process ensure that it meets | ||
// the basic minimum quality requirements (i.e. no dictionaries within have any keys w/ | ||
// invalid values) | ||
// | ||
// We also don't include invalid items in the rebuilt array. | ||
// | ||
// (NOTE: `example: ['*']` won't make it here because it will be picked up | ||
// by the recursive validation. And so it's different-- it will contain | ||
// the original items, and therefore may contain dictionaries w/ keys w/ invalid values) | ||
function _recursivelyRebuildAndSanitize (val) { | ||
if (_.isArray(val)) { | ||
return _.reduce(val,function (memo, item, i) { | ||
if (!_.isUndefined(item) && !_.isNull(item)) { | ||
memo.push(_recursivelyRebuildAndSanitize(item)); | ||
} | ||
return memo; | ||
}, []); | ||
} | ||
// First, prevent against endless circular recursion: | ||
// (this should never throw, but if it does, it needs to be handled | ||
// by the caller of `recursivelyCloneAndStripUndefinedKeysFromDictionaries`) | ||
val = JSON.parse(stringifySafe(val)); | ||
// TODO: | ||
// could consolidate this cloning + stripping undefined keys + prevention against | ||
// ∞-recursion into a single tree traversal, which would double the efficiency. | ||
// Then build a deep copy | ||
// (in the process, remove keys with undefined values from nested dictionaries recursively) | ||
return _recursivelyCloneAndStripUndefinedKeys(val); | ||
else if (_.isObject(val)) { | ||
return _.reduce(val,function (memo, subVal, key) { | ||
if (!_.isUndefined(subVal) && !_.isNull(subVal)) { | ||
memo[key] = _recursivelyRebuildAndSanitize(subVal); | ||
} | ||
return memo; | ||
}, {}); | ||
} | ||
else { | ||
return val; | ||
} | ||
} | ||
@@ -296,3 +298,2 @@ | ||
/** | ||
@@ -328,2 +329,16 @@ * This was modified by @mikermcneil from @isaacs' json-stringify-safe | ||
// Do some advanced serialization | ||
if (_.isError(value)){ | ||
value = value.stack; | ||
} | ||
else if (_.isRegExp(value)){ | ||
value = value.toString(); | ||
} | ||
else if (_.isFunction(value)){ | ||
value = value.toString(); | ||
} | ||
else if (_.isArray(value)) { | ||
} | ||
if (!replacer) { | ||
@@ -330,0 +345,0 @@ return value; |
{ | ||
"name": "rttc", | ||
"version": "3.0.0", | ||
"version": "3.0.1", | ||
"description": "Runtime type-checking for JavaScript.", | ||
@@ -32,3 +32,3 @@ "main": "index.js", | ||
"dependencies": { | ||
"lodash": "~2.4.1" | ||
"lodash": "^3.8.0" | ||
}, | ||
@@ -35,0 +35,0 @@ "devDependencies": { |
@@ -62,12 +62,14 @@ # rttc | ||
+ For "stream", base value is an empty readable buffer stream (i.e. not in object mode) | ||
+ For "machine", base value is a no-op machine that calls its success exit. | ||
--> | ||
#### Edge cases | ||
+ `undefined` is never valid. | ||
+ `null` is never valid. | ||
+ `NaN` is never valid. | ||
+ `Infinity` is never valid. | ||
+ `-Infinity` is never valid. | ||
+ `null` is only valid against `example: '*'`. | ||
+ `NaN` is only valid against `example: '*'`. | ||
+ `Infinity` is only valid against `example: '*'`. | ||
+ `-Infinity` is only valid against `example: '*'`. | ||
@@ -74,0 +76,0 @@ |
@@ -145,3 +145,3 @@ // Export the array of tests below. | ||
//////////////////////////////////////////// | ||
// DICTIONARIES | ||
// DICTIONARIES (json-serializable) | ||
//////////////////////////////////////////// | ||
@@ -187,6 +187,6 @@ | ||
//////////////////////////////////////////// | ||
// ARRAYS | ||
// (all of the tests below pass w/ either [] or ['*'] | ||
// however note they do have subtle differences re: strictEq) | ||
// ARRAYS (json-serializable) | ||
// (all of the tests below pass w/ [], not necessarily ['*']) | ||
//////////////////////////////////////////// | ||
@@ -228,3 +228,52 @@ | ||
////////////////////////////////////////////////////// | ||
// nested contents of `example: []` and `example: {}` | ||
////////////////////////////////////////////////////// | ||
// Follow JSON-serialization rules for nested objects within `example: []` and `example: {}` | ||
// with the following exceptions: | ||
// • convert Error instances to their `.stack` property (a string) | ||
// • convert RegExp instances to a string | ||
// • convert functions to a string | ||
// • after doing the rest of the things, prune undefined/null items | ||
// • after doing the rest of the things, strip keys w/ undefined/null values | ||
{ example: {}, actual: { x: undefined }, result: {} }, | ||
{ example: {}, actual: { x: NaN }, result: {} }, | ||
{ example: {}, actual: { x: Infinity }, result: {} }, | ||
{ example: {}, actual: { x: -Infinity }, result: {} }, | ||
{ example: {}, actual: { x: null }, result: {} }, | ||
{ example: {}, actual: { x: function foo(a,b){return a+' '+b;} }, result: { x: 'function foo(a,b){return a+\' \'+b;}' } }, | ||
// { example: {}, actual: { x: undefined, null, NaN, -Infinity, Infinity, function(){} }, result: [] }, | ||
{ example: {}, actual: { x: /some regexp/ig }, result: {x:'/some regexp/gi' }}, | ||
{ example: {}, actual: { x: new Date('November 5, 1605 GMT') }, result: {x: '1605-11-05T00:00:00.000Z'} }, | ||
// Skip Readable stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: {}, actual: { x: new (require('stream').Readable)() }, result: { x: { _readableState: {},readable: true,_events: {},_maxListeners: 10 } } }, | ||
// Skip Buffer stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: {}, actual: { x: new Buffer('asdf') } , result: {x: {}} }, | ||
(function (){ | ||
// Hard-code a fake `.stack` to avoid differences between computers that would cause tests to fail | ||
var e = new Error('asdf'); | ||
e.stack = 'fake_error'; | ||
return { example: {}, actual: { x: e }, result: {x:'fake_error'} }; | ||
})(), | ||
{ example: [], actual: [undefined], result: [] }, | ||
{ example: [], actual: [null], result: [] }, | ||
{ example: [], actual: [NaN], result: [] }, | ||
{ example: [], actual: [Infinity], result: [] }, | ||
{ example: [], actual: [-Infinity], result: [] }, | ||
{ example: [], actual: [function foo(a,b){return a+' '+b;}], result: ['function foo(a,b){return a+\' \'+b;}'] }, | ||
{ example: [], actual: [/some regexp/gi], result: ['/some regexp/gi'] }, | ||
{ example: [], actual: [new Date('November 5, 1605 GMT')], result: ['1605-11-05T00:00:00.000Z'] }, | ||
// Skip Readable stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: [], actual: [new (require('stream').Readable)()], result: [ { _readableState: {},readable: true,_events: {},_maxListeners: 10 }] }, | ||
// Skip Buffer stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: [], actual: [new Buffer('asdf')], result: [{}] }, | ||
(function (){ | ||
var e = new Error('asdf'); | ||
e.stack = 'fake_error'; | ||
return { example: [], actual: [e], result: ['fake_error'] }; | ||
})(), | ||
//////////////////////////////////////////// | ||
@@ -275,3 +324,3 @@ // example: * (aka undefined) | ||
{ example: undefined, actual: /some regexp/, result: /some regexp/ }, | ||
{ example: undefined, actual: /some regexp/gi, result: /some regexp/gi }, | ||
{ example: undefined, actual: new Date('November 5, 1605 GMT'), result: new Date('November 5, 1605 GMT') }, | ||
@@ -358,2 +407,24 @@ | ||
// Leave `undefined` items from arrays and nested arrays alone (`*` case) | ||
{ | ||
example: '*', | ||
actual: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}], | ||
result: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}] | ||
}, | ||
// Leave `undefined` items from arrays and nested arrays (`[*]` case) | ||
// (because '*' leaves them there) | ||
{ | ||
example: ['*'], | ||
actual: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}], | ||
result: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}] | ||
}, | ||
// Prune `undefined` items from arrays and nested arrays (`[]` case) | ||
{ | ||
example: [], | ||
actual: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}], | ||
result: [{a: 3}, {a: 5}, {a:7}, {a:9, b:[9,2,4,8]}] | ||
}, | ||
// Ensure that nested dictionaries inside of an array passed | ||
@@ -360,0 +431,0 @@ // through `example: ['*']` are NOT stripped of keys with undefined values-- |
@@ -149,3 +149,3 @@ // Export the array of tests below | ||
//////////////////////////////////////////// | ||
// DICTIONARIES | ||
// DICTIONARIES (json-serializable) | ||
//////////////////////////////////////////// | ||
@@ -192,9 +192,8 @@ | ||
{ example: {}, actual: new Error('asdf'), result: {} }, // TODO: consider enhancing this behavior to guarantee e.g. `.message` (string), `.stack` (string), `.code` (string), and `.status` (number). Needs community discussion | ||
{ example: {}, actual: new Error('asdf'), error: true }, | ||
//////////////////////////////////////////// | ||
// ARRAYS | ||
// (all of the tests below pass w/ either [] or ['*'] | ||
// however note they do have subtle differences re: strictEq) | ||
// ARRAYS (json-serializable) | ||
// (all of the tests below pass w/ [], not necessarily ['*']) | ||
//////////////////////////////////////////// | ||
@@ -237,2 +236,54 @@ | ||
////////////////////////////////////////////////////// | ||
// nested contents of `example: []` and `example: {}` | ||
////////////////////////////////////////////////////// | ||
// Follow JSON-serialization rules for nested objects within `example: []` and `example: {}` | ||
// with the following exceptions: | ||
// • convert Error instances to their `.stack` property (a string) | ||
// • convert RegExp instances to a string | ||
// • convert functions to a string | ||
// • after doing the rest of the things, prune undefined/null items | ||
// • after doing the rest of the things, strip keys w/ undefined/null values | ||
{ example: {}, actual: { x: undefined }, result: {} }, | ||
{ example: {}, actual: { x: NaN }, result: {} }, | ||
{ example: {}, actual: { x: Infinity }, result: {} }, | ||
{ example: {}, actual: { x: -Infinity }, result: {} }, | ||
{ example: {}, actual: { x: null }, result: {} }, | ||
{ example: {}, actual: { x: function foo(a,b){return a+' '+b;} }, result: { x: 'function foo(a,b){return a+\' \'+b;}' } }, | ||
// { example: {}, actual: { x: undefined, null, NaN, -Infinity, Infinity, function(){} }, result: [] }, | ||
{ example: {}, actual: { x: /some regexp/ig }, result: {x:'/some regexp/gi' }}, | ||
{ example: {}, actual: { x: new Date('November 5, 1605 GMT') }, result: {x: '1605-11-05T00:00:00.000Z'} }, | ||
// Skip Readable stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: {}, actual: { x: new (require('stream').Readable)() }, result: { x: { _readableState: {},readable: true,_events: {},_maxListeners: 10 } } }, | ||
// Skip Buffer stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: {}, actual: { x: new Buffer('asdf') } , result: {x: {}} }, | ||
(function (){ | ||
// Hard-code a fake `.stack` to avoid differences between computers that would cause tests to fail | ||
var e = new Error('asdf'); | ||
e.stack = 'fake_error'; | ||
return { example: {}, actual: { x: e }, result: {x:'fake_error'} }; | ||
})(), | ||
{ example: [], actual: [undefined], result: [] }, | ||
{ example: [], actual: [null], result: [] }, | ||
{ example: [], actual: [NaN], result: [] }, | ||
{ example: [], actual: [Infinity], result: [] }, | ||
{ example: [], actual: [-Infinity], result: [] }, | ||
{ example: [], actual: [function foo(a,b){return a+' '+b;}], result: ['function foo(a,b){return a+\' \'+b;}'] }, | ||
{ example: [], actual: [/some regexp/gi], result: ['/some regexp/gi'] }, | ||
{ example: [], actual: [new Date('November 5, 1605 GMT')], result: ['1605-11-05T00:00:00.000Z'] }, | ||
// Skip Readable stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: [], actual: [new (require('stream').Readable)()], result: [ { _readableState: {},readable: true,_events: {},_maxListeners: 10 }] }, | ||
// Skip Buffer stream tests for now since the enumerable properties vary between Node.js versions. | ||
// { example: [], actual: [new Buffer('asdf')], result: [{}] }, | ||
(function (){ | ||
var e = new Error('asdf'); | ||
e.stack = 'fake_error'; | ||
return { example: [], actual: [e], result: ['fake_error'] }; | ||
})(), | ||
//////////////////////////////////////////// | ||
@@ -393,2 +444,24 @@ // RECURSIVE OBJECTS | ||
// Prune `undefined` items from arrays and nested arrays (`[]` case) | ||
{ | ||
example: [], | ||
actual: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}], | ||
result: [{a: 3}, {a: 5}, {a:7}, {a:9, b:[9,2,4,8]}] | ||
}, | ||
// DO allow `undefined` items from arrays and nested arrays (`*` case) | ||
{ | ||
example: '*', | ||
actual: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}], | ||
result: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}] | ||
}, | ||
// Don't allow `undefined` items from arrays and nested arrays (`[*]` case) | ||
// (because '*' does not allow `undefined`) | ||
{ | ||
example: ['*'], | ||
actual: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}], | ||
error: true | ||
}, | ||
// Ensure that nested dictionaries inside of an array passed | ||
@@ -395,0 +468,0 @@ // through `example: ['*']` are NOT stripped of keys with undefined values-- |
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
97987
2087
297
+ Addedlodash@3.10.1(transitive)
- Removedlodash@2.4.2(transitive)
Updatedlodash@^3.8.0