Skip to content

Commit

Permalink
feat(data-point/reducer-types): Add the ReducerConstant type (#173)
Browse files Browse the repository at this point in the history
closes #173
  • Loading branch information
raingerber authored and acatl committed Jan 27, 2018
1 parent d1515c0 commit 7a95d38
Show file tree
Hide file tree
Showing 22 changed files with 611 additions and 161 deletions.
111 changes: 104 additions & 7 deletions packages/data-point/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ npm install --save data-point
- [map](#reducer-map)
- [filter](#reducer-filter)
- [find](#reducer-find)
- [constant](#reducer-constant)
- [Entities](#entities)
- [dataPoint.addEntities](#api-data-point-add-entities)
- [Built-in entities](#built-in-entities)
Expand Down Expand Up @@ -283,6 +284,7 @@ This method returns a **Promise** with the final output value.

- [Hello World](#hello-world) example.
- [With options](#acc-locals-example) example.
- [With constants in options](#options-with-constants) example.

### <a name="api-data-point-transform">dataPoint.transform()</a>

Expand All @@ -309,7 +311,7 @@ This method will return a **Promise** if `done` is omitted.
|:---|:---|:---|
| *reducer* | [Reducer](#reducers) | Reducer that manipulates the input. |
| *input* | `*` | Input value that you want to transform. If **none**, pass `null` or empty object `{}`. |
| *options* | [TransformOptions](#transform-options) | Options within the scope of the current transformation |
| *options* | [Reducer](#reducers) | Request options. See this [example](#options-with-constants) for using constants in the reducer |
| *done* | `function` _(optional)_ | Error-first callback [Node.js style callback](https://nodejs.org/api/errors.html#errors_node_js_style_callbacks) that has the arguments `(error, result)`, where `result` contains the final resolved [Accumulator](#accumulator). The actual transformation result will be inside the `result.value` property. |

**<a name="transform-options">TransformOptions</a>**
Expand Down Expand Up @@ -638,7 +640,7 @@ Example at: [examples/reducer-function-error.js](examples/reducer-function-error
### <a name="object-reducer">ObjectReducer</a>
ObjectReducers are plain objects where the values are reducers. They're used to aggregate data or transform objects.
ObjectReducers are plain objects where the values are reducers. They're used to aggregate data or transform objects. For values that should be constants instead of reducers, you can use the [constant](#reducer-constant) reducer helper.
<details>
<summary>Transforming an object</summary>
Expand Down Expand Up @@ -1134,6 +1136,58 @@ find(reducer:Reducer):*

Example at: [examples/reducer-helper-find.js](examples/reducer-helper-find.js)

### <a name="reducer-constant">constant</a>

The **constant** reducer always returns the given value.

**SYNOPSIS**

```js
constant(value:*):*
```

**Reducer's arguments**

| Argument | Type | Description |
|:---|:---|:---|
| *value* | * | The value the reducer should return |

**EXAMPLE:**

<details>
<summary>returning an object constant</summary>

```js
const { constant } = DataPoint.helpers

const input = {
a: 1,
b: 2
}

const reducer = {
a: '$a',
b: constant({
a: '$a',
b: 3
})
}

dataPoint
.resolve(reducer, input)
.then(output => {
// {
// a: 1,
// b: {
// a: '$a',
// b: 3
// }
// }
}
})
```
</details>

## <a name="entities">Entities</a>

Entities are artifacts that transform data. An entity is represented by a data structure (spec) that defines how the entity behaves.
Expand Down Expand Up @@ -1703,7 +1757,7 @@ dataPoint.addEntities({
| *inputType* | String, [Reducer](#reducers) | type checks the entity's input value, does not mutate value. [Entity Type checking](#entity-type-check) |
| *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 should return an object to use as request options. These map directly to [request.js](https://github.com/request/request) options
| *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 the entity's output value, does not mutate value. [Entity Type checking](#entity-type-check) |
Expand Down Expand Up @@ -1744,7 +1798,7 @@ Example at: [examples/entity-request-basic.js](examples/entity-request-basic.js)

StringTemplate is a string that supports a **minimal** templating system. You may inject any value into the string by enclosing it within `{ObjectPath}` curly braces. **The context of the string is the Request's [Accumulator](#accumulator) Object**, meaning you have access to any property within it.

Using `acc.value` property to make the url dynamic.
Using `acc.value` property to make the url dynamic:

<details>
<summary>`acc.value` Example</summary>
Expand Down Expand Up @@ -1775,11 +1829,10 @@ Using `acc.value` property to make the url dynamic.
```
</details>

Example at: [examples/entity-request-string-template.js](examples/entity-request-string-template.js)

<a name="acc-locals-example" >Using `acc.locals` property to make the url dynamic:</a>

For more information on acc.locals: [TransformOptions](#transform-options) and [Accumulator](#accumulator) Objects.

<details>
<summary>`acc.locals` example</summary>

Expand Down Expand Up @@ -1811,8 +1864,52 @@ For more information on acc.locals: [TransformOptions](#transform-options) and [
```
</details>

Example at: [examples/entity-request-options-locals.js](examples/entity-request-options-locals.js)

For more information on acc.locals: [TransformOptions](#transform-options) and [Accumulator](#accumulator) Objects.

<a name="options-with-constants" >Using constants in the options reducer:</a>

<details>
<summary>constants example</summary>

```js
const DataPoint = require('data-point')
const c = DataPoint.helpers.constant
const dataPoint = DataPoint.create()

dataPoint.addEntities({
'request:searchPeople': {
url: 'https://swapi.co/api/people',
// options is a Reducer, but values
// at any level can be wrapped as
// constants (or just wrap the whole
// object if all the values are static)
options: {
'content-type': c('application/json'), // constant
qs: {
// get path `searchTerm` from input
// to dataPoint.resolve
search: '$searchTerm'
}
}
}
})

const input = {
searchTerm: 'r2'
}

// the second parameter to transform is the input value
dataPoint
.resolve('request:searchPeople', input)
.then(output => {
assert.equal(output.results[0].name, 'R2-D2')
})
```
</details>

Example at: [examples/entity-request-string-template.js](examples/entity-request-options-locals.js)
Example at: [examples/entity-request-options.js](examples/entity-request-options.js)

For more examples of request entities, see the [Examples](examples), the [Integration Examples](test/definitions/integrations.js), and the unit tests: [Request Definitions](test/definitions/sources.js).

Expand Down
38 changes: 38 additions & 0 deletions packages/data-point/examples/entity-request-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const assert = require('assert')
const mock = require('./entity-request-options.mock')

const DataPoint = require('..')

const c = DataPoint.helpers.constant
const dataPoint = DataPoint.create()

dataPoint.addEntities({
'request:searchPeople': {
url: 'https://swapi.co/api/people',
// options is a "ReducerObject", but
// values at any level can be wrapped
// as constants (or just wrap the whole
// object if all the values are static)
options: {
'content-type': c('application/json'), // constant
qs: {
// get path `searchTerm` from input
// to dataPoint.resolve
search: '$searchTerm'
}
}
}
})

// this will mock the remote service
mock()

const input = {
searchTerm: 'r2'
}

// the second parameter to transform is the input value
dataPoint.resolve('request:searchPeople', input).then(output => {
assert.equal(output.results[0].name, 'R2-D2')
console.dir(output, { colors: true })
})
19 changes: 19 additions & 0 deletions packages/data-point/examples/entity-request-options.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const nock = require('nock')

module.exports = () => {
nock('https://swapi.co')
.get('/api/people')
.query({
search: 'r2'
})
.reply(200, {
count: 1,
next: null,
previous: null,
results: [
{
name: 'R2-D2'
}
]
})
}
1 change: 1 addition & 0 deletions packages/data-point/lib/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Object {
"createReducerResolver": [Function],
"helpers": Object {
"assign": [Function],
"constant": [Function],
"filter": [Function],
"find": [Function],
"isArray": [Function],
Expand Down
20 changes: 10 additions & 10 deletions packages/data-point/lib/entity-types/entity-collection/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ function validateAsArray (acc) {
return acc.value instanceof Array
? acc
: Promise.reject(
new Error(
Util.format(
'%s received value = %s of type %s,',
entity.id,
_.truncate(Util.inspect(acc.value, { breakLength: Infinity }), {
length: 30
}),
utils.typeOf(acc.value),
'this entity only resolves Array values. More info https://github.com/ViacomInc/data-point/tree/master/packages/data-point#collection-entity'
new Error(
Util.format(
'%s received value = %s of type %s,',
entity.id,
_.truncate(Util.inspect(acc.value, { breakLength: Infinity }), {
length: 30
}),
utils.typeOf(acc.value),
'this entity only resolves Array values. More info https://github.com/ViacomInc/data-point/tree/master/packages/data-point#collection-entity'
)
)
)
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,9 @@ describe('factory#parse composed modifiers', () => {
)

expect(result.compose.reducers[1]).toHaveProperty('type', 'ReducerObject')
expect(result.compose.reducers[1].props).toHaveLength(1)
expect(result.compose.reducers[1].props[0].reducer).toHaveProperty(
expect(result.compose.reducers[1].source()).toEqual({})
expect(result.compose.reducers[1].reducers).toHaveLength(1)
expect(result.compose.reducers[1].reducers[0].reducer).toHaveProperty(
'type',
'ReducerPath'
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Object {
"createReducerResolver": [Function],
"helpers": Object {
"assign": [Function],
"constant": [Function],
"filter": [Function],
"find": [Function],
"isArray": [Function],
Expand Down
1 change: 1 addition & 0 deletions packages/data-point/lib/helpers/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const typeCheckFunctionReducers = require('./type-check-function-reducers')

module.exports.helpers = {
assign: stubFactories.assign,
constant: stubFactories.constant,
filter: stubFactories.filter,
find: stubFactories.find,
map: stubFactories.map,
Expand Down
3 changes: 2 additions & 1 deletion packages/data-point/lib/reducer-types/factory.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ describe('reducer#create', () => {
test('create object', () => {
const reducer = Factory.create({})
expect(reducer.type).toBe(ReducerObject.type)
expect(reducer.props).toHaveLength(0)
expect(reducer.source()).toEqual({})
expect(reducer.reducers).toHaveLength(0)
})

test('create entity', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const createStub = require('./reducer-stub').create

const reducerAssign = require('./reducer-assign')
const reducerConstant = require('./reducer-constant')
const reducerFilter = require('./reducer-filter')
const reducerFind = require('./reducer-find')
const reducerMap = require('./reducer-map')
Expand Down Expand Up @@ -30,6 +31,7 @@ module.exports.isType = require('./reducer-stub').isType

const reducers = {
[reducerAssign.type]: reducerAssign,
[reducerConstant.type]: reducerConstant,
[reducerFilter.type]: reducerFilter,
[reducerFind.type]: reducerFind,
[reducerMap.type]: reducerMap,
Expand All @@ -45,6 +47,7 @@ function bindStubFunction (reducerType) {

const stubFactories = {
[reducerAssign.name]: bindStubFunction(reducerAssign.type),
[reducerConstant.name]: bindStubFunction(reducerConstant.type),
[reducerFilter.name]: bindStubFunction(reducerFilter.type),
[reducerFind.name]: bindStubFunction(reducerFind.type),
[reducerMap.name]: bindStubFunction(reducerMap.type),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const REDUCER_CONSTANT = 'ReducerConstant'

module.exports.type = REDUCER_CONSTANT

const HELPER_NAME = 'constant'

module.exports.name = HELPER_NAME

/**
* @class
* @property {string} type
* @property {*} value
*/
function ReducerConstant () {
this.type = REDUCER_CONSTANT
this.value = undefined
}

module.exports.ReducerConstant = ReducerConstant

/**
* @param {Function} createReducer
* @param {*} value
* @return {ReducerConstant}
*/
function create (createReducer, value) {
const reducer = new ReducerConstant()
reducer.value = value

return reducer
}

module.exports.create = create
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-env jest */

const Factory = require('./factory')
const createReducer = require('../../index').create

describe('ReducerConstant#factory', () => {
test('reducer with undefined as value', () => {
const reducer = Factory.create(createReducer)
expect(reducer).toBeInstanceOf(Factory.ReducerConstant)
expect(reducer.type).toBe(Factory.type)
expect(reducer.value).toBe(undefined)
})
test('reducers with different value types', () => {
expect(Factory.create(createReducer, null).value).toBe(null)
expect(Factory.create(createReducer, 100).value).toBe(100)
expect(Factory.create(createReducer, 'string').value).toBe('string')
expect(Factory.create(createReducer, ['a', 'b']).value).toEqual(['a', 'b'])
expect(Factory.create(createReducer, { a: 1, b: 2 }).value).toEqual({
a: 1,
b: 2
})
})
})
Loading

0 comments on commit 7a95d38

Please sign in to comment.