Comparing version 0.2.2 to 0.2.3
@@ -7,6 +7,5 @@ /** | ||
var _ = require('lodash'); | ||
var types = require('./types'); | ||
var validateRecursive = require('./helpers/validate-recursive'); | ||
/** | ||
@@ -27,5 +26,9 @@ * Coerce value to type schema | ||
var errors = []; | ||
var result = _coerceRecursive(expected, actual, errors); | ||
var result = validateRecursive(expected, actual, errors); | ||
// Strip out "E_INVALID_TYPE" errors- they are ok if we're just coercing. | ||
_.remove(errors, {code: 'E_INVALID_TYPE'}); | ||
if (errors.length) { | ||
throw (function (){ | ||
@@ -41,86 +44,1 @@ var err = new Error(util.format('%d error(s) coercing value:\n', errors.length, errors)); | ||
}; | ||
function _coerceRecursive (expected, actual, errors){ | ||
// Look up expected type from `types` object using `expected`. | ||
var expectedType; | ||
var isExpectingArray; | ||
var isExpectingDictionary; | ||
// Arrays | ||
if (_.isArray(expected)) { | ||
expectedType = types.arr; | ||
isExpectingArray = true; | ||
} | ||
// Dictionaries | ||
else if (_.isObject(expected)) { | ||
expectedType = types.obj; | ||
isExpectingDictionary = true; | ||
} | ||
// Primitives | ||
else { | ||
expectedType = types[expected]; | ||
// If this refers to an unknown type, default | ||
// to a string's base type and remember the error. | ||
if (_.isUndefined(expectedType)) { | ||
errors.push((function (){ | ||
var err = new Error('Unknown type: '+expected); | ||
err.code = 'E_UNKNOWN_TYPE'; | ||
return err; | ||
})()); | ||
return types.string.getBase(); | ||
} | ||
} | ||
// Default the coercedValue to the actual value. | ||
var coercedValue = actual; | ||
// If the actual value is undefined, fill in with the | ||
// appropriate base type. | ||
if(types.undefined.is(actual)) { | ||
coercedValue = expectedType.getBase(); | ||
} | ||
// Check `actual` value against expectedType | ||
if (!expectedType.is(actual)){ | ||
// Invalid expected type. Try to coerce: | ||
try { | ||
coercedValue = expectedType.to(actual); | ||
} | ||
catch (e) { | ||
// If that doesn't work, use the base type: | ||
coercedValue = expectedType.getBase(); | ||
// Saving this error here in case we need it: | ||
// (but turning it off for now because this function | ||
// is very forgiving and kind) | ||
// 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'; | ||
// return err; | ||
// })()); | ||
} | ||
} | ||
// Build partial result | ||
// (taking recursive step if necessary) | ||
if (isExpectingArray) { | ||
var arrayItemTpl = expected[0]; | ||
return [_coerceRecursive(arrayItemTpl, coercedValue[0], errors)]; | ||
} | ||
if (isExpectingDictionary) { | ||
return _.reduce(expected, function (memo, expectedVal, expectedKey) { | ||
memo[expectedKey] = _coerceRecursive(expected[expectedKey], coercedValue[expectedKey], errors); | ||
return memo; | ||
}, {}); | ||
} | ||
return coercedValue; | ||
} |
@@ -13,2 +13,5 @@ /** | ||
// Backwards compat. | ||
/** | ||
@@ -15,0 +18,0 @@ * Given a definition and a values object, ensure our types match up. |
@@ -5,10 +5,37 @@ /** | ||
var util = require('util'); | ||
var _ = require('lodash'); | ||
var rttc = require('./rttc'); | ||
var validateRecursive = require('./helpers/validate-recursive'); | ||
/** | ||
* Validate a bit of mystery meat (`actual`) against an `expected` type schema, | ||
* saving up any fatal errors and throwing them in a lump, and otherwise | ||
* returning the value that was accepted (i.e. because some coercion might have | ||
* occurred) | ||
* | ||
* @param {~Schema} expected type schema | ||
* @param {*} actual "mystery meat" | ||
* @return {<expected>} | ||
*/ | ||
module.exports = function validate (expected, actual) { | ||
module.exports = function validate (expected, actual) { | ||
// TODO: make this the top-level method instead of rttc | ||
// and share common fn dependencies | ||
return rttc(expected, actual); | ||
// Avoid damaging the provided parameters. | ||
expected = _.cloneDeep(expected); | ||
actual = _.cloneDeep(actual); | ||
var errors = []; | ||
var result = validateRecursive(expected, actual, 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; | ||
err.errors = errors; | ||
return err; | ||
})(); | ||
} | ||
return result; | ||
}; | ||
{ | ||
"name": "rttc", | ||
"version": "0.2.2", | ||
"version": "0.2.3", | ||
"description": "Runtime type-checking for JavaScript.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
223
README.md
# rttc | ||
Runtime type-checking for JavaScript. | ||
Runtime (recursive) type-checking for JavaScript. | ||
@@ -10,6 +10,10 @@ ## Installation | ||
```js | ||
var rttc = require('rttc'); | ||
``` | ||
## Rules | ||
## Philosophy | ||
#### General | ||
@@ -22,37 +26,54 @@ | ||
#### Coercion vs. Validation | ||
+ `.validate()` either returns a (potentially "lightly" coerced) version of the value that was accepted, or it throws. The "lightly" coerced value might turn `"3"` into `3`, `"true"` into `true`, `-4.5` into `"-4.5"`, etc. | ||
+ `.coerce()` ALWAYS returns an acceptable version of the value, even if it has to mangle it to get there. | ||
## Legacy Usage | ||
#### Dictionaries | ||
The usage of this module is changing, but backwards compatibility will be maintained up until the first major version bump (v1.0.0). | ||
See tests for more details. | ||
+ Dictionaries (i.e. plain old JavaScript objects) in type schemas can be infinitely nested. Type validation and coercion will proceed through the nested objects recursively. | ||
#### rttc.rttc(expectations, inputValues, [options]) | ||
```js | ||
{ | ||
id: 'number', | ||
name: 'string', | ||
isAdmin: 'boolean', | ||
mom: { | ||
id: 'number', | ||
name: 'string', | ||
occupation: { | ||
title: 'string', | ||
workplace: 'string' | ||
} | ||
} | ||
} | ||
``` | ||
Validate and/or coerce a set of input values against a set of expectations (defined as input definitions.) | ||
#### Arrays | ||
Two options may be provided: | ||
+ `coerce` - before failing, attempt to coerce not-quite-right input values to their expected type. | ||
+ `base` - if an input value is missing, fill in its place in the result to the "base type" (falsy value) | ||
+ Arrays in type schemas must be homogeneous and have exactly one item; that is, if you want to validate an array, you only need to provide the type/schema for the first item in the array, e.g.: | ||
```js | ||
require('rttc').rttc({ | ||
foo: { | ||
type: 'string', | ||
required: true | ||
}, | ||
bar: { | ||
type: { baz: {name: 'string'} }, | ||
required: false | ||
[ | ||
{ | ||
id: 'number', | ||
name: 'string', | ||
email: 'string', | ||
age: 'number', | ||
isAdmin: 'boolean', | ||
favoriteColors: ['string'], | ||
friends: [ | ||
{ | ||
id: 'number', | ||
name: 'string' | ||
} | ||
] | ||
} | ||
}, { | ||
foo: 'hi', | ||
bar: { | ||
baz: { | ||
name: 'Rick' | ||
} | ||
} | ||
}); | ||
] | ||
``` | ||
## Usage | ||
#### rttc.infer(value) | ||
@@ -115,2 +136,152 @@ | ||
#### `.validate(expected, actual)` | ||
```js | ||
rttc.validate('string', 'foo'); | ||
// => 'foo' | ||
rttc.validate('number', 4.5); | ||
// => 4.5 | ||
rttc.validate('boolean', true); | ||
// => true | ||
rttc.validate('string', -2); | ||
// => '-2' | ||
rttc.validate('string', false); | ||
// => 'false' | ||
rttc.validate('number', '3'); | ||
// => 3 | ||
rttc.validate('boolean', 'true'); | ||
// => true | ||
rttc.validate({ | ||
user: { | ||
friends: [{ | ||
name: 'Lenny', | ||
age: 77 | ||
}] | ||
}, { | ||
user: { | ||
friends: [{ | ||
name: 'Lenny', | ||
age: '77' | ||
}] | ||
} | ||
}); | ||
// => | ||
/* | ||
{ | ||
user: { | ||
friends: [{ | ||
name: 'Lenny', | ||
age: 77 | ||
}] | ||
} | ||
} | ||
*/ | ||
``` | ||
If value cannot be properly coerced, throws error with code=`E_INVALID_TYPE`: | ||
```js | ||
rttc.validate('number', 'asdf'); | ||
// throws E_INVALID_TYPE | ||
``` | ||
#### `.coerce(expected, actual)` | ||
```js | ||
rttc.coerce('string', 'foo'); | ||
// => 'foo' | ||
rttc.coerce('number', 4.5); | ||
// => 4.5 | ||
rttc.coerce('boolean', true); | ||
// => true | ||
rttc.coerce('string', -2); | ||
// => '-2' | ||
rttc.coerce('string', false); | ||
// => 'false' | ||
rttc.coerce('number', '3'); | ||
// => 3 | ||
rttc.coerce('boolean', 'true'); | ||
// => true | ||
``` | ||
If value cannot be properly coerced, defaults to base type: | ||
``` | ||
rttc.coerce('number', 'asdf'); | ||
// => 0 | ||
rttc.coerce('boolean', 'asdf'); | ||
// => false | ||
rttc.coerce({ | ||
user: { | ||
friends: [{ | ||
name: 'Lenny', | ||
age: 77 | ||
}] | ||
}, 'err... some dude who\'s friends with lenny?'); | ||
// => | ||
/* | ||
{ | ||
user: { | ||
friends: [{ | ||
name: 'Lenny', | ||
age: 77 | ||
}] | ||
} | ||
} | ||
*/ | ||
``` | ||
## Legacy Usage | ||
The usage of this module is changing, but backwards compatibility will be maintained up until the first major version bump (v1.0.0). | ||
See tests for more details. | ||
#### rttc.rttc(expectations, inputValues, [options]) | ||
Validate and/or coerce a set of input values against a set of expectations (defined as input definitions.) | ||
Two options may be provided: | ||
+ `coerce` - before failing, attempt to coerce not-quite-right input values to their expected type. | ||
+ `base` - if an input value is missing, fill in its place in the result to the "base type" (falsy value) | ||
```js | ||
require('rttc').rttc({ | ||
foo: { | ||
type: 'string', | ||
required: true | ||
}, | ||
bar: { | ||
type: { baz: {name: 'string'} }, | ||
required: false | ||
} | ||
}, { | ||
foo: 'hi', | ||
bar: { | ||
baz: { | ||
name: 'Rick' | ||
} | ||
} | ||
}); | ||
``` | ||
#### rttc.types | ||
@@ -117,0 +288,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
67354
25
1938
349