From bbcfed0cb8ed784e4915b782792b407ad758bc67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Wed, 16 Dec 2015 10:20:52 +0100 Subject: [PATCH] Revert "Refactor and rework http coercion." This reverts commit b924a79f23a97eaa294bbd60345fb0f0cc77c5a0, as it introduced severe regressions, e.g. `arg=false` coerced to `true`. --- lib/dynamic.js | 91 ++++++++------------- lib/http-coerce.js | 104 ------------------------ lib/http-context.js | 161 +++++++++++++++++++++++++++++++------- lib/http-invocation.js | 14 +++- test/http-context.test.js | 133 ++----------------------------- test/type.test.js | 136 ++++---------------------------- 6 files changed, 198 insertions(+), 441 deletions(-) delete mode 100644 lib/http-coerce.js diff --git a/lib/dynamic.js b/lib/dynamic.js index d25e0639..2438da28 100644 --- a/lib/dynamic.js +++ b/lib/dynamic.js @@ -27,7 +27,7 @@ function Dynamic(val, ctx) { * Object containing converter functions. */ -Dynamic.converters = {}; +Dynamic.converters = []; /** * Define a named type conversion. The conversion is used when a @@ -46,7 +46,7 @@ Dynamic.converters = {}; Dynamic.define = function(name, converter) { converter.typeName = name; - this.converters[name] = converter; + this.converters.unshift(converter); }; /** @@ -63,36 +63,19 @@ Dynamic.canConvert = function(type) { /** * Get converter by type name. * - * If passed an array, will return an array, all coerced to the single - * item in the array. More than one type in an array is not supported. - * - * @param {String|Array} type + * @param {String} type * @returns {Function} */ + Dynamic.getConverter = function(type) { - if (Array.isArray(type) && this.converters[type[0]]) { - if (type.length !== 1) { - throw new Error('Coercing to an array type with more than 1 value is unsupported.'); + var converters = this.converters; + var converter; + for (var i = 0; i < converters.length; i++) { + converter = converters[i]; + if (converter.typeName === type) { + return converter; } - return this.getArrayConverter.bind(this, this.converters[type[0]]); } - return this.converters[type]; -}; - -/** - * If the type passed is an array, get a converter that returns an array. - * Type coercion will be one layer deep: ['a', 2, ['c', 4]] with type - * ['string'] coerces to ['a', '2', '[c,4]']. - * @param {Function} converter Non-array converter fn. - * @param {*} val The value object - * @param {Context} ctx The Remote Context - * @return {Function} Converter - */ -Dynamic.getArrayConverter = function(converter, val, ctx) { - if (!Array.isArray(val)) val = this.converters.array(val, ctx); - return val.map(function(v) { - return converter(v, ctx); - }); }; /** @@ -112,39 +95,29 @@ Dynamic.prototype.to = function(type) { * Built in type converters... */ -Dynamic.define('boolean', function convertToBoolean(val) { - if (val == null) return val; - return Boolean(val); +Dynamic.define('boolean', function convertBoolean(val) { + switch (typeof val) { + case 'string': + switch (val) { + case 'false': + case 'undefined': + case 'null': + case '0': + case '': + return false; + default: + return true; + } + break; + case 'number': + return val !== 0; + default: + return Boolean(val); + } }); -Dynamic.define('number', function convertToNumber(val) { - if (val == null) return val; +Dynamic.define('number', function convertNumber(val) { + if (val === 0) return val; + if (!val) return val; return Number(val); }); - -Dynamic.define('integer', function convertToInteger(val) { - if (val == null) return val; - return Math.floor(Dynamic.getConverter('number')(val)); -}); - -Dynamic.define('string', function convertToString(val) { - if (val == null) return val; - if (typeof val === 'string') return val; - if (typeof val.toString === 'function' && - val.toString !== Object.prototype.toString) return val.toString(); - if (val && typeof val === 'object') return JSON.stringify(val); - throw new Error('Could not properly convert ' + val + ' to a string.'); -}); - -Dynamic.define('array', function convertToArray(val, ctx) { - if (val == null || val === '') return []; - if (Array.isArray(val)) return val; - - // This is not a sloppy conversion, so just wrap in array if it isn't already one. - return [val]; -}); - -// Defined so we can use a type like ['any'] -Dynamic.define('any', function noop(val) { - return val; -}); diff --git a/lib/http-coerce.js b/lib/http-coerce.js deleted file mode 100644 index e34c0942..00000000 --- a/lib/http-coerce.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Expose `httpCoerce` - */ - -module.exports = httpCoerce; - -/** - * Do a sloppy string coercion into a target type. - * Useful for params sent via HTTP params, querystring, headers, or non-JSON body. - * - * @param {*} val Value to coerce. Only works on strings, just returns non-string values. - * @param {string|Array} type Type to coerce to. - * @param {Context} HTTP Context. - */ - -function httpCoerce(val, type, ctx) { - // If an array type if defined, regardless of what type it is, try to coerce the string - // into an array. - if (Array.isArray(type)) { - val = coerceArray(val, ctx); - // Members may need to be coerced as well. - val = val.map(function(v) { - return httpCoerce(v, type[0], ctx); - }); - } else if (type === 'any' || type === 'object') { - if (val && typeof val === 'object') { - // Objects should have all their members coerced. - var props = Object.keys(val); - for (var i = 0, n = props.length; i < n; i++) { - var key = props[i]; - val[key] = httpCoerce(val[key], 'any', ctx); - } - } else { - // If the type specified is 'any', do sloppy string conversion. - val = coerceString(val); - } - } - return val; -} - -/*! - * Integer test regexp. Doesn't match if number has a leading zero. - */ - -var isInt = /^\-?(?:[0-9]|[1-9][0-9]*)$/; - -/*! - * Float test regexp. - */ - -var isFloat = /^([0-9]+)?\.[0-9]+$/; -var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1; - -function coerceString(val) { - if (typeof val !== 'string') return val; - if (coerceMap.hasOwnProperty(val)) return coerceMap[val]; - if (isFloat.test(val) || isInt.test(val)) { - var out = Number(val); - // Cap at MAX_SAFE_INTEGER so we don't lose precision. - if (out > MAX_SAFE_INTEGER) out = val; - return out; - } - // Nothing matched; return string. - return val; -} - -function coerceArray(val, ctx) { - if (val === undefined || val === null || val === '') return []; - if (Array.isArray(val)) return val; - // If it looks like an array, try to parse it. - if (val[0] === '[') { - try { - return JSON.parse(val); - } catch (ex) { /* Do nothing */ } - } - - // The user may set delimiters like ',', or ';' to designate array items - // for easier usage. - var delims = ctx.options && ctx.options.arrayItemDelimiters; - if (delims) { - // Construct delimiter regex if input was an array. Overwrite option - // so this only needs to happen once. - if (Array.isArray(delims)) { - delims = new RegExp(delims.map(escapeRegex).join('|'), 'g'); - ctx.options.arrayItemDelimiters = delims; - } - return val.split(delims); - } - // Alright, not array-like, just wrap it in an array on the way out. - return [val]; -} - -// see http://stackoverflow.com/a/6969486/69868 -function escapeRegex(d) { - return d.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); -} - -// Map of some values to convert directly to primitives. -var coerceMap = { - 'false': false, - 'true': true, - 'undefined': undefined, - 'null': null -}; diff --git a/lib/http-context.js b/lib/http-context.js index 15b643a2..09c0ba32 100644 --- a/lib/http-context.js +++ b/lib/http-context.js @@ -14,7 +14,6 @@ var util = require('util'); var inherits = util.inherits; var assert = require('assert'); var Dynamic = require('./dynamic'); -var httpCoerce = require('./http-coerce'); var js2xmlparser = require('js2xmlparser'); var DEFAULT_SUPPORTED_TYPES = [ 'application/json', 'application/javascript', 'application/xml', @@ -117,7 +116,12 @@ HttpContext.prototype.buildArgs = function(method) { var httpFormat = o.http; var name = o.name || o.arg; var val; - var doSloppyCoerce = true; + + // Support array types, such as ['string'] + var isArrayType = Array.isArray(o.type); + var otype = isArrayType ? o.type[0] : o.type; + otype = (typeof otype === 'string') && otype; + var isAny = !otype || otype.toLowerCase() === 'any'; // This is an http method keyword, which requires special parsing. if (httpFormat) { @@ -147,17 +151,14 @@ HttpContext.prototype.buildArgs = function(method) { val = ctx.req.get(name); break; case 'req': - doSloppyCoerce = false; // complex object // Direct access to http req val = ctx.req; break; case 'res': - doSloppyCoerce = false; // complex object // Direct access to http res val = ctx.res; break; case 'context': - doSloppyCoerce = false; // complex object // Direct access to http context val = ctx; break; @@ -166,36 +167,54 @@ HttpContext.prototype.buildArgs = function(method) { } } else { val = ctx.getArgByName(name, o); + // Safe to coerce the contents of this + if (typeof val === 'object' && (!isArrayType || isAny)) { + val = coerceAll(val); + } } - // If this is from the body and we were doing a JSON POST, turn off sloppy coercion. - // This is because JSON, unlike other methods, properly retains types like Numbers, - // Booleans, and null/undefined. - if (ctx.req.body && ctx.req.get('content-type') === 'application/json' && - (ctx.req.body === val || ctx.req.body[name] === val)) { - doSloppyCoerce = false; + // If we expect an array type and we received a string, parse it with JSON. + // If that fails, parse it with the arrayItemDelimiters option. + if (val && typeof val === 'string' && isArrayType) { + var parsed = false; + if (val[0] === '[') { + try { + val = JSON.parse(val); + parsed = true; + } catch (e) {} + } + if (!parsed && ctx.options.arrayItemDelimiters) { + // Construct delimiter regex if input was an array. Overwrite option + // so this only needs to happen once. + var delims = this.options.arrayItemDelimiters; + if (Array.isArray(delims)) { + delims = new RegExp(delims.map(escapeRegex).join('|'), 'g'); + this.options.arrayItemDelimiters = delims; + } + + val = val.split(delims); + } + } + + // Coerce dynamic args when input is a string. + if (isAny && typeof val === 'string') { + val = coerceAll(val); } - // Most of the time, the data comes through 'sloppy' methods like HTTP headers or a qs - // which don't preserve types. If so, if we are: - // - // * Expecting an array - // * Expecting an object - // * Using the type 'any' - // - // Use some sloppy typing semantics to try to guess what the user meant to send. - if (doSloppyCoerce && - (o.type === 'any' || Array.isArray(o.type) || o.type === 'object')) { - val = httpCoerce(val, o.type, ctx); + // If the input is not an array, but we were expecting one, create + // an array. Create an empty array if input is empty. + if (!Array.isArray(val) && isArrayType) { + if (val !== undefined && val !== '') val = [val]; + else val = []; } - // Convert input so it always matches the method definition. + // For boolean and number types, convert certain strings to that type. // The user can also define new dynamic types. - if (Dynamic.canConvert(o.type)) { - val = new Dynamic(val, ctx).to(o.type); + if (Dynamic.canConvert(otype)) { + val = dynamic(val, otype, ctx); } - // Set the argument value. + // set the argument value args[o.arg] = val; } @@ -211,16 +230,98 @@ HttpContext.prototype.buildArgs = function(method) { HttpContext.prototype.getArgByName = function(name, options) { var req = this.req; + var args = req.params && req.params.args !== undefined ? req.params.args : + req.body && req.body.args !== undefined ? req.body.args : + req.query && req.query.args !== undefined ? req.query.args : + undefined; + + if (args) { + args = JSON.parse(args); + } + + if (typeof args !== 'object' || !args) { + args = {}; + } + var arg = (args && args[name] !== undefined) ? args[name] : + this.req.params[name] !== undefined ? this.req.params[name] : + (this.req.body && this.req.body[name]) !== undefined ? this.req.body[name] : + this.req.query[name] !== undefined ? this.req.query[name] : + this.req.get(name); // search these in order by name - var arg = req.params[name] !== undefined ? req.params[name] : // params - (req.body && req.body[name]) !== undefined ? req.body[name] : // body - req.query[name] !== undefined ? req.query[name] : // query - req.get(name); // header + // req.params + // req.body + // req.query + // req.header return arg; }; +/*! + * Integer test regexp. + */ + +var isint = /^[0-9]+$/; + +/*! + * Float test regexp. + */ + +var isfloat = /^([0-9]+)?\.[0-9]+$/; + +// see http://stackoverflow.com/a/6969486/69868 +function escapeRegex(d) { + return d.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); +} + +// Use dynamic to coerce a value or array of values. +function dynamic(val, toType, ctx) { + if (Array.isArray(val)) { + return val.map(function(v) { + return dynamic(v, toType, ctx); + }); + } + return (new Dynamic(val, ctx)).to(toType); +} + +function coerce(str) { + if (typeof str !== 'string') return str; + if ('null' === str) return null; + if ('true' === str) return true; + if ('false' === str) return false; + if (isfloat.test(str)) return parseFloat(str, 10); + if (isint.test(str) && str.charAt(0) !== '0') return parseInt(str, 10); + return str; +} + +// coerce every string in the given object / array +function coerceAll(obj) { + var type = Array.isArray(obj) ? 'array' : typeof obj; + var i; + var n; + + switch (type) { + case 'string': + return coerce(obj); + case 'object': + if (obj) { + var props = Object.keys(obj); + for (i = 0, n = props.length; i < n; i++) { + var key = props[i]; + obj[key] = coerceAll(obj[key]); + } + } + break; + case 'array': + for (i = 0, n = obj.length; i < n; i++) { + coerceAll(obj[i]); + } + break; + } + + return obj; +} + function buildArgs(ctx, method, fn) { try { return ctx.buildArgs(method); diff --git a/lib/http-invocation.js b/lib/http-invocation.js index 378a34ab..d4333994 100644 --- a/lib/http-invocation.js +++ b/lib/http-invocation.js @@ -50,6 +50,7 @@ function HttpInvocation(method, ctorArgs, args, base, auth) { (method.hasOwnProperty('sharedMethod') && method.sharedMethod.isStatic); var namedArgs = this.namedArgs = {}; var val; + var type; if (!this.isStatic) { method.restClass.ctor.accepts.forEach(function(accept) { @@ -309,6 +310,7 @@ HttpInvocation.prototype.transformResponse = function(res, body, callback) { var ret = returns[i]; var name = ret.name || ret.arg; var val; + var dynamic; var type = ret.type; if (ret.root) { @@ -317,8 +319,16 @@ HttpInvocation.prototype.transformResponse = function(res, body, callback) { val = res.body[name]; } - if (Dynamic.canConvert(type)) { - val = new Dynamic(val, ctx).to(type); + if (typeof type === 'string' && Dynamic.canConvert(type)) { + dynamic = new Dynamic(val, ctx); + val = dynamic.to(type); + } else if (Array.isArray(type) && Dynamic.canConvert(type[0])) { + type = type[0]; + for (var j = 0, k = val.length; j < k; j++) { + var _val = val[j]; + dynamic = new Dynamic(_val, ctx); + val[j] = dynamic.to(type); + } } callbackArgs.push(val); diff --git a/test/http-context.test.js b/test/http-context.test.js index 8ec13103..303b7a7b 100644 --- a/test/http-context.test.js +++ b/test/http-context.test.js @@ -26,11 +26,6 @@ describe('HttpContext', function() { input: '0.123456', expectedValue: 0.123456 })); - it('should coerce numbers into strings', givenMethodExpectArg({ - type: 'string', - input: 123456, - expectedValue: '123456' - })); it('should coerce number strings preceded by 0 into numbers', givenMethodExpectArg({ type: 'number', input: '000123', @@ -41,16 +36,6 @@ describe('HttpContext', function() { input: 'null', expectedValue: 'null' })); - it('should not coerce null into the null string', givenMethodExpectArg({ - type: 'string', - input: null, - expectedValue: null - })); - it('should not coerce undefined into the undefined string', givenMethodExpectArg({ - type: 'string', - input: undefined, - expectedValue: undefined - })); it('should coerce array types properly with non-array input', givenMethodExpectArg({ type: ['string'], input: 123, @@ -63,79 +48,28 @@ describe('HttpContext', function() { })); }); - describe('don\'t coerce arguments without a defined type (or any) in JSON', function() { - it('should not coerce boolean strings into actual booleans', givenMethodExpectArg({ - type: 'any', - input: 'true', - expectedValue: 'true' - })); - it('should not coerce integer strings into actual numbers', givenMethodExpectArg({ - type: 'any', - input: '123456', - expectedValue: '123456' - })); - it('should not coerce float strings into actual numbers', givenMethodExpectArg({ - type: 'any', - input: '0.123456', - expectedValue: '0.123456' - })); - it('should not coerce null strings into null', givenMethodExpectArg({ - type: 'any', - input: 'null', - expectedValue: 'null' - })); - }); - - describe('coerce arguments without a defined type (or any) in formdata', function() { - it('should coerce boolean strings into actual booleans', givenFormDataExpectArg({ + describe('arguments without a defined type (or any)', function() { + it('should coerce boolean strings into actual booleans', givenMethodExpectArg({ type: 'any', input: 'true', expectedValue: true })); - it('should coerce integer strings into actual numbers', givenFormDataExpectArg({ - type: 'any', - input: '123456', - expectedValue: 123456 - })); - it('should coerce float strings into actual numbers', givenFormDataExpectArg({ - type: 'any', - input: '0.123456', - expectedValue: 0.123456 - })); - it('should coerce null strings into null', givenFormDataExpectArg({ - type: 'any', - input: 'null', - expectedValue: null - })); - it('should coerce number strings preceded by 0 into strings', givenFormDataExpectArg({ - type: 'any', - input: '000123', - expectedValue: '000123' - })); - }); - - describe('coerce arguments without a defined type (or any) in QS', function() { - it('should coerce boolean strings into actual booleans', givenQSExpectArg({ - type: 'any', - input: 'true', - expectedValue: true - })); - it('should coerce integer strings into actual numbers', givenQSExpectArg({ + it('should coerce integer strings into actual numbers', givenMethodExpectArg({ type: 'any', input: '123456', expectedValue: 123456 })); - it('should coerce float strings into actual numbers', givenQSExpectArg({ + it('should coerce float strings into actual numbers', givenMethodExpectArg({ type: 'any', input: '0.123456', expectedValue: 0.123456 })); - it('should coerce null strings into null', givenQSExpectArg({ + it('should coerce null strings into null', givenMethodExpectArg({ type: 'any', input: 'null', expectedValue: null })); - it('should coerce number strings preceded by 0 into strings', givenQSExpectArg({ + it('should coerce number strings preceded by 0 into strings', givenMethodExpectArg({ type: 'any', input: '000123', expectedValue: '000123' @@ -162,62 +96,7 @@ describe('HttpContext', function() { }); }); -// Tests sending JSON - should be strict conversions function givenMethodExpectArg(options) { - return function(done) { - var method = new SharedMethod(noop, 'testMethod', noop, { - accepts: [{arg: 'testArg', type: options.type}] - }); - - var app = require('express')(); - app.use(require('body-parser').json()); - - app.post('/', function(req, res) { - var ctx = new HttpContext(req, res, method); - try { - expect(ctx.args.testArg).to.eql(options.expectedValue); - } catch (e) { - return done(e); - } - done(); - }); - - request(app).post('/') - .type('json') - .send({testArg: options.input}) - .end(); - }; -} - -// Tests sending via formdata - should be sloppy conversions -function givenFormDataExpectArg(options) { - return function(done) { - var method = new SharedMethod(noop, 'testMethod', noop, { - accepts: [{arg: 'testArg', type: options.type}] - }); - - var app = require('express')(); - app.use(require('body-parser').urlencoded({extended: false})); - - app.post('/', function(req, res) { - var ctx = new HttpContext(req, res, method); - try { - expect(ctx.args.testArg).to.eql(options.expectedValue); - } catch (e) { - return done(e); - } - done(); - }); - - request(app).post('/') - .type('form') - .send({testArg: options.input}) - .end(); - }; -} - -// Tests sending via querystring - should be sloppy conversions -function givenQSExpectArg(options) { return function(done) { var method = new SharedMethod(noop, 'testMethod', noop, { accepts: [{arg: 'testArg', type: options.type}] diff --git a/test/type.test.js b/test/type.test.js index 3072a366..e9486260 100644 --- a/test/type.test.js +++ b/test/type.test.js @@ -1,6 +1,5 @@ var assert = require('assert'); var Dynamic = require('../lib/dynamic'); -var httpCoerce = require('../lib/http-coerce'); var RemoteObjects = require('../'); describe('types', function() { @@ -25,7 +24,7 @@ describe('types', function() { return 'boop'; }); var dyn = new Dynamic('beep'); - assert.strictEqual(dyn.to('beep'), 'boop'); + assert.equal(dyn.to('beep'), 'boop'); }); }); describe('Dynamic.canConvert(typeName)', function() { @@ -37,30 +36,28 @@ describe('types', function() { }); describe('Built in converters', function() { it('should convert Boolean values', function() { - var shouldConvert = _shouldConvert('boolean'); - shouldConvert(true, true); shouldConvert(false, false); shouldConvert(256, true); shouldConvert(-1, true); shouldConvert(1, true); shouldConvert(0, false); - shouldConvert('', false); - // Expect all these string arguments to return true as we are - // no longer doing sloppy conversion in Dynamic shouldConvert('true', true); - shouldConvert('false', true); - shouldConvert('0', true); + shouldConvert('false', false); + shouldConvert('0', false); shouldConvert('1', true); shouldConvert('-1', true); shouldConvert('256', true); - shouldConvert('null', true); - shouldConvert('undefined', true); + shouldConvert('null', false); + shouldConvert('undefined', false); + shouldConvert('', false); + function shouldConvert(val, expected) { + var dyn = new Dynamic(val); + assert.equal(dyn.to('boolean'), expected); + } }); it('should convert Number values', function() { - var shouldConvert = _shouldConvert('number'); - shouldConvert('-1', -1); shouldConvert('0', 0); shouldConvert('1', 1); @@ -70,116 +67,17 @@ describe('types', function() { shouldConvert(false, 0); shouldConvert({}, 'NaN'); shouldConvert([], 0); - }); - - it('should convert Array values', function() { - var shouldConvert = _shouldConvert('array'); - - shouldConvert(0, [0]); - shouldConvert('a', ['a']); - shouldConvert(false, [false]); - shouldConvert(null, []); - shouldConvert(undefined, []); - shouldConvert('', []); - shouldConvert(['a'], ['a']); - }); - it('should convert Arrays of types', function() { - var shouldConvert = _shouldConvert(['string']); + function shouldConvert(val, expected) { + var dyn = new Dynamic(val); - shouldConvert(['a', 0, 1, true], ['a', '0', '1', 'true'], ['string']); - }); - - it('should only convert Arrays one layer deep', function() { - var shouldConvert = _shouldConvert(['string']); - - shouldConvert(['0', 1, [2, '3']], ['0', '1', '2,3']); - }); - }); - - // Assert builder for Dynamic - function _shouldConvert(type) { - return function(val, expected) { - val = new Dynamic(val).to(type); - - if (expected === 'NaN') return assert(Number.isNaN(val)); - - if (Array.isArray(type) || type === 'array') { - assert.deepEqual(val, expected); - for (var i = 0; i < val.length; i++) { - assert.strictEqual(val[i], expected[i]); + if (expected === 'NaN') { + return assert(Number.isNaN(dyn.to('number'))); } - } else { - assert.strictEqual(val, expected); - } - }; - } - }); - - describe('Sloppy HTTP converter', function() { - it('should convert strings to primitives', function() { - shouldConvert('true', true); - shouldConvert('false', false); - shouldConvert('0', 0); - shouldConvert('-0', 0); - shouldConvert('1', 1); - shouldConvert('-1', -1); - shouldConvert('256', 256); - shouldConvert('1.022', 1.022); - shouldConvert('0.49', 0.49); - shouldConvert('null', null); - shouldConvert('undefined', undefined); - }); - - // See https://github.com/strongloop/strong-remoting/issues/223 - it('should not convert numbers larger than Number.MAX_SAFE_INTEGER', function() { - shouldConvert('2343546576878989879789', '2343546576878989879789'); - }); - - it('should not convert ints with leading zeroes', function() { - shouldConvert('0291', '0291'); - }); - - it('should not attempt to convert arrays unless given array target type', function() { - shouldConvert('["a","b","c"]', '["a","b","c"]'); - }); - it('should coerce JSON-parsable strings', function() { - shouldConvertArray('["a","b","c"]', ['a', 'b', 'c']); - shouldConvertArray('[1,2,3]', [1, 2, 3]); - shouldConvertArray('["a","b",3]', ['a', 'b', 3]); - // Note lack of 'undefined' which is not valid JSON - shouldConvertArray('[false,true,3,"c",null,"hello"]', - [false, true, 3, 'c', null, 'hello']); - }); - - it('should coerce strings with arrayItemDelimiters', function() { - shouldConvertArrayWithDelims('1,2,3', [1, 2, 3], [',']); - shouldConvertArrayWithDelims('a,b,3', ['a', 'b', 3], [',']); - shouldConvertArrayWithDelims('false,true,3,c,undefined,null,hello', - [false, true, 3, 'c', undefined, null, 'hello'], [',']); - shouldConvertArrayWithDelims('false,true,3,c,undefined;null-hello', - [false, true, 3, 'c', undefined, null, 'hello'], [',', ';', '-']); - }); - - it('should fail to parse invalid json', function() { - // Should still have array wrappter though - shouldConvertArray('[a,b,3]', ['[a,b,3]']); + assert.equal(dyn.to('number'), expected); + } + }); }); - - function shouldConvert(val, expected, type, ctx) { - val = httpCoerce(val, type || 'any', ctx || {}); - // Believe it or not, deepEqual will actually match '1' and 1, so check types - assert.strictEqual(typeof val, typeof expected); - assert.deepEqual(val, expected); - } - - function shouldConvertArray(val, expected, ctx) { - return shouldConvert(val, expected, ['any'], ctx); - } - - function shouldConvertArrayWithDelims(val, expected, delims) { - return shouldConvertArray(val, expected, {options: {arrayItemDelimiters: delims}}); - } }); });