Socket
Socket
Sign inDemoInstall

rttc

Package Overview
Dependencies
Maintainers
4
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rttc - npm Package Compare versions

Comparing version 0.1.6 to 0.2.0

lib/helpers/coerce-primitive.js

136

lib/coerce.js

@@ -5,32 +5,120 @@ /**

var util = require('util');
var _ = require('lodash');
var rttc = require('./rttc');
var infer = require('./infer');
var types = require('./types');
module.exports = function coerce (expected, actual) {
// Transform `expected` into rttc-compatible schema
// e.g. {
// foo: { type: 'string', required: false },
// bar: { type: { baz: 'number' }, required: false }
// }
var rttcSchema = {
x: {
type: infer(expected)
/**
* Coerce value to type schema
* (very forgiving)
*
* @param {~Schema} expected type schema
* @param {*} actual "mystery meat"
* @return {<expected>}
*/
module.exports = function coerce (expected, actual){
// Avoid damaging the provided parameters.
expected = _.cloneDeep(expected);
actual = _.cloneDeep(actual);
var errors = [];
var result = _coerceRecursive(expected, actual, errors);
if (errors.length) {
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;
};
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();
}
};
// Transform `actual` into rttc-compatible value set
// e.g. {
// foo: 'asdga'
// bar: { baz: 32 }
// }
var rttcValueSet = {
x: actual
};
}
return rttc(rttcSchema, rttcValueSet, {
coerce: true
}).x;
};
// 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;
}

2

lib/infer.js

