From db70d332512113b852045701402c829d06c37d84 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Wed, 21 Feb 2018 16:33:44 -0500 Subject: [PATCH 01/12] test(data-point/request): Document and add tests for EntityRequest#value method (#222) --- packages/data-point/README.md | 16 +- packages/data-point/lib/core/transform.js | 37 +- .../__snapshots__/stack.test.js.snap | 468 ++++++++++++++++++ packages/data-point/lib/debug-utils/index.js | 21 + .../data-point/lib/debug-utils/stack.test.js | 441 +++++++++++++++++ .../lib/entity-types/base-entity/resolve.js | 68 ++- .../entity-types/entity-collection/resolve.js | 6 +- .../entity-types/entity-control/resolve.js | 45 +- .../lib/entity-types/entity-entry/resolve.js | 10 +- .../lib/entity-types/entity-hash/resolve.js | 6 +- .../lib/entity-types/entity-model/resolve.js | 3 +- .../__snapshots__/resolve.test.js.snap | 52 -- .../entity-types/entity-request/resolve.js | 62 +-- .../entity-request/resolve.test.js | 63 +-- .../lib/entity-types/entity-schema/resolve.js | 37 +- .../entity-types/entity-transform/resolve.js | 7 +- .../data-point/lib/helpers/helpers.test.js | 2 +- .../data-point/lib/reducer-types/factory.js | 1 - .../reducer-types/reducer-function/factory.js | 10 +- .../reducer-helpers/reducer-filter/resolve.js | 4 +- .../reducer-helpers/reducer-find/resolve.js | 4 +- .../reducer-helpers/reducer-map/resolve.js | 4 +- .../reducer-helpers/reducer-omit/resolve.js | 3 +- .../reducer-parallel/resolve.js | 4 +- .../reducer-helpers/reducer-pick/resolve.js | 3 +- .../lib/reducer-types/reducer-list/resolve.js | 4 +- .../reducer-types/reducer-object/resolve.js | 10 +- .../lib/reducer-types/reducer-path/resolve.js | 2 + .../data-point/lib/reducer-types/resolve.js | 44 +- .../data-point/test/definitions/requests.js | 7 + 30 files changed, 1219 insertions(+), 225 deletions(-) create mode 100644 packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap create mode 100644 packages/data-point/lib/debug-utils/index.js create mode 100644 packages/data-point/lib/debug-utils/stack.test.js delete mode 100644 packages/data-point/lib/entity-types/entity-request/__snapshots__/resolve.test.js.snap diff --git a/packages/data-point/README.md b/packages/data-point/README.md index 66f67c61..6b69e824 100644 --- a/packages/data-point/README.md +++ b/packages/data-point/README.md @@ -1852,6 +1852,7 @@ dataPoint.addEntities({ 'request:': { inputType: String | Reducer, before: Reducer, + value: Reducer, url: StringTemplate, options: Reducer, after: Reducer, @@ -1867,13 +1868,14 @@ dataPoint.addEntities({ | Key | Type | Description | |:---|:---|:---| | *inputType* | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's input value, but does not mutate it | -| *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution | -| *url* | [StringTemplate](#string-template) | String value to resolve the request's url | -| *options* | [Reducer](#reducers) | reducer that returns an object to use as [request.js](https://github.com/request/request) options -| *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution | -| *error* | [Reducer](#reducers) | reducer to be resolved in case of an error | -| *outputType* | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it | -| *params* | `Object` | User defined Hash that will be passed to every reducer within the context of the transform function's execution | +| *before* | [Reducer](#reducers) | reducer to be resolved **before** the entity resolution | +| *value* | [Reducer](#reducers) | the result of this reducer is the input when resolving **url** and **options** +| *url* | [StringTemplate](#string-template) | String value to resolve the request's url | +| *options* | [Reducer](#reducers) | reducer that returns an object to use as [request.js](https://github.com/request/request) options +| *after* | [Reducer](#reducers) | reducer to be resolved **after** the entity resolution | +| *error* | [Reducer](#reducers) | reducer to be resolved in case of an error | +| *outputType* | String, [Reducer](#reducers) | [type checks](#entity-type-check) the entity's output value, but does not mutate it | +| *params* | `Object` | User defined Hash that will be passed to every reducer within the context of the transform function's execution | ##### Request.url diff --git a/packages/data-point/lib/core/transform.js b/packages/data-point/lib/core/transform.js index c10edc20..61eb690f 100644 --- a/packages/data-point/lib/core/transform.js +++ b/packages/data-point/lib/core/transform.js @@ -3,16 +3,28 @@ const Promise = require('bluebird') const Reducer = require('../reducer-types') const AccumulatorFactory = require('../accumulator/factory') +const { stringifyReducerStack } = require('../debug-utils') +/** + * @param {Object} spec + * @return {Object} + */ function getOptions (spec) { return _.defaults({}, spec, { locals: {} }) } +/** + * @param {Object} manager + * @param {*} reducerSource + * @param {*} value + * @param {Object} options + * @return {Promise} + */ function reducerResolve (manager, reducerSource, value, options) { const contextOptions = getOptions(options) - const context = AccumulatorFactory.create({ + const accumulator = AccumulatorFactory.create({ value: value, locals: contextOptions.locals, trace: contextOptions.trace, @@ -20,10 +32,24 @@ function reducerResolve (manager, reducerSource, value, options) { }) const reducer = Reducer.create(reducerSource) + return Reducer.resolve(manager, accumulator, reducer).catch(error => { + if (error._stack) { + error._message = `The following reducer failed to execute:\n${stringifyReducerStack(error._stack)}` + delete error._stack + } - return Reducer.resolve(manager, context, reducer) + throw error + }) } +/** + * @param {Object} manager + * @param {*} reducerSource + * @param {*} value + * @param {Object} options + * @param {Function} done + * @return {Promise} + */ function transform (manager, reducerSource, value, options, done) { return Promise.resolve() .then(() => reducerResolve(manager, reducerSource, value, options)) @@ -32,6 +58,13 @@ function transform (manager, reducerSource, value, options, done) { module.exports.transform = transform +/** + * @param {Object} manager + * @param {*} reducerSource + * @param {*} value + * @param {Object} options + * @return {Promise} + */ function resolve (manager, reducerSource, value, options) { return Promise.resolve() .then(() => reducerResolve(manager, reducerSource, value, options)) diff --git a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap new file mode 100644 index 00000000..2b7fe5c4 --- /dev/null +++ b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap @@ -0,0 +1,468 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ReducerAssign invalid input type 1`] = ` +Object { + "a": 1, + "b": 2, +} +`; + +exports[`ReducerAssign invalid input type 2`] = ` +"The following reducer failed to execute: +ReducerAssign -> ReducerObject[a] -> throwError()" +`; + +exports[`ReducerAssign invalid input type 3`] = ` +Object { + "a": 1, + "b": 2, +} +`; + +exports[`ReducerAssign invalid input type 4`] = ` +"The following reducer failed to execute: +ReducerAssign -> ReducerObject[b] -> throwError()" +`; + +exports[`ReducerFilter invalid input type 1`] = `false`; + +exports[`ReducerFilter invalid input type 2`] = ` +"The following reducer failed to execute: +ReducerFilter" +`; + +exports[`ReducerFilter throw error on 1st item 1`] = `1`; + +exports[`ReducerFilter throw error on 1st item 2`] = ` +"The following reducer failed to execute: +ReducerFilter[0] -> ReducerFunction()" +`; + +exports[`ReducerFilter throw error on 2nd item 1`] = `2`; + +exports[`ReducerFilter throw error on 2nd item 2`] = ` +"The following reducer failed to execute: +ReducerFilter[1] -> ReducerFunction()" +`; + +exports[`ReducerFind invalid input type 1`] = `false`; + +exports[`ReducerFind invalid input type 2`] = ` +"The following reducer failed to execute: +ReducerFind" +`; + +exports[`ReducerFind throw error on 1st item 1`] = `1`; + +exports[`ReducerFind throw error on 1st item 2`] = ` +"The following reducer failed to execute: +ReducerFind[0] -> ReducerFunction()" +`; + +exports[`ReducerFind throw error on 2nd item 1`] = `2`; + +exports[`ReducerFind throw error on 2nd item 2`] = ` +"The following reducer failed to execute: +ReducerFind[1] -> ReducerFunction()" +`; + +exports[`ReducerFunction with async error 1`] = `"input"`; + +exports[`ReducerFunction with async error 2`] = ` +"The following reducer failed to execute: +ReducerFunction()" +`; + +exports[`ReducerFunction with sync error 1`] = `"input"`; + +exports[`ReducerFunction with sync error 2`] = ` +"The following reducer failed to execute: +throwError()" +`; + +exports[`ReducerList with errors 1st item has error 1`] = `"input"`; + +exports[`ReducerList with errors 1st item has error 2`] = ` +"The following reducer failed to execute: +throwError()" +`; + +exports[`ReducerList with errors 2nd item has error 1`] = `"input"`; + +exports[`ReducerList with errors 2nd item has error 2`] = ` +"The following reducer failed to execute: +ReducerList[1] -> throwError()" +`; + +exports[`ReducerList with errors 3rd item has error 1`] = `[Function]`; + +exports[`ReducerList with errors 3rd item has error 2`] = ` +"The following reducer failed to execute: +ReducerList[2] -> ReducerFunction()" +`; + +exports[`ReducerMap invalid input type 1`] = `false`; + +exports[`ReducerMap invalid input type 2`] = ` +"The following reducer failed to execute: +ReducerMap" +`; + +exports[`ReducerMap throw error on 1st item 1`] = `1`; + +exports[`ReducerMap throw error on 1st item 2`] = ` +"The following reducer failed to execute: +ReducerMap[0] -> ReducerFunction()" +`; + +exports[`ReducerMap throw error on 2nd item 1`] = `2`; + +exports[`ReducerMap throw error on 2nd item 2`] = ` +"The following reducer failed to execute: +ReducerMap[1] -> ReducerFunction()" +`; + +exports[`ReducerObject with errors nested property 1`] = ` +Object { + "a": 1, + "b": Object { + "c": Object { + "d": 1, + }, + }, +} +`; + +exports[`ReducerObject with errors nested property 2`] = ` +"The following reducer failed to execute: +ReducerObject[b.c.d] -> throwError()" +`; + +exports[`ReducerObject with errors single property 1`] = ` +Object { + "a": 1, + "b": 2, +} +`; + +exports[`ReducerObject with errors single property 2`] = ` +"The following reducer failed to execute: +ReducerObject[b] -> throwError()" +`; + +exports[`control entity stack traces control:1 1`] = ` +Object { + "x": 1, +} +`; + +exports[`control entity stack traces control:1 2`] = ` +"The following reducer failed to execute: +control:1 -> case[0] -> throwError()" +`; + +exports[`control entity stack traces control:2 1`] = ` +Object { + "x": 1, +} +`; + +exports[`control entity stack traces control:2 2`] = ` +"The following reducer failed to execute: +control:2 -> case[1] -> throwError()" +`; + +exports[`control entity stack traces control:3 1`] = ` +Object { + "x": 1, +} +`; + +exports[`control entity stack traces control:3 2`] = ` +"The following reducer failed to execute: +control:3 -> do[0] -> throwError()" +`; + +exports[`control entity stack traces control:4 1`] = ` +Object { + "x": 1, +} +`; + +exports[`control entity stack traces control:4 2`] = ` +"The following reducer failed to execute: +control:4 -> do[1] -> throwError()" +`; + +exports[`control entity stack traces control:5 1`] = ` +Object { + "x": 1, +} +`; + +exports[`control entity stack traces control:5 2`] = ` +"The following reducer failed to execute: +control:5 -> do[default] -> throwError()" +`; + +exports[`do not log names for anonymous functions function in ReducerList 1`] = ` +Object { + "x": 1, +} +`; + +exports[`do not log names for anonymous functions function in ReducerList 2`] = ` +"The following reducer failed to execute: +ReducerFunction()" +`; + +exports[`do not log names for anonymous functions function with inferred name from object property 1`] = ` +Object { + "a": 1, +} +`; + +exports[`do not log names for anonymous functions function with inferred name from object property 2`] = ` +"The following reducer failed to execute: +ReducerObject[a] -> ReducerFunction()" +`; + +exports[`do not log names for anonymous functions function with inferred name from variable 1`] = ` +Object { + "x": 1, +} +`; + +exports[`do not log names for anonymous functions function with inferred name from variable 2`] = ` +"The following reducer failed to execute: +ReducerFunction()" +`; + +exports[`entry entity stack traces entry:1 1`] = ` +Object { + "x": 1, +} +`; + +exports[`entry entity stack traces entry:1 2`] = ` +"The following reducer failed to execute: +entry:1[before] -> throwError()" +`; + +exports[`entry entity stack traces entry:2 1`] = ` +Object { + "x": 1, +} +`; + +exports[`entry entity stack traces entry:2 2`] = ` +"The following reducer failed to execute: +entry:2[value] -> throwError()" +`; + +exports[`entry entity stack traces entry:3 1`] = ` +Object { + "x": 1, +} +`; + +exports[`entry entity stack traces entry:3 2`] = ` +"The following reducer failed to execute: +entry:3[after] -> throwError()" +`; + +exports[`entry entity stack traces entry:4 1`] = `[Error: test error message]`; + +exports[`entry entity stack traces entry:4 2`] = ` +"The following reducer failed to execute: +entry:4[error] -> throwError()" +`; + +exports[`entry entity stack traces entry:type-check-1 1`] = ` +Object { + "x": 1, +} +`; + +exports[`entry entity stack traces entry:type-check-1 2`] = ` +"The following reducer failed to execute: +entry:type-check-1[inputType] -> isString()" +`; + +exports[`entry entity stack traces entry:type-check-2 1`] = `500`; + +exports[`entry entity stack traces entry:type-check-2 2`] = ` +"The following reducer failed to execute: +entry:type-check-2[outputType] -> isString()" +`; + +exports[`entry entity stack traces entry:type-check-3 1`] = ` +Object { + "x": 1, +} +`; + +exports[`entry entity stack traces entry:type-check-3 2`] = ` +"The following reducer failed to execute: +entry:type-check-3[inputType] -> schema:a.1.0 -> ajv#validate" +`; + +exports[`entry entity stack traces entry:type-check-4 1`] = `500`; + +exports[`entry entity stack traces entry:type-check-4 2`] = ` +"The following reducer failed to execute: +entry:type-check-4[outputType] -> schema:a.1.0 -> ajv#validate" +`; + +exports[`model entity stack traces model:1 1`] = ` +Object { + "x": 1, +} +`; + +exports[`model entity stack traces model:1 2`] = ` +"The following reducer failed to execute: +model:1[before] -> throwError()" +`; + +exports[`model entity stack traces model:2 1`] = ` +Object { + "x": 1, +} +`; + +exports[`model entity stack traces model:2 2`] = ` +"The following reducer failed to execute: +model:2[value] -> throwError()" +`; + +exports[`model entity stack traces model:3 1`] = ` +Object { + "x": 1, +} +`; + +exports[`model entity stack traces model:3 2`] = ` +"The following reducer failed to execute: +model:3[after] -> throwError()" +`; + +exports[`model entity stack traces model:4 1`] = `[Error: test error message]`; + +exports[`model entity stack traces model:4 2`] = ` +"The following reducer failed to execute: +model:4[error] -> throwError()" +`; + +exports[`request entity stack traces request:1 1`] = ` +Object { + "x": 1, +} +`; + +exports[`request entity stack traces request:1 2`] = ` +"The following reducer failed to execute: +request:1[value] -> ReducerList[1] -> throwError()" +`; + +exports[`request entity stack traces request:2 1`] = ` +Object { + "x": 1, +} +`; + +exports[`request entity stack traces request:2 2`] = ` +"The following reducer failed to execute: +request:2 -> options -> ReducerObject[y] -> ReducerList[1] -> throwError()" +`; + +exports[`request entity stack traces request:3 1`] = ` +Object { + "x": 1, +} +`; + +exports[`request entity stack traces request:3 2`] = ` +"The following reducer failed to execute: +request:3[before] -> throwError()" +`; + +exports[`request entity stack traces request:4 1`] = ` +Object { + "ok": true, +} +`; + +exports[`request entity stack traces request:4 2`] = ` +"The following reducer failed to execute: +request:4[after] -> throwError()" +`; + +exports[`request entity stack traces request:5 1`] = ` +Object { + "json": true, + "method": "GET", + "url": "http://remote.test/source1", +} +`; + +exports[`request entity stack traces request:5 2`] = ` +"The following reducer failed to execute: +request:5 -> request-promise#request()" +`; + +exports[`schema entity stack traces schema:a.1.0 1`] = ` +Object { + "baaaaaar": "1", + "foo": 1, +} +`; + +exports[`schema entity stack traces schema:a.1.0 2`] = ` +"The following reducer failed to execute: +schema:a.1.0 -> ajv#validate" +`; + +exports[`schema entity stack traces schema:with-value-prop 1`] = ` +Object { + "x": 1, +} +`; + +exports[`schema entity stack traces schema:with-value-prop 2`] = ` +"The following reducer failed to execute: +schema:with-value-prop[value] -> throwError()" +`; + +exports[`transform entity stack traces transform:1 1`] = ` +Object { + "x": 1, +} +`; + +exports[`transform entity stack traces transform:1 2`] = ` +"The following reducer failed to execute: +transform:1[value] -> ReducerFunction()" +`; + +exports[`transform entity stack traces transform:2 1`] = ` +Object { + "x": 1, +} +`; + +exports[`transform entity stack traces transform:2 2`] = ` +"The following reducer failed to execute: +transform:2[value] -> transform:1[value] -> ReducerFunction()" +`; + +exports[`transform entity stack traces transform:3 1`] = ` +Object { + "x": 1, +} +`; + +exports[`transform entity stack traces transform:3 2`] = ` +"The following reducer failed to execute: +transform:3[value] -> transform:2[value] -> transform:1[value] -> ReducerFunction()" +`; diff --git a/packages/data-point/lib/debug-utils/index.js b/packages/data-point/lib/debug-utils/index.js new file mode 100644 index 00000000..85733575 --- /dev/null +++ b/packages/data-point/lib/debug-utils/index.js @@ -0,0 +1,21 @@ +/** + * @param {Array} stack + * @return {String} + */ +function stringifyReducerStack (stack) { + const message = stack.reduce((acc, id) => { + if (typeof id === 'number') { + return `${acc}[${id}]` + } + + if (Array.isArray(id)) { + return `${acc}[${id.join('.')}]` + } + + return acc ? `${acc} -> ${id}` : id + }, '') + + return message +} + +module.exports.stringifyReducerStack = stringifyReducerStack diff --git a/packages/data-point/lib/debug-utils/stack.test.js b/packages/data-point/lib/debug-utils/stack.test.js new file mode 100644 index 00000000..f0a8a47a --- /dev/null +++ b/packages/data-point/lib/debug-utils/stack.test.js @@ -0,0 +1,441 @@ +/* eslint-env jest */ + +const nock = require('nock') +const Promise = require('bluebird') +const DataPoint = require('data-point') +const { assign, filter, map, find } = DataPoint.helpers + +const debugUtils = require('./index') +const schemaA10 = require('../../test/definitions/schema') + +// TODO have a test for every reducer helper type + +const _true = () => true + +const _false = () => false + +const identity = input => input + +function throwError (acc) { + throw new Error('test error message') +} + +function testError (reducer, input) { + return dataPoint + .resolve(reducer, input) + .catch(error => error) + .then(result => { + expect(result).toBeInstanceOf(Error) + expect(result).toHaveProperty('_value') + expect(result).toHaveProperty('_message') + expect(result._value).toMatchSnapshot() + expect(result._message).toMatchSnapshot() + }) +} + +const throwIfEquals = v1 => v2 => { + return v1 === v2 ? throwError() : false +} + +nock('http://remote.test') + .get('/') + .reply(200, { + ok: true + }) + +nock('http://remote.test') + .get('/source1') + .reply(404, 'not found') + +const dataPoint = DataPoint.create({ + entities: { + 'transform:1': () => { + throw new Error('test error') + }, + 'transform:2': 'transform:1', + 'transform:3': 'transform:2', + + 'request:1': { + value: [identity, throwError], + url: 'http://remote.test' + }, + 'request:2': { + options: { + x: () => 'apples', + y: [identity, throwError] + }, + url: 'http://remote.test' + }, + 'request:3': { + before: throwError, + url: 'http://remote.test' + }, + 'request:4': { + url: 'http://remote.test', + after: throwError + }, + 'request:5': { + url: 'http://remote.test/source1' + }, + + // fail first case + 'control:1': { + select: [ + { case: throwError, do: throwError }, + { case: throwError, do: throwError }, + { default: throwError } + ] + }, + // fail second case + 'control:2': { + select: [ + { case: _false, do: throwError }, + { case: throwError, do: throwError }, + { default: throwError } + ] + }, + // fail first do + 'control:3': { + select: [ + { case: _true, do: throwError }, + { case: throwError, do: throwError }, + { default: throwError } + ] + }, + // fail second do + 'control:4': { + select: [ + { case: _false, do: throwError }, + { case: _true, do: throwError }, + { default: _true } + ] + }, + // fail default + 'control:5': { + select: [ + { case: _false, do: throwError }, + { case: _false, do: throwError }, + { default: throwError } + ] + }, + + 'model:1': { + before: throwError + }, + 'model:2': { + value: throwError + }, + 'model:3': { + after: throwError + }, + 'model:4': { + value: throwError, + error: throwError + }, + + 'entry:1': { + before: throwError + }, + 'entry:2': { + value: throwError + }, + 'entry:3': { + after: throwError + }, + 'entry:4': { + value: throwError, + error: throwError + }, + + 'entry:type-check-1': { + inputType: 'string' + }, + 'entry:type-check-2': { + value: () => 500, + outputType: 'string' + }, + 'entry:type-check-3': { + inputType: 'schema:a.1.0' + }, + 'entry:type-check-4': { + value: () => 500, + outputType: 'schema:a.1.0' + }, + + 'schema:with-value-prop': { + value: throwError, + schema: { + type: 'object' + } + } + } +}) + +dataPoint.addEntities(schemaA10) + +describe('transform entity stack traces', () => { + test('transform:1', () => { + return testError('transform:1', { x: 1 }) + }) + test('transform:2', () => { + return testError('transform:2', { x: 1 }) + }) + test('transform:3', () => { + return testError('transform:3', { x: 1 }) + }) +}) + +describe('request entity stack traces', () => { + test('request:1', () => { + return testError('request:1', { x: 1 }) + }) + test('request:2', () => { + return testError('request:2', { x: 1 }) + }) + test('request:3', () => { + return testError('request:3', { x: 1 }) + }) + test('request:4', () => { + return testError('request:4', { x: 1 }) + }) + test('request:5', () => { + return testError('request:5', { x: 1 }) + }) +}) + +describe('control entity stack traces', () => { + test('control:1', () => { + return testError('control:1', { x: 1 }) + }) + test('control:2', () => { + return testError('control:2', { x: 1 }) + }) + test('control:3', () => { + return testError('control:3', { x: 1 }) + }) + test('control:4', () => { + return testError('control:4', { x: 1 }) + }) + test('control:5', () => { + return testError('control:5', { x: 1 }) + }) +}) + +describe('model entity stack traces', () => { + test('model:1', () => { + return testError('model:1', { x: 1 }) + }) + test('model:2', () => { + return testError('model:2', { x: 1 }) + }) + test('model:3', () => { + return testError('model:3', { x: 1 }) + }) + test('model:4', () => { + return testError('model:4', { x: 1 }) + }) +}) + +describe('entry entity stack traces', () => { + test('entry:1', () => { + return testError('entry:1', { x: 1 }) + }) + test('entry:2', () => { + return testError('entry:2', { x: 1 }) + }) + test('entry:3', () => { + return testError('entry:3', { x: 1 }) + }) + test('entry:4', () => { + return testError('entry:4', { x: 1 }) + }) + + test('entry:type-check-1', () => { + return testError('entry:type-check-1', { x: 1 }) + }) + test('entry:type-check-2', () => { + return testError('entry:type-check-2', { x: 1 }) + }) + test('entry:type-check-3', () => { + return testError('entry:type-check-3', { x: 1 }) + }) + test('entry:type-check-4', () => { + return testError('entry:type-check-4', { x: 1 }) + }) +}) + +describe('schema entity stack traces', () => { + test('schema:with-value-prop', () => { + return testError('schema:with-value-prop', { x: 1 }) + }) + + test('schema:a.1.0', () => { + return testError('schema:a.1.0', { + foo: 1, + baaaaaar: '1' + }) + }) +}) + +describe('ReducerFunction', () => { + test('with sync error', () => { + return testError(throwError, 'input') + }) + test('with async error', () => { + return testError(acc => { + return Promise.try(throwError).delay(10) + }, 'input') + }) +}) + +describe('ReducerList with errors', () => { + test('1st item has error', () => { + const reducer = [throwError] + return testError(reducer, 'input') + }) + test('2nd item has error', () => { + const reducer = [identity, throwError] + return testError(reducer, 'input') + }) + test('3rd item has error', () => { + const reducer = ['$a', '$b', input => input()] + const input = { + a: { + b: throwError + } + } + return testError(reducer, input) + }) +}) + +describe('ReducerObject with errors', () => { + test('single property', () => { + const reducer = { + a: '$a', + b: throwError + } + return testError(reducer, { a: 1, b: 2 }) + }) + test('nested property', () => { + const reducer = { + a: '$a', + b: { + c: { + d: throwError + } + } + } + return testError(reducer, { a: 1, b: { c: { d: 1 } } }) + }) +}) + +describe('do not log names for anonymous functions', () => { + test('function in ReducerList', () => { + const reducer = [ + () => { + throw new Error('test error') + } + ] + return testError(reducer, { x: 1 }) + }) + test('function with inferred name from variable', () => { + const anonFunction = () => { + throw new Error('test error') + } + return testError(anonFunction, { x: 1 }) + }) + test('function with inferred name from object property', () => { + const reducer = { + a: () => { + throw new Error('test error') + } + } + return testError(reducer, { a: 1 }) + }) +}) + +describe('ReducerAssign', () => { + test('invalid input type', () => { + const reducer = assign({ a: throwError, b: '$b' }) + return testError(reducer, { a: 1, b: 2 }) + }) + test('invalid input type', () => { + const reducer = assign({ a: '$a', b: throwError }) + return testError(reducer, { a: 1, b: 2 }) + }) +}) + +describe('ReducerMap', () => { + test('invalid input type', () => { + const reducer = map('$a') + return testError(reducer, false) + }) + test('throw error on 1st item', () => { + const reducer = map(throwIfEquals(1)) + return testError(reducer, [1, 2]) + }) + test('throw error on 2nd item', () => { + const reducer = map(throwIfEquals(2)) + return testError(reducer, [1, 2]) + }) +}) + +describe('ReducerFilter', () => { + test('invalid input type', () => { + const reducer = filter('$a') + return testError(reducer, false) + }) + test('throw error on 1st item', () => { + const reducer = filter(throwIfEquals(1)) + return testError(reducer, [1, 2]) + }) + test('throw error on 2nd item', () => { + const reducer = filter(throwIfEquals(2)) + return testError(reducer, [1, 2]) + }) +}) + +describe('ReducerFind', () => { + test('invalid input type', () => { + const reducer = find('$a') + return testError(reducer, false) + }) + test('throw error on 1st item', () => { + const reducer = find(throwIfEquals(1)) + return testError(reducer, [1, 2]) + }) + test('throw error on 2nd item', () => { + const reducer = find(throwIfEquals(2)) + return testError(reducer, [1, 2]) + }) +}) + +describe('stringifyReducerStack', () => { + const toString = debugUtils.stringifyReducerStack + test("should stringify: [] => ''", () => { + expect(toString([])).toBe('') + }) + test("should stringify: [0] => '[0]'", () => { + expect(toString([0])).toBe('[0]') + }) + test("should stringify: ['abc'] => 'abc'", () => { + expect(toString(['abc'])).toBe('abc') + }) + test("should stringify: [['abc']] => '[abc]'", () => { + expect(toString([['abc']])).toBe('[abc]') + }) + test("should stringify: [['a', 'b']] => '[a.b]'", () => { + expect(toString([['a', 'b']])).toBe('[a.b]') + }) + test("should stringify: ['abc', 'def'] => 'abc -> def'", () => { + expect(toString(['abc', 'def'])).toBe('abc -> def') + }) + test("should stringify: ['abc', ['a'], 'def', ['b', 'c']] => 'abc[a] -> def[b.c]'", () => { + expect(toString(['abc', ['a'], 'def', ['b', 'c']])).toBe( + 'abc[a] -> def[b.c]' + ) + }) + test("should stringify: ['abc', 'def', ['a'], 0] => 'abc -> def[a][0]'", () => { + expect(toString(['abc', 'def', ['a'], 0])).toBe('abc -> def[a][0]') + }) +}) diff --git a/packages/data-point/lib/entity-types/base-entity/resolve.js b/packages/data-point/lib/entity-types/base-entity/resolve.js index 77bfd965..4baf279f 100644 --- a/packages/data-point/lib/entity-types/base-entity/resolve.js +++ b/packages/data-point/lib/entity-types/base-entity/resolve.js @@ -23,7 +23,8 @@ function resolveErrorReducers (manager, error, accumulator, resolveReducer) { const reducerResolved = resolveReducer( manager, errorAccumulator, - errorReducer + errorReducer, + [['error']] ) return reducerResolved.then(result => @@ -97,10 +98,18 @@ function resolveMiddleware (manager, name, acc) { module.exports.resolveMiddleware = resolveMiddleware -function typeCheck (manager, acc, reducer, resolveReducer) { +/** + * @param {Object} manager + * @param {Accumulator} accumulator + * @param {Reducer} reducer + * @param {Function} resolveReducer + * @param {String} key + * @return {Promise} + */ +function typeCheck (manager, accumulator, reducer, resolveReducer, key) { // if no error returns original accumulator // this prevents typeCheckTransform from mutating the value - return resolveReducer(manager, acc, reducer).return(acc) + return resolveReducer(manager, accumulator, reducer, key).return(accumulator) } /** @@ -139,18 +148,33 @@ function resolveEntity ( const resolveReducerBound = _.partial(resolveReducer, manager) return Promise.resolve(accUid) - .then(acc => - typeCheck(manager, acc, acc.reducer.spec.inputType, resolveReducer) - ) - .then(acc => resolveMiddleware(manager, `before`, acc)) - .then(acc => - resolveMiddleware(manager, `${reducer.entityType}:before`, acc) - ) - .then(acc => resolveReducer(manager, acc, acc.reducer.spec.before)) - .then(acc => mainResolver(acc, resolveReducerBound)) - .then(acc => resolveReducer(manager, acc, acc.reducer.spec.after)) - .then(acc => resolveMiddleware(manager, `${reducer.entityType}:after`, acc)) - .then(acc => resolveMiddleware(manager, `after`, acc)) + .then(acc => { + const reducer = acc.reducer.spec.inputType + return typeCheck(manager, acc, reducer, resolveReducer, [['inputType']]) + }) + .then(acc => { + return resolveMiddleware(manager, 'before', acc) + }) + .then(acc => { + return resolveMiddleware(manager, `${reducer.entityType}:before`, acc) + }) + .then(acc => { + const reducer = acc.reducer.spec.before + return resolveReducer(manager, acc, reducer, [['before']]) + }) + .then(acc => { + return mainResolver(acc, resolveReducerBound) + }) + .then(acc => { + const reducer = acc.reducer.spec.after + return resolveReducer(manager, acc, reducer, [['after']]) + }) + .then(acc => { + return resolveMiddleware(manager, `${reducer.entityType}:after`, acc) + }) + .then(acc => { + return resolveMiddleware(manager, 'after', acc) + }) .catch(error => { // checking if this is an error to bypass the `then` chain if (error.bypass === true) { @@ -159,9 +183,10 @@ function resolveEntity ( throw error }) - .then(acc => - typeCheck(manager, acc, acc.reducer.spec.outputType, resolveReducer) - ) + .then(acc => { + const reducer = acc.reducer.spec.outputType + return typeCheck(manager, acc, reducer, resolveReducer, [['outputType']]) + }) .catch(error => { // attach entity information to help debug error.entityId = currentAccumulator.reducer.spec.id @@ -171,9 +196,10 @@ function resolveEntity ( error, currentAccumulator, resolveReducer - ).then(acc => - typeCheck(manager, acc, acc.reducer.spec.outputType, resolveReducer) - ) + ).then(acc => { + const reducer = acc.reducer.spec.outputType + return typeCheck(manager, acc, reducer, resolveReducer, [['outputType']]) + }) }) .then(resultContext => { if (trace === true) { diff --git a/packages/data-point/lib/entity-types/entity-collection/resolve.js b/packages/data-point/lib/entity-types/entity-collection/resolve.js index a8d50c3b..ca881c95 100644 --- a/packages/data-point/lib/entity-types/entity-collection/resolve.js +++ b/packages/data-point/lib/entity-types/entity-collection/resolve.js @@ -5,9 +5,9 @@ */ function resolve (accumulator, resolveReducer) { const entity = accumulator.reducer.spec - return resolveReducer(accumulator, entity.value).then(acc => - resolveReducer(acc, entity.compose) - ) + return resolveReducer(accumulator, entity.value, [['value']]).then(acc => { + return resolveReducer(acc, entity.compose, [['compose']]) + }) } module.exports.resolve = resolve diff --git a/packages/data-point/lib/entity-types/entity-control/resolve.js b/packages/data-point/lib/entity-types/entity-control/resolve.js index 5b47e369..27002a27 100644 --- a/packages/data-point/lib/entity-types/entity-control/resolve.js +++ b/packages/data-point/lib/entity-types/entity-control/resolve.js @@ -2,16 +2,16 @@ const Promise = require('bluebird') /** * - * @param {any} caseStatements - * @param {any} acc - * @param {any} resolveReducer - * @returns + * @param {Array} caseStatements + * @param {Accumulator} accumulator + * @param {Function} resolveReducer + * @return {Number} */ -function getMatchingCaseStatement (caseStatements, acc, resolveReducer) { +function getMatchingCaseIndex (caseStatements, accumulator, resolveReducer) { return Promise.reduce( caseStatements, - (result, statement) => { - if (result) { + (result, statement, index) => { + if (result !== null) { // doing this until proven wrong :) const err = new Error('bypassing') err.name = 'bypass' @@ -20,9 +20,11 @@ function getMatchingCaseStatement (caseStatements, acc, resolveReducer) { return Promise.reject(err) } - return resolveReducer(acc, statement.case).then(res => { - return res.value ? statement : false - }) + return resolveReducer(accumulator, statement.case, ['case', index]).then( + res => { + return res.value ? index : null + } + ) }, null ).catch(error => { @@ -34,20 +36,27 @@ function getMatchingCaseStatement (caseStatements, acc, resolveReducer) { throw error }) } -module.exports.getMatchingCaseStatement = getMatchingCaseStatement +module.exports.getMatchingCaseIndex = getMatchingCaseIndex -function resolve (acc, resolveReducer) { - const selectControl = acc.reducer.spec.select +/** + * @param {Accumulator} accumulator + * @param {Function} resolveReducer + * @returns {Promise} + */ +function resolve (accumulator, resolveReducer) { + const selectControl = accumulator.reducer.spec.select const caseStatements = selectControl.cases const defaultTransform = selectControl.default - return getMatchingCaseStatement(caseStatements, acc, resolveReducer).then( - caseStatement => { - if (caseStatement) { - return resolveReducer(acc, caseStatement.do) + return getMatchingCaseIndex(caseStatements, accumulator, resolveReducer).then( + index => { + if (index === null) { + // const _index = caseStatements.length + return resolveReducer(accumulator, defaultTransform, ['do', ['default']]) } - return resolveReducer(acc, defaultTransform) + const caseStatement = caseStatements[index] + return resolveReducer(accumulator, caseStatement.do, ['do', index]) } ) } diff --git a/packages/data-point/lib/entity-types/entity-entry/resolve.js b/packages/data-point/lib/entity-types/entity-entry/resolve.js index d8c60e23..66723087 100644 --- a/packages/data-point/lib/entity-types/entity-entry/resolve.js +++ b/packages/data-point/lib/entity-types/entity-entry/resolve.js @@ -1,11 +1,15 @@ const _ = require('lodash') const utils = require('../../utils') +/** + * @param {Accumulator} acc + * @param {Function} resolveReducer + * @return {Promise} + */ function resolve (acc, resolveReducer) { const contextTransform = acc.reducer.spec.value - let racc = acc - racc = utils.set(acc, 'value', _.defaultTo(acc.value, {})) - return resolveReducer(racc, contextTransform) + const racc = utils.set(acc, 'value', _.defaultTo(acc.value, {})) + return resolveReducer(racc, contextTransform, [['value']]) } module.exports.resolve = resolve diff --git a/packages/data-point/lib/entity-types/entity-hash/resolve.js b/packages/data-point/lib/entity-types/entity-hash/resolve.js index a8d50c3b..ca881c95 100644 --- a/packages/data-point/lib/entity-types/entity-hash/resolve.js +++ b/packages/data-point/lib/entity-types/entity-hash/resolve.js @@ -5,9 +5,9 @@ */ function resolve (accumulator, resolveReducer) { const entity = accumulator.reducer.spec - return resolveReducer(accumulator, entity.value).then(acc => - resolveReducer(acc, entity.compose) - ) + return resolveReducer(accumulator, entity.value, [['value']]).then(acc => { + return resolveReducer(acc, entity.compose, [['compose']]) + }) } module.exports.resolve = resolve diff --git a/packages/data-point/lib/entity-types/entity-model/resolve.js b/packages/data-point/lib/entity-types/entity-model/resolve.js index 2ea02065..cab966f6 100644 --- a/packages/data-point/lib/entity-types/entity-model/resolve.js +++ b/packages/data-point/lib/entity-types/entity-model/resolve.js @@ -3,7 +3,8 @@ * @param {Function} resolveReducer */ function resolve (accumulator, resolveReducer) { - return resolveReducer(accumulator, accumulator.reducer.spec.value) + const entity = accumulator.reducer.spec + return resolveReducer(accumulator, entity.value, [['value']]) } module.exports.resolve = resolve diff --git a/packages/data-point/lib/entity-types/entity-request/__snapshots__/resolve.test.js.snap b/packages/data-point/lib/entity-types/entity-request/__snapshots__/resolve.test.js.snap deleted file mode 100644 index 6c5dd339..00000000 --- a/packages/data-point/lib/entity-types/entity-request/__snapshots__/resolve.test.js.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`resolve it should omit options.auth when encountering an error 1`] = ` -"404 - undefined - -Entity info: - - Id: request:a9 - - options: { auth: '[omitted]', - method: 'GET', - json: true, - url: 'http://remote.test/source1' } - - value: {} - - Request: - - message: '404 - undefined' - - statusCode: 404 - - options: { auth: '[omitted]', - method: 'GET', - json: true, - url: 'http://remote.test/source1', - callback: [Function: RP$callback], - transform: undefined, - simple: true, - resolveWithFullResponse: false, - transform2xxOnly: false } -" -`; - -exports[`resolveRequest log errors when request fails 1`] = ` -"404 - \\"not found\\" - -Entity info: - - Id: test:test - - options: { json: true, - url: 'http://remote.test/source1', - auth: '[omitted]' } - - value: 'foo' - - Request: - - error: 'not found' - - message: '404 - \\"not found\\"' - - statusCode: 404 - - options: { json: true, - url: 'http://remote.test/source1', - callback: [Function: RP$callback], - transform: undefined, - simple: true, - resolveWithFullResponse: false, - transform2xxOnly: false, - auth: '[omitted]' } -" -`; diff --git a/packages/data-point/lib/entity-types/entity-request/resolve.js b/packages/data-point/lib/entity-types/entity-request/resolve.js index 99391599..5333ac92 100644 --- a/packages/data-point/lib/entity-types/entity-request/resolve.js +++ b/packages/data-point/lib/entity-types/entity-request/resolve.js @@ -1,8 +1,8 @@ const _ = require('lodash') -const fp = require('lodash/fp') const Promise = require('bluebird') const rp = require('request-promise') +const createReducer = require('../../reducer-types').create const utils = require('../../utils') /** @@ -43,7 +43,7 @@ module.exports.getRequestOptions = getRequestOptions function resolveOptions (accumulator, resolveReducer) { accumulator = resolveUrl(accumulator) const specOptions = accumulator.reducer.spec.options - return resolveReducer(accumulator, specOptions).then(acc => { + return resolveReducer(accumulator, specOptions, 'options').then(acc => { const options = getRequestOptions(acc.url, acc.value) return utils.assign(accumulator, { options }) }) @@ -96,41 +96,33 @@ function inspect (acc) { module.exports.inspect = inspect /** - * @param {Accumulator} acc + * @param {Object} options + * @return {Promise} + */ +function _requestReducer (options) { + return rp(options) +} + +// this name will appear in the stack trace when a request fails +Object.defineProperty(_requestReducer, 'name', { + value: 'request-promise#request' +}) + +// this function is a reducer so that we can log +// reducer stack traces if the request has an error +const requestReducer = createReducer(_requestReducer) + +module.exports.requestReducer = requestReducer + +/** + * @param {Accumulator} accumulator * @param {Function} resolveReducer * @return {Promise} */ -function resolveRequest (acc, resolveReducer) { - inspect(acc) - return rp(acc.options) - .then(result => utils.set(acc, 'value', result)) - .catch(error => { - // remove auth objects from acc and error for printing to console - const redactedAcc = fp.set('options.auth', '[omitted]', acc) - const redactedError = fp.set('options.auth', '[omitted]', error) - - const message = [ - 'Entity info:', - '\n - Id: ', - _.get(redactedAcc, 'reducer.spec.id'), - '\n', - utils.inspectProperties( - redactedAcc, - ['options', 'params', 'value'], - ' ' - ), - '\n Request:\n', - utils.inspectProperties( - redactedError, - ['error', 'message', 'statusCode', 'options', 'body'], - ' ' - ) - ].join('') - - // attaching to error so it can be exposed by a handler outside datapoint - error.message = `${error.message}\n\n${message}` - throw error - }) +function resolveRequest (accumulator, resolveReducer) { + inspect(accumulator) + const acc = utils.set(accumulator, 'value', accumulator.options) + return resolveReducer(acc, requestReducer) } module.exports.resolveRequest = resolveRequest @@ -143,7 +135,7 @@ module.exports.resolveRequest = resolveRequest function resolve (acc, resolveReducer) { const entity = acc.reducer.spec return Promise.resolve(acc) - .then(itemContext => resolveReducer(itemContext, entity.value)) + .then(itemContext => resolveReducer(itemContext, entity.value, [['value']])) .then(itemContext => resolveOptions(itemContext, resolveReducer)) .then(itemContext => resolveRequest(itemContext, resolveReducer)) } diff --git a/packages/data-point/lib/entity-types/entity-request/resolve.test.js b/packages/data-point/lib/entity-types/entity-request/resolve.test.js index bc35ec0c..a062cafd 100644 --- a/packages/data-point/lib/entity-types/entity-request/resolve.test.js +++ b/packages/data-point/lib/entity-types/entity-request/resolve.test.js @@ -54,6 +54,16 @@ beforeEach(() => { dataPoint.middleware.clear() }) +describe('requestReducer', () => { + test('it is a reducer', () => { + expect(ReducerFactory.isReducer(Resolve.requestReducer)).toBe(true) + }) + + test('it has a custom function name', () => { + expect(Resolve.requestReducer.body.name).toBe('request-promise#request') + }) +}) + describe('resolveUrlInjections', () => { test('url with no reducer', () => { const value = { @@ -233,32 +243,12 @@ describe('resolveRequest', () => { } } - return Resolve.resolveRequest(acc).then(result => { + return Resolve.resolveRequest(acc, resolveReducerBound).then(result => { expect(result.value).toEqual({ ok: true }) }) }) - - test('log errors when request fails', () => { - nock('http://remote.test') - .get('/source1') - .reply(404, 'not found') - - const acc = { - options: { - json: true, - url: 'http://remote.test/source1' - }, - value: 'foo' - } - _.set(acc, 'reducer.spec.id', 'test:test') - return Resolve.resolveRequest(acc) - .catch(e => e) - .then(result => { - expect(result.message).toMatchSnapshot() - }) - }) }) describe('inspect', () => { @@ -311,6 +301,20 @@ describe('resolve', () => { }) }) + test("interpolate data that's returned from the value lifecycle method", () => { + nock('http://remote.test') + .get('/source5') + .reply(200, { + ok: true + }) + + return transform('request:a1.4', null).then(result => { + expect(result.value).toEqual({ + ok: true + }) + }) + }) + test('url injections', () => { nock('http://remote.test') .get('/source1') @@ -382,21 +386,4 @@ describe('resolve', () => { }) }) }) - - test('it should omit options.auth when encountering an error', () => { - nock('http://remote.test') - .get('/source1') - .reply(404) - - return transform('request:a9', {}).catch(err => { - expect(err.statusCode).toEqual(404) - expect(err.message).toMatchSnapshot() - - // credentials are still available in the raw error.options - expect(err.options.auth).toEqual({ - user: 'cool_user', - pass: 'super_secret!' - }) - }) - }) }) diff --git a/packages/data-point/lib/entity-types/entity-schema/resolve.js b/packages/data-point/lib/entity-types/entity-schema/resolve.js index 9c962b36..a2146ac2 100644 --- a/packages/data-point/lib/entity-types/entity-schema/resolve.js +++ b/packages/data-point/lib/entity-types/entity-schema/resolve.js @@ -2,31 +2,38 @@ const _ = require('lodash') const Promise = require('bluebird') const Ajv = require('ajv') +/** + * @param {Accumulator} acc + * @return {Promise} + */ function validateContext (acc) { const ajv = new Ajv(acc.reducer.spec.options) const validate = ajv.compile(acc.reducer.spec.schema) + if (validate(acc.value)) { + return Promise.resolve(acc) + } - return Promise.resolve(validate(acc.value)).then(valid => { - if (!valid) { - const messages = _.map(validate.errors, 'message') - const messageListStr = messages.join('\n -') - const error = new Error(`Errors Found:\n - ${messageListStr}`) - error.name = 'InvalidSchema' - error.errors = validate.errors - return Promise.reject(error) - } - - return acc - }) + const message = _.map(validate.errors, 'message').join('\n -') + const error = new Error(`Errors Found:\n - ${message}`) + error.name = 'InvalidSchema' + error.errors = validate.errors + return Promise.reject(error) } module.exports.validateContext = validateContext +/** + * @param {Accumulator} acc + * @param {Function} resolveReducer + * @param {Promise} + */ function resolve (acc, resolveReducer) { const value = acc.reducer.spec.value - - return resolveReducer(acc, value).then(racc => { - return validateContext(racc) + return resolveReducer(acc, value, [['value']]).then(racc => { + return validateContext(racc).catch(error => { + error._stack = ['ajv#validate'] + throw error + }) }) } diff --git a/packages/data-point/lib/entity-types/entity-transform/resolve.js b/packages/data-point/lib/entity-types/entity-transform/resolve.js index deb27d85..b3f6ca44 100644 --- a/packages/data-point/lib/entity-types/entity-transform/resolve.js +++ b/packages/data-point/lib/entity-types/entity-transform/resolve.js @@ -1,6 +1,11 @@ +/** + * @param {Accumulator} accumulator + * @param {Function} resolveReducer + * @return {Promise} + */ function resolve (accumulator, resolveReducer) { const entity = accumulator.reducer.spec - return resolveReducer(accumulator, entity.value) + return resolveReducer(accumulator, entity.value, [['value']]) } module.exports.resolve = resolve diff --git a/packages/data-point/lib/helpers/helpers.test.js b/packages/data-point/lib/helpers/helpers.test.js index 38e37238..eecf6fc7 100644 --- a/packages/data-point/lib/helpers/helpers.test.js +++ b/packages/data-point/lib/helpers/helpers.test.js @@ -76,6 +76,6 @@ describe('helpers.createAccumulator', () => { describe('helpers.createReducerResolver', () => { test('It should partially apply dataPoint to method', () => { const resolveReducerBound = helpers.createReducerResolver({}) - expect(resolveReducerBound.length).toEqual(2) + expect(resolveReducerBound.length).toEqual(3) }) }) diff --git a/packages/data-point/lib/reducer-types/factory.js b/packages/data-point/lib/reducer-types/factory.js index 097c6734..985fcece 100644 --- a/packages/data-point/lib/reducer-types/factory.js +++ b/packages/data-point/lib/reducer-types/factory.js @@ -54,7 +54,6 @@ function normalizeInput (source) { function createReducer (source, options = {}) { source = normalizeInput(source) const reducerType = reducerTypes.find(r => r.isType(source)) - if (_.isUndefined(reducerType)) { const message = [ 'Invalid reducer type.', diff --git a/packages/data-point/lib/reducer-types/reducer-function/factory.js b/packages/data-point/lib/reducer-types/reducer-function/factory.js index 1e076e39..826a3e21 100644 --- a/packages/data-point/lib/reducer-types/reducer-function/factory.js +++ b/packages/data-point/lib/reducer-types/reducer-function/factory.js @@ -8,10 +8,7 @@ module.exports.type = REDUCER_FUNCTION /** * @class * @property {string} type - @see reducerType - * @property {string} name - name of the reducer - * @property {Array} parameters - collection of @see {@link Parameter} items - * @property {boolean} isFunction - true if reducer is already function - * @property {Function} body - actual function body + * @property {Function} body - actual function */ function ReducerFunction () { this.type = REDUCER_FUNCTION @@ -63,6 +60,11 @@ function create (createReducer, source) { reducer.body = source } + // do not include the name for arrow functions (which do not have a prototype), + // because some arrow functions have inferred names, which might be confusing + // if they show up in the reducer stack traces for error messages + const name = (reducer.body.prototype && reducer.body.name) || '' + name && (reducer.name = name) return reducer } diff --git a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-filter/resolve.js b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-filter/resolve.js index 6ca455fb..9d4e87ff 100644 --- a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-filter/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-filter/resolve.js @@ -12,9 +12,9 @@ const { reducerPredicateIsTruthy } = require('../utils') */ function resolve (manager, resolveReducer, accumulator, reducerFilter) { const reducer = reducerFilter.reducer - return Promise.filter(accumulator.value, itemValue => { + return Promise.filter(accumulator.value, (itemValue, index) => { const itemContext = utils.set(accumulator, 'value', itemValue) - return resolveReducer(manager, itemContext, reducer).then(res => { + return resolveReducer(manager, itemContext, reducer, index).then(res => { return reducerPredicateIsTruthy(reducer, res.value) }) }).then(result => utils.set(accumulator, 'value', result)) diff --git a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-find/resolve.js b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-find/resolve.js index 3898485c..ded31d27 100644 --- a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-find/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-find/resolve.js @@ -18,11 +18,11 @@ function resolve (manager, resolveReducer, accumulator, reducerFind) { const reducer = reducerFind.reducer return Promise.reduce( accumulator.value, - (result, itemValue) => { + (result, itemValue, index) => { const itemContext = utils.set(accumulator, 'value', itemValue) return ( result || - resolveReducer(manager, itemContext, reducer).then(res => { + resolveReducer(manager, itemContext, reducer, index).then(res => { return reducerPredicateIsTruthy(reducer, res.value) ? itemValue : undefined diff --git a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-map/resolve.js b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-map/resolve.js index 81753676..047bf37a 100644 --- a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-map/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-map/resolve.js @@ -10,9 +10,9 @@ const utils = require('../../../utils') */ function resolve (manager, resolveReducer, accumulator, reducerMap) { const reducer = reducerMap.reducer - return Promise.map(accumulator.value, itemValue => { + return Promise.map(accumulator.value, (itemValue, index) => { const itemContext = utils.set(accumulator, 'value', itemValue) - return resolveReducer(manager, itemContext, reducer).then(res => { + return resolveReducer(manager, itemContext, reducer, index).then(res => { return res.value }) }).then(result => utils.set(accumulator, 'value', result)) diff --git a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-omit/resolve.js b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-omit/resolve.js index f88a3e77..19bee61d 100644 --- a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-omit/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-omit/resolve.js @@ -11,8 +11,7 @@ const utils = require('../../../utils') * @returns {Promise} */ function resolve (manager, resolveReducer, accumulator, reducerOmit) { - const keys = reducerOmit.keys - const value = omit(accumulator.value, keys) + const value = omit(accumulator.value, reducerOmit.keys) return Promise.resolve(utils.set(accumulator, 'value', value)) } diff --git a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-parallel/resolve.js b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-parallel/resolve.js index 589b9043..f8fdc379 100644 --- a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-parallel/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-parallel/resolve.js @@ -10,8 +10,8 @@ const utils = require('../../../utils') * @returns {Promise} */ function resolve (manager, resolveReducer, accumulator, reducerParallel) { - return Promise.map(reducerParallel.reducers, reducer => { - return resolveReducer(manager, accumulator, reducer) + return Promise.map(reducerParallel.reducers, (reducer, index) => { + return resolveReducer(manager, accumulator, reducer, index) }).then(result => { const value = result.map(acc => acc.value) return utils.assign(accumulator, { value }) diff --git a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-pick/resolve.js b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-pick/resolve.js index 9f72fe6e..f7195705 100644 --- a/packages/data-point/lib/reducer-types/reducer-helpers/reducer-pick/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-helpers/reducer-pick/resolve.js @@ -10,8 +10,7 @@ const utils = require('../../../utils') * @returns {Promise} */ function resolve (manager, resolveReducer, accumulator, reducerPick) { - const keys = reducerPick.keys - const value = pick(accumulator.value, keys) + const value = pick(accumulator.value, reducerPick.keys) return Promise.resolve(utils.set(accumulator, 'value', value)) } diff --git a/packages/data-point/lib/reducer-types/reducer-list/resolve.js b/packages/data-point/lib/reducer-types/reducer-list/resolve.js index bc047bc5..2aeeca3d 100644 --- a/packages/data-point/lib/reducer-types/reducer-list/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-list/resolve.js @@ -17,8 +17,8 @@ function resolve (manager, resolveReducer, accumulator, reducerList) { const result = Promise.reduce( reducers, - (accumulator, reducer) => { - return resolveReducer(manager, accumulator, reducer) + (accumulator, reducer, index) => { + return resolveReducer(manager, accumulator, reducer, index) }, accumulator ) diff --git a/packages/data-point/lib/reducer-types/reducer-object/resolve.js b/packages/data-point/lib/reducer-types/reducer-object/resolve.js index ec0b1bb3..5cf2017a 100644 --- a/packages/data-point/lib/reducer-types/reducer-object/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-object/resolve.js @@ -12,10 +12,12 @@ const utils = require('../../utils') */ function resolve (manager, resolveReducer, accumulator, reducer) { return Promise.map(reducer.reducers, ({ reducer, path }) => { - return resolveReducer(manager, accumulator, reducer).then(({ value }) => ({ - path, - value - })) + return resolveReducer(manager, accumulator, reducer, [path]).then( + ({ value }) => ({ + path, + value + }) + ) }).then(result => { const value = result.reduce( (acc, { path, value }) => set(acc, path, value), diff --git a/packages/data-point/lib/reducer-types/reducer-path/resolve.js b/packages/data-point/lib/reducer-types/reducer-path/resolve.js index e3d112c8..6b6bc1d6 100644 --- a/packages/data-point/lib/reducer-types/reducer-path/resolve.js +++ b/packages/data-point/lib/reducer-types/reducer-path/resolve.js @@ -1,3 +1,5 @@ +const Promise = require('bluebird') + const utils = require('../../utils') /** diff --git a/packages/data-point/lib/reducer-types/resolve.js b/packages/data-point/lib/reducer-types/resolve.js index 777b0a92..d07d7c46 100644 --- a/packages/data-point/lib/reducer-types/resolve.js +++ b/packages/data-point/lib/reducer-types/resolve.js @@ -1,4 +1,5 @@ const Promise = require('bluebird') +const castArray = require('lodash/castArray') const ReducerEntity = require('./reducer-entity') const ReducerFunction = require('./reducer-function') @@ -45,9 +46,10 @@ function getResolveFunction (reducer) { * @param {Object} manager * @param {Accumulator} accumulator * @param {Reducer} reducer + * @param {Array|String|Number} key - id for reducer stack traces if an error is thrown * @returns {Promise} */ -function resolveReducer (manager, accumulator, reducer) { +function resolveReducer (manager, accumulator, reducer, key) { // this conditional is here because BaseEntity#resolve // does not check that lifecycle methods are defined // before trying to resolve them @@ -55,6 +57,9 @@ function resolveReducer (manager, accumulator, reducer) { return Promise.resolve(accumulator) } + // storing this in case we need it for the catch block, since we + // can't trust it won't be overwritten in the accumulator object + const value = accumulator.value const resolve = getResolveFunction(reducer) // NOTE: recursive call const result = resolve(manager, resolveReducer, accumulator, reducer) @@ -64,7 +69,42 @@ function resolveReducer (manager, accumulator, reducer) { return result.then(acc => resolveDefault(acc, _default)) } - return result + return result.catch(error => onResolveMalfunction(reducer, key, value, error)) } module.exports.resolve = resolveReducer + +/** + * @param {Reducer} reducer + * @param {Array|String|Number} key + * @param {*} value + * @param {Error} error + * @throws the given error with more data attached + */ +function onResolveMalfunction (reducer, key, value, error) { + if (!error.hasOwnProperty('_value')) { + error._value = value + } + + let stack + if (typeof key === 'undefined') { + stack = [] + } else { + stack = castArray(key) + } + + if (reducer.type === 'ReducerFunction') { + stack.push(`${reducer.name || reducer.type}()`) + } else if (reducer.type === 'ReducerEntity') { + stack.push(reducer.id) + } else { + stack.push(reducer.type) + } + + if (error._stack) { + stack = stack.concat(error._stack) + } + + error._stack = stack + throw error +} diff --git a/packages/data-point/test/definitions/requests.js b/packages/data-point/test/definitions/requests.js index f48b219e..d47c9fa7 100644 --- a/packages/data-point/test/definitions/requests.js +++ b/packages/data-point/test/definitions/requests.js @@ -36,6 +36,10 @@ module.exports = { 'request:a1.3': { url: 'http://remote.test/source4' }, + 'request:a1.4': { + value: () => ({ source: 'source5' }), + url: 'http://remote.test/{value.source}' + }, 'request:a2': { url: 'http://remote.test', options: { @@ -57,6 +61,9 @@ module.exports = { 'request:a4': { url: 'source1', options: { + json: () => true, + method: () => 'GET', + timeout: () => 5000, baseUrl: () => 'http://remote.test' } }, From 6b064630d1b7f86dc4fa11e33f315f4a5c0694f8 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Fri, 23 Feb 2018 16:39:25 -0500 Subject: [PATCH 02/12] test: Fix tests for error._stack properties --- .../__snapshots__/stack.test.js.snap | 240 ++++-------------- .../data-point/lib/debug-utils/stack.test.js | 4 +- 2 files changed, 50 insertions(+), 194 deletions(-) diff --git a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap index 2b7fe5c4..5c86782d 100644 --- a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap +++ b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap @@ -7,10 +7,7 @@ Object { } `; -exports[`ReducerAssign invalid input type 2`] = ` -"The following reducer failed to execute: -ReducerAssign -> ReducerObject[a] -> throwError()" -`; +exports[`ReducerAssign invalid input type 2`] = `"ReducerAssign -> ReducerObject[a] -> throwError()"`; exports[`ReducerAssign invalid input type 3`] = ` Object { @@ -19,108 +16,63 @@ Object { } `; -exports[`ReducerAssign invalid input type 4`] = ` -"The following reducer failed to execute: -ReducerAssign -> ReducerObject[b] -> throwError()" -`; +exports[`ReducerAssign invalid input type 4`] = `"ReducerAssign -> ReducerObject[b] -> throwError()"`; exports[`ReducerFilter invalid input type 1`] = `false`; -exports[`ReducerFilter invalid input type 2`] = ` -"The following reducer failed to execute: -ReducerFilter" -`; +exports[`ReducerFilter invalid input type 2`] = `"ReducerFilter"`; exports[`ReducerFilter throw error on 1st item 1`] = `1`; -exports[`ReducerFilter throw error on 1st item 2`] = ` -"The following reducer failed to execute: -ReducerFilter[0] -> ReducerFunction()" -`; +exports[`ReducerFilter throw error on 1st item 2`] = `"ReducerFilter[0] -> ReducerFunction()"`; exports[`ReducerFilter throw error on 2nd item 1`] = `2`; -exports[`ReducerFilter throw error on 2nd item 2`] = ` -"The following reducer failed to execute: -ReducerFilter[1] -> ReducerFunction()" -`; +exports[`ReducerFilter throw error on 2nd item 2`] = `"ReducerFilter[1] -> ReducerFunction()"`; exports[`ReducerFind invalid input type 1`] = `false`; -exports[`ReducerFind invalid input type 2`] = ` -"The following reducer failed to execute: -ReducerFind" -`; +exports[`ReducerFind invalid input type 2`] = `"ReducerFind"`; exports[`ReducerFind throw error on 1st item 1`] = `1`; -exports[`ReducerFind throw error on 1st item 2`] = ` -"The following reducer failed to execute: -ReducerFind[0] -> ReducerFunction()" -`; +exports[`ReducerFind throw error on 1st item 2`] = `"ReducerFind[0] -> ReducerFunction()"`; exports[`ReducerFind throw error on 2nd item 1`] = `2`; -exports[`ReducerFind throw error on 2nd item 2`] = ` -"The following reducer failed to execute: -ReducerFind[1] -> ReducerFunction()" -`; +exports[`ReducerFind throw error on 2nd item 2`] = `"ReducerFind[1] -> ReducerFunction()"`; exports[`ReducerFunction with async error 1`] = `"input"`; -exports[`ReducerFunction with async error 2`] = ` -"The following reducer failed to execute: -ReducerFunction()" -`; +exports[`ReducerFunction with async error 2`] = `"ReducerFunction()"`; exports[`ReducerFunction with sync error 1`] = `"input"`; -exports[`ReducerFunction with sync error 2`] = ` -"The following reducer failed to execute: -throwError()" -`; +exports[`ReducerFunction with sync error 2`] = `"throwError()"`; exports[`ReducerList with errors 1st item has error 1`] = `"input"`; -exports[`ReducerList with errors 1st item has error 2`] = ` -"The following reducer failed to execute: -throwError()" -`; +exports[`ReducerList with errors 1st item has error 2`] = `"throwError()"`; exports[`ReducerList with errors 2nd item has error 1`] = `"input"`; -exports[`ReducerList with errors 2nd item has error 2`] = ` -"The following reducer failed to execute: -ReducerList[1] -> throwError()" -`; +exports[`ReducerList with errors 2nd item has error 2`] = `"ReducerList[1] -> throwError()"`; exports[`ReducerList with errors 3rd item has error 1`] = `[Function]`; -exports[`ReducerList with errors 3rd item has error 2`] = ` -"The following reducer failed to execute: -ReducerList[2] -> ReducerFunction()" -`; +exports[`ReducerList with errors 3rd item has error 2`] = `"ReducerList[2] -> ReducerFunction()"`; exports[`ReducerMap invalid input type 1`] = `false`; -exports[`ReducerMap invalid input type 2`] = ` -"The following reducer failed to execute: -ReducerMap" -`; +exports[`ReducerMap invalid input type 2`] = `"ReducerMap"`; exports[`ReducerMap throw error on 1st item 1`] = `1`; -exports[`ReducerMap throw error on 1st item 2`] = ` -"The following reducer failed to execute: -ReducerMap[0] -> ReducerFunction()" -`; +exports[`ReducerMap throw error on 1st item 2`] = `"ReducerMap[0] -> ReducerFunction()"`; exports[`ReducerMap throw error on 2nd item 1`] = `2`; -exports[`ReducerMap throw error on 2nd item 2`] = ` -"The following reducer failed to execute: -ReducerMap[1] -> ReducerFunction()" -`; +exports[`ReducerMap throw error on 2nd item 2`] = `"ReducerMap[1] -> ReducerFunction()"`; exports[`ReducerObject with errors nested property 1`] = ` Object { @@ -133,10 +85,7 @@ Object { } `; -exports[`ReducerObject with errors nested property 2`] = ` -"The following reducer failed to execute: -ReducerObject[b.c.d] -> throwError()" -`; +exports[`ReducerObject with errors nested property 2`] = `"ReducerObject[b.c.d] -> throwError()"`; exports[`ReducerObject with errors single property 1`] = ` Object { @@ -145,10 +94,7 @@ Object { } `; -exports[`ReducerObject with errors single property 2`] = ` -"The following reducer failed to execute: -ReducerObject[b] -> throwError()" -`; +exports[`ReducerObject with errors single property 2`] = `"ReducerObject[b] -> throwError()"`; exports[`control entity stack traces control:1 1`] = ` Object { @@ -156,10 +102,7 @@ Object { } `; -exports[`control entity stack traces control:1 2`] = ` -"The following reducer failed to execute: -control:1 -> case[0] -> throwError()" -`; +exports[`control entity stack traces control:1 2`] = `"control:1 -> case[0] -> throwError()"`; exports[`control entity stack traces control:2 1`] = ` Object { @@ -167,10 +110,7 @@ Object { } `; -exports[`control entity stack traces control:2 2`] = ` -"The following reducer failed to execute: -control:2 -> case[1] -> throwError()" -`; +exports[`control entity stack traces control:2 2`] = `"control:2 -> case[1] -> throwError()"`; exports[`control entity stack traces control:3 1`] = ` Object { @@ -178,10 +118,7 @@ Object { } `; -exports[`control entity stack traces control:3 2`] = ` -"The following reducer failed to execute: -control:3 -> do[0] -> throwError()" -`; +exports[`control entity stack traces control:3 2`] = `"control:3 -> do[0] -> throwError()"`; exports[`control entity stack traces control:4 1`] = ` Object { @@ -189,10 +126,7 @@ Object { } `; -exports[`control entity stack traces control:4 2`] = ` -"The following reducer failed to execute: -control:4 -> do[1] -> throwError()" -`; +exports[`control entity stack traces control:4 2`] = `"control:4 -> do[1] -> throwError()"`; exports[`control entity stack traces control:5 1`] = ` Object { @@ -200,10 +134,7 @@ Object { } `; -exports[`control entity stack traces control:5 2`] = ` -"The following reducer failed to execute: -control:5 -> do[default] -> throwError()" -`; +exports[`control entity stack traces control:5 2`] = `"control:5 -> do[default] -> throwError()"`; exports[`do not log names for anonymous functions function in ReducerList 1`] = ` Object { @@ -211,10 +142,7 @@ Object { } `; -exports[`do not log names for anonymous functions function in ReducerList 2`] = ` -"The following reducer failed to execute: -ReducerFunction()" -`; +exports[`do not log names for anonymous functions function in ReducerList 2`] = `"ReducerFunction()"`; exports[`do not log names for anonymous functions function with inferred name from object property 1`] = ` Object { @@ -222,10 +150,7 @@ Object { } `; -exports[`do not log names for anonymous functions function with inferred name from object property 2`] = ` -"The following reducer failed to execute: -ReducerObject[a] -> ReducerFunction()" -`; +exports[`do not log names for anonymous functions function with inferred name from object property 2`] = `"ReducerObject[a] -> ReducerFunction()"`; exports[`do not log names for anonymous functions function with inferred name from variable 1`] = ` Object { @@ -233,10 +158,7 @@ Object { } `; -exports[`do not log names for anonymous functions function with inferred name from variable 2`] = ` -"The following reducer failed to execute: -ReducerFunction()" -`; +exports[`do not log names for anonymous functions function with inferred name from variable 2`] = `"ReducerFunction()"`; exports[`entry entity stack traces entry:1 1`] = ` Object { @@ -244,10 +166,7 @@ Object { } `; -exports[`entry entity stack traces entry:1 2`] = ` -"The following reducer failed to execute: -entry:1[before] -> throwError()" -`; +exports[`entry entity stack traces entry:1 2`] = `"entry:1[before] -> throwError()"`; exports[`entry entity stack traces entry:2 1`] = ` Object { @@ -255,10 +174,7 @@ Object { } `; -exports[`entry entity stack traces entry:2 2`] = ` -"The following reducer failed to execute: -entry:2[value] -> throwError()" -`; +exports[`entry entity stack traces entry:2 2`] = `"entry:2[value] -> throwError()"`; exports[`entry entity stack traces entry:3 1`] = ` Object { @@ -266,17 +182,11 @@ Object { } `; -exports[`entry entity stack traces entry:3 2`] = ` -"The following reducer failed to execute: -entry:3[after] -> throwError()" -`; +exports[`entry entity stack traces entry:3 2`] = `"entry:3[after] -> throwError()"`; exports[`entry entity stack traces entry:4 1`] = `[Error: test error message]`; -exports[`entry entity stack traces entry:4 2`] = ` -"The following reducer failed to execute: -entry:4[error] -> throwError()" -`; +exports[`entry entity stack traces entry:4 2`] = `"entry:4[error] -> throwError()"`; exports[`entry entity stack traces entry:type-check-1 1`] = ` Object { @@ -284,17 +194,11 @@ Object { } `; -exports[`entry entity stack traces entry:type-check-1 2`] = ` -"The following reducer failed to execute: -entry:type-check-1[inputType] -> isString()" -`; +exports[`entry entity stack traces entry:type-check-1 2`] = `"entry:type-check-1[inputType] -> isString()"`; exports[`entry entity stack traces entry:type-check-2 1`] = `500`; -exports[`entry entity stack traces entry:type-check-2 2`] = ` -"The following reducer failed to execute: -entry:type-check-2[outputType] -> isString()" -`; +exports[`entry entity stack traces entry:type-check-2 2`] = `"entry:type-check-2[outputType] -> isString()"`; exports[`entry entity stack traces entry:type-check-3 1`] = ` Object { @@ -302,17 +206,11 @@ Object { } `; -exports[`entry entity stack traces entry:type-check-3 2`] = ` -"The following reducer failed to execute: -entry:type-check-3[inputType] -> schema:a.1.0 -> ajv#validate" -`; +exports[`entry entity stack traces entry:type-check-3 2`] = `"entry:type-check-3[inputType] -> schema:a.1.0 -> ajv#validate"`; exports[`entry entity stack traces entry:type-check-4 1`] = `500`; -exports[`entry entity stack traces entry:type-check-4 2`] = ` -"The following reducer failed to execute: -entry:type-check-4[outputType] -> schema:a.1.0 -> ajv#validate" -`; +exports[`entry entity stack traces entry:type-check-4 2`] = `"entry:type-check-4[outputType] -> schema:a.1.0 -> ajv#validate"`; exports[`model entity stack traces model:1 1`] = ` Object { @@ -320,10 +218,7 @@ Object { } `; -exports[`model entity stack traces model:1 2`] = ` -"The following reducer failed to execute: -model:1[before] -> throwError()" -`; +exports[`model entity stack traces model:1 2`] = `"model:1[before] -> throwError()"`; exports[`model entity stack traces model:2 1`] = ` Object { @@ -331,10 +226,7 @@ Object { } `; -exports[`model entity stack traces model:2 2`] = ` -"The following reducer failed to execute: -model:2[value] -> throwError()" -`; +exports[`model entity stack traces model:2 2`] = `"model:2[value] -> throwError()"`; exports[`model entity stack traces model:3 1`] = ` Object { @@ -342,17 +234,11 @@ Object { } `; -exports[`model entity stack traces model:3 2`] = ` -"The following reducer failed to execute: -model:3[after] -> throwError()" -`; +exports[`model entity stack traces model:3 2`] = `"model:3[after] -> throwError()"`; exports[`model entity stack traces model:4 1`] = `[Error: test error message]`; -exports[`model entity stack traces model:4 2`] = ` -"The following reducer failed to execute: -model:4[error] -> throwError()" -`; +exports[`model entity stack traces model:4 2`] = `"model:4[error] -> throwError()"`; exports[`request entity stack traces request:1 1`] = ` Object { @@ -360,10 +246,7 @@ Object { } `; -exports[`request entity stack traces request:1 2`] = ` -"The following reducer failed to execute: -request:1[value] -> ReducerList[1] -> throwError()" -`; +exports[`request entity stack traces request:1 2`] = `"request:1[value] -> ReducerList[1] -> throwError()"`; exports[`request entity stack traces request:2 1`] = ` Object { @@ -371,10 +254,7 @@ Object { } `; -exports[`request entity stack traces request:2 2`] = ` -"The following reducer failed to execute: -request:2 -> options -> ReducerObject[y] -> ReducerList[1] -> throwError()" -`; +exports[`request entity stack traces request:2 2`] = `"request:2 -> options -> ReducerObject[y] -> ReducerList[1] -> throwError()"`; exports[`request entity stack traces request:3 1`] = ` Object { @@ -382,10 +262,7 @@ Object { } `; -exports[`request entity stack traces request:3 2`] = ` -"The following reducer failed to execute: -request:3[before] -> throwError()" -`; +exports[`request entity stack traces request:3 2`] = `"request:3[before] -> throwError()"`; exports[`request entity stack traces request:4 1`] = ` Object { @@ -393,10 +270,7 @@ Object { } `; -exports[`request entity stack traces request:4 2`] = ` -"The following reducer failed to execute: -request:4[after] -> throwError()" -`; +exports[`request entity stack traces request:4 2`] = `"request:4[after] -> throwError()"`; exports[`request entity stack traces request:5 1`] = ` Object { @@ -406,10 +280,7 @@ Object { } `; -exports[`request entity stack traces request:5 2`] = ` -"The following reducer failed to execute: -request:5 -> request-promise#request()" -`; +exports[`request entity stack traces request:5 2`] = `"request:5 -> request-promise#request()"`; exports[`schema entity stack traces schema:a.1.0 1`] = ` Object { @@ -418,10 +289,7 @@ Object { } `; -exports[`schema entity stack traces schema:a.1.0 2`] = ` -"The following reducer failed to execute: -schema:a.1.0 -> ajv#validate" -`; +exports[`schema entity stack traces schema:a.1.0 2`] = `"schema:a.1.0 -> ajv#validate"`; exports[`schema entity stack traces schema:with-value-prop 1`] = ` Object { @@ -429,10 +297,7 @@ Object { } `; -exports[`schema entity stack traces schema:with-value-prop 2`] = ` -"The following reducer failed to execute: -schema:with-value-prop[value] -> throwError()" -`; +exports[`schema entity stack traces schema:with-value-prop 2`] = `"schema:with-value-prop[value] -> throwError()"`; exports[`transform entity stack traces transform:1 1`] = ` Object { @@ -440,10 +305,7 @@ Object { } `; -exports[`transform entity stack traces transform:1 2`] = ` -"The following reducer failed to execute: -transform:1[value] -> ReducerFunction()" -`; +exports[`transform entity stack traces transform:1 2`] = `"transform:1[value] -> ReducerFunction()"`; exports[`transform entity stack traces transform:2 1`] = ` Object { @@ -451,10 +313,7 @@ Object { } `; -exports[`transform entity stack traces transform:2 2`] = ` -"The following reducer failed to execute: -transform:2[value] -> transform:1[value] -> ReducerFunction()" -`; +exports[`transform entity stack traces transform:2 2`] = `"transform:2[value] -> transform:1[value] -> ReducerFunction()"`; exports[`transform entity stack traces transform:3 1`] = ` Object { @@ -462,7 +321,4 @@ Object { } `; -exports[`transform entity stack traces transform:3 2`] = ` -"The following reducer failed to execute: -transform:3[value] -> transform:2[value] -> transform:1[value] -> ReducerFunction()" -`; +exports[`transform entity stack traces transform:3 2`] = `"transform:3[value] -> transform:2[value] -> transform:1[value] -> ReducerFunction()"`; diff --git a/packages/data-point/lib/debug-utils/stack.test.js b/packages/data-point/lib/debug-utils/stack.test.js index f0a8a47a..88fda362 100644 --- a/packages/data-point/lib/debug-utils/stack.test.js +++ b/packages/data-point/lib/debug-utils/stack.test.js @@ -27,9 +27,9 @@ function testError (reducer, input) { .then(result => { expect(result).toBeInstanceOf(Error) expect(result).toHaveProperty('_value') - expect(result).toHaveProperty('_message') + expect(result).toHaveProperty('_stack') expect(result._value).toMatchSnapshot() - expect(result._message).toMatchSnapshot() + expect(result._stack).toMatchSnapshot() }) } From 20181e92d5164550c5d4252c7d020315cf009de1 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Fri, 23 Feb 2018 16:59:08 -0500 Subject: [PATCH 03/12] refactor(data-point): Improvements to reducer stack PR --- packages/data-point/lib/core/transform.js | 5 +---- .../lib/debug-utils/__snapshots__/stack.test.js.snap | 2 +- packages/data-point/lib/debug-utils/stack.test.js | 2 -- .../lib/entity-types/base-entity/resolve.js | 11 +++++++---- .../lib/entity-types/entity-control/resolve.js | 6 ++++-- .../lib/entity-types/entity-request/resolve.js | 2 +- .../reducer-types/__snapshots__/resolve.test.js.snap | 2 +- packages/data-point/lib/reducer-types/resolve.js | 7 ++++--- packages/data-point/lib/reducer-types/resolve.test.js | 9 ++++++--- packages/data-point/test/definitions/requests.js | 3 --- 10 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/data-point/lib/core/transform.js b/packages/data-point/lib/core/transform.js index 25a037b8..f0e01d74 100644 --- a/packages/data-point/lib/core/transform.js +++ b/packages/data-point/lib/core/transform.js @@ -33,10 +33,7 @@ function reducerResolve (manager, reducerSource, value, options) { const reducer = Reducer.create(reducerSource) return Reducer.resolve(manager, accumulator, reducer).catch(error => { - if (error._stack) { - error._stack = stringifyReducerStack(error._stack) - } - + error._stack = stringifyReducerStack(error._stack) throw error }) } diff --git a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap index 5c86782d..66bbe2a4 100644 --- a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap +++ b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap @@ -254,7 +254,7 @@ Object { } `; -exports[`request entity stack traces request:2 2`] = `"request:2 -> options -> ReducerObject[y] -> ReducerList[1] -> throwError()"`; +exports[`request entity stack traces request:2 2`] = `"request:2[options] -> ReducerObject[y] -> ReducerList[1] -> throwError()"`; exports[`request entity stack traces request:3 1`] = ` Object { diff --git a/packages/data-point/lib/debug-utils/stack.test.js b/packages/data-point/lib/debug-utils/stack.test.js index 88fda362..b54076a8 100644 --- a/packages/data-point/lib/debug-utils/stack.test.js +++ b/packages/data-point/lib/debug-utils/stack.test.js @@ -8,8 +8,6 @@ const { assign, filter, map, find } = DataPoint.helpers const debugUtils = require('./index') const schemaA10 = require('../../test/definitions/schema') -// TODO have a test for every reducer helper type - const _true = () => true const _false = () => false diff --git a/packages/data-point/lib/entity-types/base-entity/resolve.js b/packages/data-point/lib/entity-types/base-entity/resolve.js index 4baf279f..5028b746 100644 --- a/packages/data-point/lib/entity-types/base-entity/resolve.js +++ b/packages/data-point/lib/entity-types/base-entity/resolve.js @@ -103,12 +103,13 @@ module.exports.resolveMiddleware = resolveMiddleware * @param {Accumulator} accumulator * @param {Reducer} reducer * @param {Function} resolveReducer - * @param {String} key + * @param {String} Array * @return {Promise} */ function typeCheck (manager, accumulator, reducer, resolveReducer, key) { - // if no error returns original accumulator - // this prevents typeCheckTransform from mutating the value + // returns original accumulator if there's no error + // this helps prevent type check reducers from mutating the value, but + // it's still possible to modify the value by reference when it's an object return resolveReducer(manager, accumulator, reducer, key).return(accumulator) } @@ -198,7 +199,9 @@ function resolveEntity ( resolveReducer ).then(acc => { const reducer = acc.reducer.spec.outputType - return typeCheck(manager, acc, reducer, resolveReducer, [['outputType']]) + return typeCheck(manager, acc, reducer, resolveReducer, [ + ['outputType'] + ]) }) }) .then(resultContext => { diff --git a/packages/data-point/lib/entity-types/entity-control/resolve.js b/packages/data-point/lib/entity-types/entity-control/resolve.js index 27002a27..d95b5d3d 100644 --- a/packages/data-point/lib/entity-types/entity-control/resolve.js +++ b/packages/data-point/lib/entity-types/entity-control/resolve.js @@ -51,8 +51,10 @@ function resolve (accumulator, resolveReducer) { return getMatchingCaseIndex(caseStatements, accumulator, resolveReducer).then( index => { if (index === null) { - // const _index = caseStatements.length - return resolveReducer(accumulator, defaultTransform, ['do', ['default']]) + return resolveReducer(accumulator, defaultTransform, [ + 'do', + ['default'] + ]) } const caseStatement = caseStatements[index] diff --git a/packages/data-point/lib/entity-types/entity-request/resolve.js b/packages/data-point/lib/entity-types/entity-request/resolve.js index 5333ac92..5485f898 100644 --- a/packages/data-point/lib/entity-types/entity-request/resolve.js +++ b/packages/data-point/lib/entity-types/entity-request/resolve.js @@ -43,7 +43,7 @@ module.exports.getRequestOptions = getRequestOptions function resolveOptions (accumulator, resolveReducer) { accumulator = resolveUrl(accumulator) const specOptions = accumulator.reducer.spec.options - return resolveReducer(accumulator, specOptions, 'options').then(acc => { + return resolveReducer(accumulator, specOptions, [['options']]).then(acc => { const options = getRequestOptions(acc.url, acc.value) return utils.assign(accumulator, { options }) }) diff --git a/packages/data-point/lib/reducer-types/__snapshots__/resolve.test.js.snap b/packages/data-point/lib/reducer-types/__snapshots__/resolve.test.js.snap index c969349e..0f0c7ac9 100644 --- a/packages/data-point/lib/reducer-types/__snapshots__/resolve.test.js.snap +++ b/packages/data-point/lib/reducer-types/__snapshots__/resolve.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`reducer#resolve It should throw error for invalid input 1`] = `"Reducer type 'INVALID TYPE' was not recognized"`; +exports[`reducer#resolve It should throw error for invalid input 1`] = `[Error: Reducer type 'INVALID TYPE' was not recognized]`; diff --git a/packages/data-point/lib/reducer-types/resolve.js b/packages/data-point/lib/reducer-types/resolve.js index d07d7c46..6a9afb42 100644 --- a/packages/data-point/lib/reducer-types/resolve.js +++ b/packages/data-point/lib/reducer-types/resolve.js @@ -60,9 +60,10 @@ function resolveReducer (manager, accumulator, reducer, key) { // storing this in case we need it for the catch block, since we // can't trust it won't be overwritten in the accumulator object const value = accumulator.value - const resolve = getResolveFunction(reducer) - // NOTE: recursive call - const result = resolve(manager, resolveReducer, accumulator, reducer) + const result = Promise.try(() => getResolveFunction(reducer)) + // NOTE: recursive call + .then(resolve => resolve(manager, resolveReducer, accumulator, reducer)) + if (hasDefault(reducer)) { const _default = reducer[DEFAULT_VALUE].value const resolveDefault = reducers.ReducerDefault.resolve diff --git a/packages/data-point/lib/reducer-types/resolve.test.js b/packages/data-point/lib/reducer-types/resolve.test.js index eacff4d9..39c549b3 100644 --- a/packages/data-point/lib/reducer-types/resolve.test.js +++ b/packages/data-point/lib/reducer-types/resolve.test.js @@ -38,9 +38,12 @@ describe('reducer#resolve', () => { }) const reducer = { type: 'INVALID TYPE' } - expect(() => { - Resolve.resolve(manager, accumulator, reducer) - }).toThrowErrorMatchingSnapshot() + return Resolve.resolve(manager, accumulator, reducer) + .catch(e => e) + .then(result => { + expect(result).toBeInstanceOf(Error) + expect(result).toMatchSnapshot() + }) }) test('It should return undefined when no default is provided', () => { diff --git a/packages/data-point/test/definitions/requests.js b/packages/data-point/test/definitions/requests.js index d47c9fa7..61983a7a 100644 --- a/packages/data-point/test/definitions/requests.js +++ b/packages/data-point/test/definitions/requests.js @@ -61,9 +61,6 @@ module.exports = { 'request:a4': { url: 'source1', options: { - json: () => true, - method: () => 'GET', - timeout: () => 5000, baseUrl: () => 'http://remote.test' } }, From fe941614a55c342a922704ea65e4f11ffd2391a0 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Fri, 23 Feb 2018 17:25:50 -0500 Subject: [PATCH 04/12] docs: Reword some comments and tests --- .../lib/entity-types/entity-request/resolve.js | 2 +- .../lib/entity-types/entity-schema/resolve.js | 10 +++++----- packages/data-point/lib/reducer-types/resolve.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/data-point/lib/entity-types/entity-request/resolve.js b/packages/data-point/lib/entity-types/entity-request/resolve.js index 5485f898..9897b049 100644 --- a/packages/data-point/lib/entity-types/entity-request/resolve.js +++ b/packages/data-point/lib/entity-types/entity-request/resolve.js @@ -108,7 +108,7 @@ Object.defineProperty(_requestReducer, 'name', { value: 'request-promise#request' }) -// this function is a reducer so that we can log +// this function is a reducer so that we can generate // reducer stack traces if the request has an error const requestReducer = createReducer(_requestReducer) diff --git a/packages/data-point/lib/entity-types/entity-schema/resolve.js b/packages/data-point/lib/entity-types/entity-schema/resolve.js index a2146ac2..bb3f700f 100644 --- a/packages/data-point/lib/entity-types/entity-schema/resolve.js +++ b/packages/data-point/lib/entity-types/entity-schema/resolve.js @@ -23,14 +23,14 @@ function validateContext (acc) { module.exports.validateContext = validateContext /** - * @param {Accumulator} acc + * @param {Accumulator} accumulator * @param {Function} resolveReducer * @param {Promise} */ -function resolve (acc, resolveReducer) { - const value = acc.reducer.spec.value - return resolveReducer(acc, value, [['value']]).then(racc => { - return validateContext(racc).catch(error => { +function resolve (accumulator, resolveReducer) { + const value = accumulator.reducer.spec.value + return resolveReducer(accumulator, value, [['value']]).then(acc => { + return validateContext(acc).catch(error => { error._stack = ['ajv#validate'] throw error }) diff --git a/packages/data-point/lib/reducer-types/resolve.js b/packages/data-point/lib/reducer-types/resolve.js index 6a9afb42..f9804bb7 100644 --- a/packages/data-point/lib/reducer-types/resolve.js +++ b/packages/data-point/lib/reducer-types/resolve.js @@ -80,7 +80,7 @@ module.exports.resolve = resolveReducer * @param {Array|String|Number} key * @param {*} value * @param {Error} error - * @throws the given error with more data attached + * @throws the given error with _value and _stack properties attached */ function onResolveMalfunction (reducer, key, value, error) { if (!error.hasOwnProperty('_value')) { From aa525b5f38c1ea28c7d90a46b3be4828c8d4dce5 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Fri, 23 Feb 2018 18:12:44 -0500 Subject: [PATCH 05/12] docs(base-entity): Fix jsdoc --- packages/data-point/lib/entity-types/base-entity/resolve.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/data-point/lib/entity-types/base-entity/resolve.js b/packages/data-point/lib/entity-types/base-entity/resolve.js index 5028b746..acd16d08 100644 --- a/packages/data-point/lib/entity-types/base-entity/resolve.js +++ b/packages/data-point/lib/entity-types/base-entity/resolve.js @@ -103,7 +103,7 @@ module.exports.resolveMiddleware = resolveMiddleware * @param {Accumulator} accumulator * @param {Reducer} reducer * @param {Function} resolveReducer - * @param {String} Array + * @param {Array} key * @return {Promise} */ function typeCheck (manager, accumulator, reducer, resolveReducer, key) { From 739935b2741200ecdd89cb61cf9df960a6e52176 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Sat, 24 Feb 2018 09:04:36 -0500 Subject: [PATCH 06/12] refactor(entity-schema): Wrap validateContext in a reducer This allows for easier reducer stack traces --- .../__snapshots__/stack.test.js.snap | 6 ++-- .../lib/entity-types/entity-schema/resolve.js | 35 ++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap index 66bbe2a4..cb5b18e9 100644 --- a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap +++ b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap @@ -206,11 +206,11 @@ Object { } `; -exports[`entry entity stack traces entry:type-check-3 2`] = `"entry:type-check-3[inputType] -> schema:a.1.0 -> ajv#validate"`; +exports[`entry entity stack traces entry:type-check-3 2`] = `"entry:type-check-3[inputType] -> schema:a.1.0 -> ajv#validate()"`; exports[`entry entity stack traces entry:type-check-4 1`] = `500`; -exports[`entry entity stack traces entry:type-check-4 2`] = `"entry:type-check-4[outputType] -> schema:a.1.0 -> ajv#validate"`; +exports[`entry entity stack traces entry:type-check-4 2`] = `"entry:type-check-4[outputType] -> schema:a.1.0 -> ajv#validate()"`; exports[`model entity stack traces model:1 1`] = ` Object { @@ -289,7 +289,7 @@ Object { } `; -exports[`schema entity stack traces schema:a.1.0 2`] = `"schema:a.1.0 -> ajv#validate"`; +exports[`schema entity stack traces schema:a.1.0 2`] = `"schema:a.1.0 -> ajv#validate()"`; exports[`schema entity stack traces schema:with-value-prop 1`] = ` Object { diff --git a/packages/data-point/lib/entity-types/entity-schema/resolve.js b/packages/data-point/lib/entity-types/entity-schema/resolve.js index bb3f700f..6414f990 100644 --- a/packages/data-point/lib/entity-types/entity-schema/resolve.js +++ b/packages/data-point/lib/entity-types/entity-schema/resolve.js @@ -2,15 +2,18 @@ const _ = require('lodash') const Promise = require('bluebird') const Ajv = require('ajv') +const createReducer = require('../../reducer-types').create + /** - * @param {Accumulator} acc + * @param {*} value + * @param {Object} context * @return {Promise} */ -function validateContext (acc) { - const ajv = new Ajv(acc.reducer.spec.options) - const validate = ajv.compile(acc.reducer.spec.schema) - if (validate(acc.value)) { - return Promise.resolve(acc) +function validateContext (value, context) { + const ajv = new Ajv(context.reducer.spec.options) + const validate = ajv.compile(context.reducer.spec.schema) + if (validate(value)) { + return Promise.resolve(value) } const message = _.map(validate.errors, 'message').join('\n -') @@ -22,6 +25,17 @@ function validateContext (acc) { module.exports.validateContext = validateContext +// this name will appear in the stack trace if schema validation fails +Object.defineProperty(validateContext, 'name', { + value: 'ajv#validate' +}) + +// this function is a reducer so that we can generate +// reducer stack traces if the schema validation fails +const _validateContext = createReducer(validateContext) + +module.exports._validateContext = _validateContext + /** * @param {Accumulator} accumulator * @param {Function} resolveReducer @@ -29,12 +43,9 @@ module.exports.validateContext = validateContext */ function resolve (accumulator, resolveReducer) { const value = accumulator.reducer.spec.value - return resolveReducer(accumulator, value, [['value']]).then(acc => { - return validateContext(acc).catch(error => { - error._stack = ['ajv#validate'] - throw error - }) - }) + return resolveReducer(accumulator, value, [['value']]).then(acc => + resolveReducer(acc, _validateContext) + ) } module.exports.resolve = resolve From d7acd7935570927d2a23822ca652f73cc8f5cb2b Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Sat, 24 Feb 2018 09:19:52 -0500 Subject: [PATCH 07/12] docs(reducer-types): Improve a comment --- packages/data-point/lib/reducer-types/resolve.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/data-point/lib/reducer-types/resolve.js b/packages/data-point/lib/reducer-types/resolve.js index f9804bb7..fe7520be 100644 --- a/packages/data-point/lib/reducer-types/resolve.js +++ b/packages/data-point/lib/reducer-types/resolve.js @@ -59,6 +59,7 @@ function resolveReducer (manager, accumulator, reducer, key) { // storing this in case we need it for the catch block, since we // can't trust it won't be overwritten in the accumulator object + // although it could still be modified by reference if it's an object const value = accumulator.value const result = Promise.try(() => getResolveFunction(reducer)) // NOTE: recursive call From e8fce242a07dfdb60b840bc7ff8e5661ae80528c Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Mon, 26 Feb 2018 07:39:36 -0500 Subject: [PATCH 08/12] style: Use explicit return in arrow function --- .../data-point/lib/entity-types/entity-schema/resolve.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/data-point/lib/entity-types/entity-schema/resolve.js b/packages/data-point/lib/entity-types/entity-schema/resolve.js index 6414f990..ca11ea67 100644 --- a/packages/data-point/lib/entity-types/entity-schema/resolve.js +++ b/packages/data-point/lib/entity-types/entity-schema/resolve.js @@ -43,9 +43,9 @@ module.exports._validateContext = _validateContext */ function resolve (accumulator, resolveReducer) { const value = accumulator.reducer.spec.value - return resolveReducer(accumulator, value, [['value']]).then(acc => - resolveReducer(acc, _validateContext) - ) + return resolveReducer(accumulator, value, [['value']]).then(acc => { + return resolveReducer(acc, _validateContext) + }) } module.exports.resolve = resolve From d614af49923b18e80c820d8f506fd7b91b6d406a Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Wed, 28 Feb 2018 10:50:41 -0500 Subject: [PATCH 09/12] feat: Rename _value variable to _input --- packages/data-point/README.md | 4 ++-- packages/data-point/lib/debug-utils/stack.test.js | 4 ++-- packages/data-point/lib/reducer-types/resolve.js | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/data-point/README.md b/packages/data-point/README.md index 4afd14aa..f705e8a1 100644 --- a/packages/data-point/README.md +++ b/packages/data-point/README.md @@ -3334,7 +3334,7 @@ Example at: [examples/custom-entity-type.js](examples/custom-entity-type.js) When a reducer throws an error, DataPoint adds two properties to the error object: -`_value:*` - the input value to the reducer that failed +`_input:*` - the input value to the reducer that failed `_stack:String` - the reducer stack trace @@ -3366,7 +3366,7 @@ dataPoint.resolve('model:with-error', input) 'model:with-error[value] -> ReducerObject[a.b] -> throwError()' ) assert.equal( - error._value, + error._input, 'foo' ) }) diff --git a/packages/data-point/lib/debug-utils/stack.test.js b/packages/data-point/lib/debug-utils/stack.test.js index b54076a8..74d5b499 100644 --- a/packages/data-point/lib/debug-utils/stack.test.js +++ b/packages/data-point/lib/debug-utils/stack.test.js @@ -24,9 +24,9 @@ function testError (reducer, input) { .catch(error => error) .then(result => { expect(result).toBeInstanceOf(Error) - expect(result).toHaveProperty('_value') + expect(result).toHaveProperty('_input') expect(result).toHaveProperty('_stack') - expect(result._value).toMatchSnapshot() + expect(result._input).toMatchSnapshot() expect(result._stack).toMatchSnapshot() }) } diff --git a/packages/data-point/lib/reducer-types/resolve.js b/packages/data-point/lib/reducer-types/resolve.js index fe7520be..332632cc 100644 --- a/packages/data-point/lib/reducer-types/resolve.js +++ b/packages/data-point/lib/reducer-types/resolve.js @@ -79,13 +79,13 @@ module.exports.resolve = resolveReducer /** * @param {Reducer} reducer * @param {Array|String|Number} key - * @param {*} value + * @param {*} input * @param {Error} error - * @throws the given error with _value and _stack properties attached + * @throws the given error with _input and _stack properties attached */ -function onResolveMalfunction (reducer, key, value, error) { - if (!error.hasOwnProperty('_value')) { - error._value = value +function onResolveMalfunction (reducer, key, input, error) { + if (!error.hasOwnProperty('_input')) { + error._input = input } let stack From 178678dd0af04529116bd9530a54a27f3639d2b0 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Wed, 28 Feb 2018 10:54:47 -0500 Subject: [PATCH 10/12] docs: Update a comment describing ReducerFunction behavior --- .../data-point/lib/reducer-types/reducer-function/factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/data-point/lib/reducer-types/reducer-function/factory.js b/packages/data-point/lib/reducer-types/reducer-function/factory.js index 826a3e21..8d3ef5d7 100644 --- a/packages/data-point/lib/reducer-types/reducer-function/factory.js +++ b/packages/data-point/lib/reducer-types/reducer-function/factory.js @@ -62,7 +62,7 @@ function create (createReducer, source) { // do not include the name for arrow functions (which do not have a prototype), // because some arrow functions have inferred names, which might be confusing - // if they show up in the reducer stack traces for error messages + // if they show up in the error._stack string when an error is thrown const name = (reducer.body.prototype && reducer.body.name) || '' name && (reducer.name = name) return reducer From 4adaea51a2e29101df945504160458c667d95dc2 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Fri, 2 Mar 2018 17:12:32 -0500 Subject: [PATCH 11/12] test(stack.test.js): Improve test descriptions --- .../__snapshots__/stack.test.js.snap | 136 +++++++++--------- .../data-point/lib/debug-utils/stack.test.js | 68 ++++----- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap index cb5b18e9..04a617ae 100644 --- a/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap +++ b/packages/data-point/lib/debug-utils/__snapshots__/stack.test.js.snap @@ -1,22 +1,22 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ReducerAssign invalid input type 1`] = ` +exports[`ReducerAssign invalid input type #1 1`] = ` Object { "a": 1, "b": 2, } `; -exports[`ReducerAssign invalid input type 2`] = `"ReducerAssign -> ReducerObject[a] -> throwError()"`; +exports[`ReducerAssign invalid input type #1 2`] = `"ReducerAssign -> ReducerObject[a] -> throwError()"`; -exports[`ReducerAssign invalid input type 3`] = ` +exports[`ReducerAssign invalid input type #2 1`] = ` Object { "a": 1, "b": 2, } `; -exports[`ReducerAssign invalid input type 4`] = `"ReducerAssign -> ReducerObject[b] -> throwError()"`; +exports[`ReducerAssign invalid input type #2 2`] = `"ReducerAssign -> ReducerObject[b] -> throwError()"`; exports[`ReducerFilter invalid input type 1`] = `false`; @@ -74,7 +74,7 @@ exports[`ReducerMap throw error on 2nd item 1`] = `2`; exports[`ReducerMap throw error on 2nd item 2`] = `"ReducerMap[1] -> ReducerFunction()"`; -exports[`ReducerObject with errors nested property 1`] = ` +exports[`ReducerObject with errors nested property throws an error 1`] = ` Object { "a": 1, "b": Object { @@ -85,194 +85,194 @@ Object { } `; -exports[`ReducerObject with errors nested property 2`] = `"ReducerObject[b.c.d] -> throwError()"`; +exports[`ReducerObject with errors nested property throws an error 2`] = `"ReducerObject[b.c.d] -> throwError()"`; -exports[`ReducerObject with errors single property 1`] = ` +exports[`ReducerObject with errors single property throws an error 1`] = ` Object { "a": 1, "b": 2, } `; -exports[`ReducerObject with errors single property 2`] = `"ReducerObject[b] -> throwError()"`; +exports[`ReducerObject with errors single property throws an error 2`] = `"ReducerObject[b] -> throwError()"`; -exports[`control entity stack traces control:1 1`] = ` +exports[`control entity stack traces control:1 - first case throws an error 1`] = ` Object { "x": 1, } `; -exports[`control entity stack traces control:1 2`] = `"control:1 -> case[0] -> throwError()"`; +exports[`control entity stack traces control:1 - first case throws an error 2`] = `"control:1 -> case[0] -> throwError()"`; -exports[`control entity stack traces control:2 1`] = ` +exports[`control entity stack traces control:2 - second case throws an error 1`] = ` Object { "x": 1, } `; -exports[`control entity stack traces control:2 2`] = `"control:2 -> case[1] -> throwError()"`; +exports[`control entity stack traces control:2 - second case throws an error 2`] = `"control:2 -> case[1] -> throwError()"`; -exports[`control entity stack traces control:3 1`] = ` +exports[`control entity stack traces control:3 - first do throws an error 1`] = ` Object { "x": 1, } `; -exports[`control entity stack traces control:3 2`] = `"control:3 -> do[0] -> throwError()"`; +exports[`control entity stack traces control:3 - first do throws an error 2`] = `"control:3 -> do[0] -> throwError()"`; -exports[`control entity stack traces control:4 1`] = ` +exports[`control entity stack traces control:4 - second do throws an error 1`] = ` Object { "x": 1, } `; -exports[`control entity stack traces control:4 2`] = `"control:4 -> do[1] -> throwError()"`; +exports[`control entity stack traces control:4 - second do throws an error 2`] = `"control:4 -> do[1] -> throwError()"`; -exports[`control entity stack traces control:5 1`] = ` +exports[`control entity stack traces control:5 - default throws an error 1`] = ` Object { "x": 1, } `; -exports[`control entity stack traces control:5 2`] = `"control:5 -> do[default] -> throwError()"`; +exports[`control entity stack traces control:5 - default throws an error 2`] = `"control:5 -> do[default] -> throwError()"`; -exports[`do not log names for anonymous functions function in ReducerList 1`] = ` +exports[`do not log names for anonymous functions function in ReducerList throws an error 1`] = ` Object { "x": 1, } `; -exports[`do not log names for anonymous functions function in ReducerList 2`] = `"ReducerFunction()"`; +exports[`do not log names for anonymous functions function in ReducerList throws an error 2`] = `"ReducerFunction()"`; -exports[`do not log names for anonymous functions function with inferred name from object property 1`] = ` +exports[`do not log names for anonymous functions function with inferred name from object property throws an error 1`] = ` Object { "a": 1, } `; -exports[`do not log names for anonymous functions function with inferred name from object property 2`] = `"ReducerObject[a] -> ReducerFunction()"`; +exports[`do not log names for anonymous functions function with inferred name from object property throws an error 2`] = `"ReducerObject[a] -> ReducerFunction()"`; -exports[`do not log names for anonymous functions function with inferred name from variable 1`] = ` +exports[`do not log names for anonymous functions function with inferred name from variable throws an error 1`] = ` Object { "x": 1, } `; -exports[`do not log names for anonymous functions function with inferred name from variable 2`] = `"ReducerFunction()"`; +exports[`do not log names for anonymous functions function with inferred name from variable throws an error 2`] = `"ReducerFunction()"`; -exports[`entry entity stack traces entry:1 1`] = ` +exports[`entry entity stack traces entry:1 - before throws an error 1`] = ` Object { "x": 1, } `; -exports[`entry entity stack traces entry:1 2`] = `"entry:1[before] -> throwError()"`; +exports[`entry entity stack traces entry:1 - before throws an error 2`] = `"entry:1[before] -> throwError()"`; -exports[`entry entity stack traces entry:2 1`] = ` +exports[`entry entity stack traces entry:2 - value throws an error 1`] = ` Object { "x": 1, } `; -exports[`entry entity stack traces entry:2 2`] = `"entry:2[value] -> throwError()"`; +exports[`entry entity stack traces entry:2 - value throws an error 2`] = `"entry:2[value] -> throwError()"`; -exports[`entry entity stack traces entry:3 1`] = ` +exports[`entry entity stack traces entry:3 - after throws an error 1`] = ` Object { "x": 1, } `; -exports[`entry entity stack traces entry:3 2`] = `"entry:3[after] -> throwError()"`; +exports[`entry entity stack traces entry:3 - after throws an error 2`] = `"entry:3[after] -> throwError()"`; -exports[`entry entity stack traces entry:4 1`] = `[Error: test error message]`; +exports[`entry entity stack traces entry:4 - error handler throws a new error 1`] = `[Error: test error message]`; -exports[`entry entity stack traces entry:4 2`] = `"entry:4[error] -> throwError()"`; +exports[`entry entity stack traces entry:4 - error handler throws a new error 2`] = `"entry:4[error] -> throwError()"`; -exports[`entry entity stack traces entry:type-check-1 1`] = ` +exports[`entry entity stack traces entry:type-check-1 - 'string' inputType throws an error 1`] = ` Object { "x": 1, } `; -exports[`entry entity stack traces entry:type-check-1 2`] = `"entry:type-check-1[inputType] -> isString()"`; +exports[`entry entity stack traces entry:type-check-1 - 'string' inputType throws an error 2`] = `"entry:type-check-1[inputType] -> isString()"`; -exports[`entry entity stack traces entry:type-check-2 1`] = `500`; +exports[`entry entity stack traces entry:type-check-2 - 'string' outputType throws an error 1`] = `500`; -exports[`entry entity stack traces entry:type-check-2 2`] = `"entry:type-check-2[outputType] -> isString()"`; +exports[`entry entity stack traces entry:type-check-2 - 'string' outputType throws an error 2`] = `"entry:type-check-2[outputType] -> isString()"`; -exports[`entry entity stack traces entry:type-check-3 1`] = ` +exports[`entry entity stack traces entry:type-check-3 - inputType uses a schema and throws an error 1`] = ` Object { "x": 1, } `; -exports[`entry entity stack traces entry:type-check-3 2`] = `"entry:type-check-3[inputType] -> schema:a.1.0 -> ajv#validate()"`; +exports[`entry entity stack traces entry:type-check-3 - inputType uses a schema and throws an error 2`] = `"entry:type-check-3[inputType] -> schema:a.1.0 -> ajv#validate()"`; -exports[`entry entity stack traces entry:type-check-4 1`] = `500`; +exports[`entry entity stack traces entry:type-check-4 - outputType uses a schema and throws an error 1`] = `500`; -exports[`entry entity stack traces entry:type-check-4 2`] = `"entry:type-check-4[outputType] -> schema:a.1.0 -> ajv#validate()"`; +exports[`entry entity stack traces entry:type-check-4 - outputType uses a schema and throws an error 2`] = `"entry:type-check-4[outputType] -> schema:a.1.0 -> ajv#validate()"`; -exports[`model entity stack traces model:1 1`] = ` +exports[`model entity stack traces model:1 - before throws an error 1`] = ` Object { "x": 1, } `; -exports[`model entity stack traces model:1 2`] = `"model:1[before] -> throwError()"`; +exports[`model entity stack traces model:1 - before throws an error 2`] = `"model:1[before] -> throwError()"`; -exports[`model entity stack traces model:2 1`] = ` +exports[`model entity stack traces model:2 - value throws an error 1`] = ` Object { "x": 1, } `; -exports[`model entity stack traces model:2 2`] = `"model:2[value] -> throwError()"`; +exports[`model entity stack traces model:2 - value throws an error 2`] = `"model:2[value] -> throwError()"`; -exports[`model entity stack traces model:3 1`] = ` +exports[`model entity stack traces model:3 - after throws an error 1`] = ` Object { "x": 1, } `; -exports[`model entity stack traces model:3 2`] = `"model:3[after] -> throwError()"`; +exports[`model entity stack traces model:3 - after throws an error 2`] = `"model:3[after] -> throwError()"`; -exports[`model entity stack traces model:4 1`] = `[Error: test error message]`; +exports[`model entity stack traces model:4 - error handler throws a new error 1`] = `[Error: test error message]`; -exports[`model entity stack traces model:4 2`] = `"model:4[error] -> throwError()"`; +exports[`model entity stack traces model:4 - error handler throws a new error 2`] = `"model:4[error] -> throwError()"`; -exports[`request entity stack traces request:1 1`] = ` +exports[`request entity stack traces request:1 - value throws an error 1`] = ` Object { "x": 1, } `; -exports[`request entity stack traces request:1 2`] = `"request:1[value] -> ReducerList[1] -> throwError()"`; +exports[`request entity stack traces request:1 - value throws an error 2`] = `"request:1[value] -> ReducerList[1] -> throwError()"`; -exports[`request entity stack traces request:2 1`] = ` +exports[`request entity stack traces request:2 - options throws an error 1`] = ` Object { "x": 1, } `; -exports[`request entity stack traces request:2 2`] = `"request:2[options] -> ReducerObject[y] -> ReducerList[1] -> throwError()"`; +exports[`request entity stack traces request:2 - options throws an error 2`] = `"request:2[options] -> ReducerObject[y] -> ReducerList[1] -> throwError()"`; -exports[`request entity stack traces request:3 1`] = ` +exports[`request entity stack traces request:3 - before throws an error 1`] = ` Object { "x": 1, } `; -exports[`request entity stack traces request:3 2`] = `"request:3[before] -> throwError()"`; +exports[`request entity stack traces request:3 - before throws an error 2`] = `"request:3[before] -> throwError()"`; -exports[`request entity stack traces request:4 1`] = ` +exports[`request entity stack traces request:4 - after throws an error 1`] = ` Object { "ok": true, } `; -exports[`request entity stack traces request:4 2`] = `"request:4[after] -> throwError()"`; +exports[`request entity stack traces request:4 - after throws an error 2`] = `"request:4[after] -> throwError()"`; -exports[`request entity stack traces request:5 1`] = ` +exports[`request entity stack traces request:5 - request returns a 404 1`] = ` Object { "json": true, "method": "GET", @@ -280,45 +280,45 @@ Object { } `; -exports[`request entity stack traces request:5 2`] = `"request:5 -> request-promise#request()"`; +exports[`request entity stack traces request:5 - request returns a 404 2`] = `"request:5 -> request-promise#request()"`; -exports[`schema entity stack traces schema:a.1.0 1`] = ` +exports[`schema entity stack traces schema:a.1.0 - schema throws an error from ajv#validate 1`] = ` Object { "baaaaaar": "1", "foo": 1, } `; -exports[`schema entity stack traces schema:a.1.0 2`] = `"schema:a.1.0 -> ajv#validate()"`; +exports[`schema entity stack traces schema:a.1.0 - schema throws an error from ajv#validate 2`] = `"schema:a.1.0 -> ajv#validate()"`; -exports[`schema entity stack traces schema:with-value-prop 1`] = ` +exports[`schema entity stack traces schema:with-value-prop - value throws an error 1`] = ` Object { "x": 1, } `; -exports[`schema entity stack traces schema:with-value-prop 2`] = `"schema:with-value-prop[value] -> throwError()"`; +exports[`schema entity stack traces schema:with-value-prop - value throws an error 2`] = `"schema:with-value-prop[value] -> throwError()"`; -exports[`transform entity stack traces transform:1 1`] = ` +exports[`transform entity stack traces transform:1 - value is a function that throws an error 1`] = ` Object { "x": 1, } `; -exports[`transform entity stack traces transform:1 2`] = `"transform:1[value] -> ReducerFunction()"`; +exports[`transform entity stack traces transform:1 - value is a function that throws an error 2`] = `"transform:1[value] -> ReducerFunction()"`; -exports[`transform entity stack traces transform:2 1`] = ` +exports[`transform entity stack traces transform:2 - references another entity that throws an error 1`] = ` Object { "x": 1, } `; -exports[`transform entity stack traces transform:2 2`] = `"transform:2[value] -> transform:1[value] -> ReducerFunction()"`; +exports[`transform entity stack traces transform:2 - references another entity that throws an error 2`] = `"transform:2[value] -> transform:1[value] -> ReducerFunction()"`; -exports[`transform entity stack traces transform:3 1`] = ` +exports[`transform entity stack traces transform:3 - references an entity chain that throws an error 1`] = ` Object { "x": 1, } `; -exports[`transform entity stack traces transform:3 2`] = `"transform:3[value] -> transform:2[value] -> transform:1[value] -> ReducerFunction()"`; +exports[`transform entity stack traces transform:3 - references an entity chain that throws an error 2`] = `"transform:3[value] -> transform:2[value] -> transform:1[value] -> ReducerFunction()"`; diff --git a/packages/data-point/lib/debug-utils/stack.test.js b/packages/data-point/lib/debug-utils/stack.test.js index 74d5b499..95854f25 100644 --- a/packages/data-point/lib/debug-utils/stack.test.js +++ b/packages/data-point/lib/debug-utils/stack.test.js @@ -172,102 +172,102 @@ const dataPoint = DataPoint.create({ dataPoint.addEntities(schemaA10) describe('transform entity stack traces', () => { - test('transform:1', () => { + test('transform:1 - value is a function that throws an error', () => { return testError('transform:1', { x: 1 }) }) - test('transform:2', () => { + test('transform:2 - references another entity that throws an error', () => { return testError('transform:2', { x: 1 }) }) - test('transform:3', () => { + test('transform:3 - references an entity chain that throws an error', () => { return testError('transform:3', { x: 1 }) }) }) describe('request entity stack traces', () => { - test('request:1', () => { + test('request:1 - value throws an error', () => { return testError('request:1', { x: 1 }) }) - test('request:2', () => { + test('request:2 - options throws an error', () => { return testError('request:2', { x: 1 }) }) - test('request:3', () => { + test('request:3 - before throws an error', () => { return testError('request:3', { x: 1 }) }) - test('request:4', () => { + test('request:4 - after throws an error', () => { return testError('request:4', { x: 1 }) }) - test('request:5', () => { + test('request:5 - request returns a 404', () => { return testError('request:5', { x: 1 }) }) }) describe('control entity stack traces', () => { - test('control:1', () => { + test('control:1 - first case throws an error', () => { return testError('control:1', { x: 1 }) }) - test('control:2', () => { + test('control:2 - second case throws an error', () => { return testError('control:2', { x: 1 }) }) - test('control:3', () => { + test('control:3 - first do throws an error', () => { return testError('control:3', { x: 1 }) }) - test('control:4', () => { + test('control:4 - second do throws an error', () => { return testError('control:4', { x: 1 }) }) - test('control:5', () => { + test('control:5 - default throws an error', () => { return testError('control:5', { x: 1 }) }) }) describe('model entity stack traces', () => { - test('model:1', () => { + test('model:1 - before throws an error', () => { return testError('model:1', { x: 1 }) }) - test('model:2', () => { + test('model:2 - value throws an error', () => { return testError('model:2', { x: 1 }) }) - test('model:3', () => { + test('model:3 - after throws an error', () => { return testError('model:3', { x: 1 }) }) - test('model:4', () => { + test('model:4 - error handler throws a new error', () => { return testError('model:4', { x: 1 }) }) }) describe('entry entity stack traces', () => { - test('entry:1', () => { + test('entry:1 - before throws an error', () => { return testError('entry:1', { x: 1 }) }) - test('entry:2', () => { + test('entry:2 - value throws an error', () => { return testError('entry:2', { x: 1 }) }) - test('entry:3', () => { + test('entry:3 - after throws an error', () => { return testError('entry:3', { x: 1 }) }) - test('entry:4', () => { + test('entry:4 - error handler throws a new error', () => { return testError('entry:4', { x: 1 }) }) - test('entry:type-check-1', () => { + test("entry:type-check-1 - 'string' inputType throws an error", () => { return testError('entry:type-check-1', { x: 1 }) }) - test('entry:type-check-2', () => { + test("entry:type-check-2 - 'string' outputType throws an error", () => { return testError('entry:type-check-2', { x: 1 }) }) - test('entry:type-check-3', () => { + test('entry:type-check-3 - inputType uses a schema and throws an error', () => { return testError('entry:type-check-3', { x: 1 }) }) - test('entry:type-check-4', () => { + test('entry:type-check-4 - outputType uses a schema and throws an error', () => { return testError('entry:type-check-4', { x: 1 }) }) }) describe('schema entity stack traces', () => { - test('schema:with-value-prop', () => { + test('schema:with-value-prop - value throws an error', () => { return testError('schema:with-value-prop', { x: 1 }) }) - test('schema:a.1.0', () => { + test('schema:a.1.0 - schema throws an error from ajv#validate', () => { return testError('schema:a.1.0', { foo: 1, baaaaaar: '1' @@ -307,14 +307,14 @@ describe('ReducerList with errors', () => { }) describe('ReducerObject with errors', () => { - test('single property', () => { + test('single property throws an error', () => { const reducer = { a: '$a', b: throwError } return testError(reducer, { a: 1, b: 2 }) }) - test('nested property', () => { + test('nested property throws an error', () => { const reducer = { a: '$a', b: { @@ -328,7 +328,7 @@ describe('ReducerObject with errors', () => { }) describe('do not log names for anonymous functions', () => { - test('function in ReducerList', () => { + test('function in ReducerList throws an error', () => { const reducer = [ () => { throw new Error('test error') @@ -336,13 +336,13 @@ describe('do not log names for anonymous functions', () => { ] return testError(reducer, { x: 1 }) }) - test('function with inferred name from variable', () => { + test('function with inferred name from variable throws an error', () => { const anonFunction = () => { throw new Error('test error') } return testError(anonFunction, { x: 1 }) }) - test('function with inferred name from object property', () => { + test('function with inferred name from object property throws an error', () => { const reducer = { a: () => { throw new Error('test error') @@ -353,11 +353,11 @@ describe('do not log names for anonymous functions', () => { }) describe('ReducerAssign', () => { - test('invalid input type', () => { + test('invalid input type #1', () => { const reducer = assign({ a: throwError, b: '$b' }) return testError(reducer, { a: 1, b: 2 }) }) - test('invalid input type', () => { + test('invalid input type #2', () => { const reducer = assign({ a: '$a', b: throwError }) return testError(reducer, { a: 1, b: 2 }) }) From 692ce8001ccb425fb7af7786e9c2ffa0916612c9 Mon Sep 17 00:00:00 2001 From: Matthew Armstrong Date: Sun, 11 Mar 2018 09:32:16 -0400 Subject: [PATCH 12/12] fix(reducer-types): Call onResolveMalfunction even if a default value is set --- packages/data-point/lib/reducer-types/resolve.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/data-point/lib/reducer-types/resolve.js b/packages/data-point/lib/reducer-types/resolve.js index 332632cc..7561294f 100644 --- a/packages/data-point/lib/reducer-types/resolve.js +++ b/packages/data-point/lib/reducer-types/resolve.js @@ -61,14 +61,16 @@ function resolveReducer (manager, accumulator, reducer, key) { // can't trust it won't be overwritten in the accumulator object // although it could still be modified by reference if it's an object const value = accumulator.value - const result = Promise.try(() => getResolveFunction(reducer)) + let result = Promise.try(() => { + const resolve = getResolveFunction(reducer) // NOTE: recursive call - .then(resolve => resolve(manager, resolveReducer, accumulator, reducer)) + return resolve(manager, resolveReducer, accumulator, reducer) + }) if (hasDefault(reducer)) { const _default = reducer[DEFAULT_VALUE].value const resolveDefault = reducers.ReducerDefault.resolve - return result.then(acc => resolveDefault(acc, _default)) + result = result.then(acc => resolveDefault(acc, _default)) } return result.catch(error => onResolveMalfunction(reducer, key, value, error))