@@ -6,3 +6,3 @@ /**

var _ = require('lodash');
var types = require('./types');
var types = require('./helpers/types');

@@ -9,0 +9,0 @@

@@ -8,90 +8,9 @@ /**

var infer = require('./infer');
var types = require('./types');
var types = require('./helpers/types');
var validateAndOrCoerceObject = require('./helpers/validate-and-or-coerce-object');
var validatePrimitive = require('./helpers/validate-primitive');
var coercePrimitive = require('./helpers/coerce-primitive');
/**
* Run-time type checking. Given a set of typed inputs, ensure the run-time configured
* inputs are valid.
* ________________________________________________________________________________
* @param {String} type the expected type
*
* @param {*} val the "mystery meat"
*
* @param {Object} flags an object of boolean flags
* @property {Boolean} coerce
* @property {Boolean} baseType
* ________________________________________________________________________________
* @returns {*} If everything worked
* the value that what was formerly "mystery meat", now coerced to `type`.
* @throws {E_UNDEFINED_VAL} If there were validation errors
*/
function coercePrimitive (type, val, flags) {
var coerceFlag = flags && flags.coerce || false;
var baseTypeFlag = flags && flags.baseType || false;
// Map types that are shorthand
var to = type;
if(type === 'string') to = 'str';
if(type === 'boolean') to = 'bool';
// WARNING: Will throw if the value can't be coerced
if(!coerceFlag) return val;
try {
// If val === undefined lets throw and either error or use the base type
if(val === undefined) {
var err = new Error();
err.code = 'E_UNDEFINED_VAL';
err.message = 'Undefined value';
throw err;
}
val = types[to].to(val);
}
catch (e) {
// If we want the base type for this input catch it here
if(!baseTypeFlag) throw e;
val = types[to].base && types[to].base();
}
return val;
}
/**
* Given a type and primitive value, check that it matches.
* ________________________________________________________________________________
* @param {String} type the expected type
*
* @param {*} val the "mystery meat"
* ________________________________________________________________________________
* @return {Boolean} is this a match?
*/
function validatePrimitive (type, val) {
// Check for string
if(type === 'string') {
return types.str.is(val);
}
// Check for number
if(type === 'number') {
return types.number.is(val);
}
// Check for boolean
if(type === 'boolean') {
return types.bool.is(val);
}
return false;
}
/**
* Given a definition and a values object, ensure our types match up.

@@ -112,3 +31,3 @@ * ________________________________________________________________________________

function validateInputSchema(def, val, options) {
function validateInputs(def, val, options) {

@@ -127,39 +46,4 @@ options = options || {};

var parseObject = function(input, value) {
_.each(_.keys(input), function(key) {
var _input = input[key];
var _value = value[key];
// If the input is an object continue recursively parsing it
if(types.obj.is(_input)) {
parseObject(_input, _value);
return;
}
_value = coercePrimitive(_input, _value, { coerce: coerce, baseType: baseType });
var valid = validatePrimitive(_input, _value);
if(!valid) {
var err = new Error();
err.code = 'E_INVALID_TYPE';
err.message = 'Invalid input value '+ value;
throw new Error(err);
}
value[key] = _value;
});
// Find the difference in the input and the value and remove any keys that
// exist on the value but not on the input definition.
var inputKeys = _.keys(input);
var valueKeys = _.keys(value);
var invalidKeys = _.difference(valueKeys, inputKeys);
_.each(invalidKeys, function(key) {
delete value[key];
});
return value;
};
// If we don't have an object then just check the tuple
// If we don't have an object as our definition then just check the tuple
// If the input type isn't an object or array we can just do a simple type check

@@ -249,3 +133,3 @@ if(!_.isPlainObject(def)) {

try {
item = parseObject(input.type[0], item);
item = validateAndOrCoerceObject(input.type[0], item, { coerce: coerce, baseType: baseType });
}

@@ -255,4 +139,4 @@ catch (err) {

err.message = util.format(
'An invalid value was specified. The value ' + item + ' was used \n' +
'and doesn\'t match the specified type: ' + input.type[0]
'An invalid value was specified: \n' + util.inspect(item, false, null) + '\n\n' +
'This doesn\'t match the specified type: \n' + util.inspect(input.type[0], false, null)
);

@@ -296,3 +180,3 @@

try {
value = parseObject(input.type, value);
value = validateAndOrCoerceObject(input.type, value, { coerce: coerce, baseType: baseType });
}

@@ -353,2 +237,2 @@ catch (e) {

module.exports = validateInputSchema;
module.exports = validateInputs;

@@ -1,194 +0,2 @@

/**
* Module dependencies
*/
var _ = require('lodash');
/**
* Basic type definitions.
*
* Roughly based on https://github.com/bishopZ/Typecast.js
* ________________________________________________________________________________
* @type {Object}
*/
var type = {
// 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: {
is: _.isBoolean,
to: function(v) {
if(_.isBoolean(v)) return v;
if(v === 'true') return true;
if(v === 'false') return false;
if(v === 1) return true;
if(v === 0) return false;
if(v === '1') return true;
if(v === '0') return false;
throw new Error('E_runtimeInputTypeCoercionError');
},
base: 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
},
// String
str: {
is: _.isString,
to: function(v) {
if(_.isString(v)) return v;
if(v instanceof Function) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(_.isDate(v)) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(v instanceof Function) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(v instanceof Object) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(v instanceof Array) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(v === Infinity) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(v === NaN) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(v === null) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(type.defined.is(v)) return String(v);
return '';
},
base: ''
},
// Object
obj: {
is: function(v) {
return _.isObject(v) && !type.arr.is(v);
},
to: function(v) {
return {};
},
base: {}
},
// Array
arr: {
is: _.isArray,
to: function(v) {
return _.toArray(v);
},
base: []
},
// Date
'date': {
is: _.isDate,
to: function(v) {
return new Date(v);
},
base: new Date()
},
// Numeric
'number': {
is: function(v) {
return _.isNumber(v) && !type.nan.is(parseFloat(v));
},
to: function(v) {
// Check for Infinity
if(v === Infinity) {
throw new Error('E_runtimeInputTypeCoercionError');
}
if(type.number.is(v)) return v;
if(type.bool.is(v)) return v ? 1 : 0;
if(type.str.is(v)) {
// Check for Infinity
if(v === 'Infinity') {
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;
}
throw new Error('E_runtimeInputTypeCoercionError');
},
base: 0
},
};
// Aliases
type.string = type.email = type.url = type.str;
type.boolean = type.bool;
type.integer = type.int;
type.float = type.number;
module.exports = type;
// Backwards-compat.
module.exports = require('./helpers/types');
{
"name": "rttc",
"version": "0.1.6",
"version": "0.2.0",
"description": "Runtime type-checking for JavaScript.",
"main": "index.js",
"scripts": {
"test": "node ./node_modules/mocha/bin/mocha"
"test": "node ./node_modules/mocha/bin/mocha --recursive"
},

@@ -9,0 +9,0 @@ "keywords": [

@@ -11,2 +11,14 @@ # rttc

## Rules
#### General
+ `null` is never allowed.
+ `NaN` is never allowed.
+ `Infinity` is never allowed.
+ `-Infinity` is never allowed.
## Legacy Usage

@@ -13,0 +25,0 @@

var assert = require('assert');
var coerce = require('../lib/coerce');
describe('Run-time type checking', function() {
describe('Runtime type checking', function() {
describe('type coercion', function() {
describe('.coerce()', function() {
it.skip('should coerce primitive value', function() {
assert.strictEqual(coerce('string', 'string'), 'foo');
describe('to string', function() {
it('should coerce undefined to base type', function() {
assert.strictEqual(coerce('string', undefined), '');
});
it('should fail on null', function (){
assert.throws(function (){
coerce('string', null);
});
});
it('should fail on NaN', function (){
assert.throws(function (){
coerce('string', NaN);
});
});
it('should fail on Infinity', function (){
assert.throws(function (){
coerce('string', Infinity);
});
});
it('should fail on -Infinity', function (){
assert.throws(function (){
coerce('string', -Infinity);
});
});
it('should not touch arbitrary string', function() {
assert.strictEqual(coerce('string', 'foo'), 'foo');
});
it('should not touch empty string', function() {
assert.strictEqual(coerce('string', ''), '');
});
it('should not touch integerish string', function() {
assert.strictEqual(coerce('string', '2382'), '2382');
});
it('should not touch negative integerish string', function() {
assert.strictEqual(coerce('string', '-2382'), '-2382');
});
it('should not touch negative zeroish string', function() {
assert.strictEqual(coerce('string', '0'), '0');
});
it('should not touch decimalish string', function() {
assert.strictEqual(coerce('string', '1.325'), '1.325');
});
it('should not touch negative decimalish string', function() {
assert.strictEqual(coerce('string', '-1.325'), '-1.325');
});
it('should coerce numbers to strings', function() {
assert.strictEqual(coerce('string', 2382), '2382');
assert.strictEqual(coerce('string', -2382), '-2382');
assert.strictEqual(coerce('string', 0), '0');
assert.strictEqual(coerce('string', 1.325), '1.325');
assert.strictEqual(coerce('string', -1.325), '-1.325');
});
});
describe('to number', function (){
it('should coerce undefined to base type', function() {
assert.strictEqual(coerce('number', undefined), 0);
});
it('should fail on null', function (){
assert.throws(function (){
coerce('number', null);
});
});
it('should fail on NaN', function (){
assert.throws(function (){
coerce('number', NaN);
});
});
it('should fail on Infinity', function (){
assert.throws(function (){
coerce('number', Infinity);
});
});
it('should fail on -Infinity', function (){
assert.throws(function (){
coerce('number', -Infinity);
});
});
it('should not touch positive integer', function (){
assert.strictEqual(coerce('number', 3), 3);
});
it('should not touch negative integer', function (){
assert.strictEqual(coerce('number', -3), -3);
});
it('should not touch negative decimal', function (){
assert.strictEqual(coerce('number', -3.2), -3.2);
});
it('should not touch zero', function (){
assert.strictEqual(coerce('number', 0), 0);
});
it('should coerce "3.25" to 3.25', function() {
assert.strictEqual(coerce('number', '3.25'), 3.25);
});
it('should coerce "-3.25" to -3.25', function() {
assert.strictEqual(coerce('number', '-3.25'), -3.25);
});
it('should coerce "0" to 0', function() {
assert.strictEqual(coerce('number', '0'), 0);
});
});
describe('to boolean', function (){
it('should coerce undefined to base type', function() {
assert.strictEqual(coerce('boolean', undefined), false);
});
it('should fail on null', function (){
assert.throws(function (){
coerce('boolean', null);
});
});
it('should fail on NaN', function (){
assert.throws(function (){
coerce('boolean', NaN);
});
});
it('should fail on Infinity', function (){
assert.throws(function (){
coerce('boolean', Infinity);
});
});
it('should fail on -Infinity', function (){
assert.throws(function (){
coerce('boolean', -Infinity);
});
});
it('should not touch true', function() {
assert.strictEqual(coerce('boolean', true), true);
});
it('should not touch false', function() {
assert.strictEqual(coerce('boolean', false), false);
});
it('should coerce "true" to true', function() {
assert.strictEqual(coerce('boolean', 'true'), true);
});
it('should coerce "false" to false', function() {
assert.strictEqual(coerce('boolean', 'false'), false);
});
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc