Skip to content

Commit 3f3ad40

Browse files
committed
Feature: .prop()
1 parent 602918f commit 3f3ad40

File tree

8 files changed

+195
-8
lines changed

8 files changed

+195
-8
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ request
132132

133133
// You can use async/await of course
134134
const { status, body } = await request.get('http://example.com')
135+
136+
// Helper method to return a nested value from the request
137+
const status = await request.get('https://example.com/accounts.json').prop('status')
138+
const contentType = await request.get('https://example.com/accounts.json').prop(['headers', 'content-type'])
139+
const data = await request.get('https://example.com/accounts.json').prop('data')
140+
const account = await request.get('https://example.com/accounts.json').prop('data.accounts[0]')
135141
```
136142

137143

@@ -154,6 +160,7 @@ The following methods are available.
154160
- [.urlencoded(value)](#urlencoded)
155161
- [.timeout(milliseconds)](#timeout)
156162
- [.unsetTimeout()](#unsettimeout)
163+
- [.prop(path)](#prop)
157164
- [.send([body])](#send)
158165
- [.sendUrlencoded(data)](#sendurlencoded)
159166
- [.sendJson(data)](#sendjson)
@@ -355,6 +362,23 @@ Removes the timeout-value previously set with [`timeout`](#timeout).
355362
.unsetTimeout()
356363
```
357364

365+
### prop
366+
367+
Sets a path (similar to [`lodash.get`](https://lodash.com/docs/4.17.15#get)) which will be resolved once the response is returned.
368+
369+
```js
370+
.prop(path)
371+
```
372+
373+
Where `path` is a string or an array.
374+
375+
Example:
376+
377+
```js
378+
cosnt account = await request.get('...').prop('data.accounts[0]');
379+
cosnt contentType = await request.get('...').prop(['headers', 'content-type']);
380+
```
381+
358382
### send
359383

360384
Dispatches the request and returns a `Promise`.

build/yea.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
return assign({}, config, updated, {
7676
headers: assign({}, config.headers, updated.headers || {}),
7777
responseTransformers: [].concat(updated.responseTransformers || config.responseTransformers),
78-
polyfills: assign({}, config.polyfills, updated.polyfills || {})
78+
polyfills: assign({}, config.polyfills, updated.polyfills || {}),
79+
prop: [].concat(updated.prop || config.prop),
7980
});
8081
}
8182

@@ -86,6 +87,21 @@
8687
return response;
8788
}
8889

90+
function parsePropPath(path) {
91+
if (Array.isArray(path)) {
92+
return path;
93+
}
94+
return path.replace(/\]$/, '').replace(/[[\]]/g, '.').split('.');
95+
}
96+
97+
function applyPropPath(object, path) {
98+
var value = object;
99+
for (var i = 0; i < path.length; i++) {
100+
value = value[path[i]];
101+
}
102+
return value;
103+
}
104+
89105
function YeaAjaxRequest(config) {
90106
this._config = config;
91107
this.jsonResponseTransformer = jsonResponseTransformer;
@@ -95,7 +111,9 @@
95111
this.utils = {
96112
assign: assign,
97113
toQueryString: toQueryString,
98-
createUrl: createUrl
114+
createUrl: createUrl,
115+
parsePropPath: parsePropPath,
116+
applyPropPath: applyPropPath
99117
};
100118
}
101119

@@ -215,6 +233,17 @@
215233
return new YeaAjaxRequest(mergeConfig(this._config, { timeout: null }));
216234
};
217235

236+
YeaAjaxRequest.prototype.prop = function prop(path) {
237+
if (path === null || path === '') {
238+
path = [];
239+
} else if (typeof path === 'string') {
240+
path = parsePropPath(path);
241+
} else if (!Array.isArray(path)) {
242+
throw new Error('Expected a string (e.g. "data.items[0]") or an array for prop');
243+
}
244+
return new YeaAjaxRequest(mergeConfig(this._config, { prop: path }));
245+
};
246+
218247
YeaAjaxRequest.prototype.setResponseTransformers = function setResponseTransformers(transformers) {
219248
if (!Array.isArray(transformers)) {
220249
throw new Error('Expected an array of response transformers');
@@ -318,6 +347,9 @@
318347
if (timeoutId) {
319348
clearTimeout(timeoutId);
320349
}
350+
351+
response = applyPropPath(response, config.prop);
352+
321353
resolve(response);
322354
} else {
323355
var error = new Error('Request failed with status ' + response.status);
@@ -363,6 +395,7 @@
363395
headers: {},
364396
allowedStatusCode: /^2[0-9]{2}$/,
365397
timeout: null,
398+
prop: [],
366399
responseTransformers: [
367400
jsonResponseTransformer
368401
],

build/yea.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@
4949
"bundlesize": [
5050
{
5151
"path": "./build/yea.js",
52-
"maxSize": "2.8KB"
52+
"maxSize": "3.0KB"
5353
},
5454
{
5555
"path": "./build/yea.min.js",
56-
"maxSize": "2.4KB"
56+
"maxSize": "2.6KB"
5757
}
5858
],
5959
"scripts": {

src/index.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@
7575
return assign({}, config, updated, {
7676
headers: assign({}, config.headers, updated.headers || {}),
7777
responseTransformers: [].concat(updated.responseTransformers || config.responseTransformers),
78-
polyfills: assign({}, config.polyfills, updated.polyfills || {})
78+
polyfills: assign({}, config.polyfills, updated.polyfills || {}),
79+
prop: [].concat(updated.prop || config.prop),
7980
});
8081
}
8182

@@ -86,6 +87,21 @@
8687
return response;
8788
}
8889

90+
function parsePropPath(path) {
91+
if (Array.isArray(path)) {
92+
return path;
93+
}
94+
return path.replace(/\]$/, '').replace(/[[\]]/g, '.').split('.');
95+
}
96+
97+
function applyPropPath(object, path) {
98+
var value = object;
99+
for (var i = 0; i < path.length; i++) {
100+
value = value[path[i]];
101+
}
102+
return value;
103+
}
104+
89105
function YeaAjaxRequest(config) {
90106
this._config = config;
91107
this.jsonResponseTransformer = jsonResponseTransformer;
@@ -95,7 +111,9 @@
95111
this.utils = {
96112
assign: assign,
97113
toQueryString: toQueryString,
98-
createUrl: createUrl
114+
createUrl: createUrl,
115+
parsePropPath: parsePropPath,
116+
applyPropPath: applyPropPath
99117
};
100118
}
101119

@@ -215,6 +233,17 @@
215233
return new YeaAjaxRequest(mergeConfig(this._config, { timeout: null }));
216234
};
217235

236+
YeaAjaxRequest.prototype.prop = function prop(path) {
237+
if (path === null || path === '') {
238+
path = [];
239+
} else if (typeof path === 'string') {
240+
path = parsePropPath(path);
241+
} else if (!Array.isArray(path)) {
242+
throw new Error('Expected a string (e.g. "data.items[0]") or an array for prop');
243+
}
244+
return new YeaAjaxRequest(mergeConfig(this._config, { prop: path }));
245+
};
246+
218247
YeaAjaxRequest.prototype.setResponseTransformers = function setResponseTransformers(transformers) {
219248
if (!Array.isArray(transformers)) {
220249
throw new Error('Expected an array of response transformers');
@@ -318,6 +347,9 @@
318347
if (timeoutId) {
319348
clearTimeout(timeoutId);
320349
}
350+
351+
response = applyPropPath(response, config.prop);
352+
321353
resolve(response);
322354
} else {
323355
var error = new Error('Request failed with status ' + response.status);
@@ -363,6 +395,7 @@
363395
headers: {},
364396
allowedStatusCode: /^2[0-9]{2}$/,
365397
timeout: null,
398+
prop: [],
366399
responseTransformers: [
367400
jsonResponseTransformer
368401
],

test/specs/methods.spec.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ describe('Methods', function () {
22
describe('defaults', function () {
33
it('has defaults', function () {
44
var defaults = yea.toObject();
5-
expect(defaults).to.have.keys(['method', 'baseUrl', 'url', 'query', 'body', 'headers', 'responseTransformers', 'allowedStatusCode', 'timeout', 'polyfills']);
5+
expect(defaults).to.have.keys(['method', 'baseUrl', 'url', 'query', 'body', 'headers', 'responseTransformers', 'allowedStatusCode', 'timeout', 'prop', 'polyfills']);
66
expect(defaults.method).to.equal('GET');
77
expect(defaults.baseUrl).to.equal('');
88
expect(defaults.url).to.equal('');
@@ -12,6 +12,7 @@ describe('Methods', function () {
1212
expect(defaults.responseTransformers).to.deep.equal([yea.jsonResponseTransformer]);
1313
expect(defaults.allowedStatusCode).to.deep.equal(/^2[0-9]{2}$/);
1414
expect(defaults.timeout).to.equal(null);
15+
expect(defaults.prop).to.deep.equal([]);
1516
expect(defaults.polyfills).to.deep.equal({});
1617
});
1718
});
@@ -350,6 +351,43 @@ describe('Methods', function () {
350351
});
351352
});
352353

354+
describe('.prop', function () {
355+
it('sets prop', function () {
356+
expect(yea.prop(null).toObject().prop).to.deep.equal([]);
357+
expect(yea.prop('').toObject().prop).to.deep.equal([]);
358+
expect(yea.prop('data.accounts[0]').toObject().prop).to.deep.equal(['data', 'accounts', '0']);
359+
expect(yea.prop(['data', 'accounts', '0']).toObject().prop).to.deep.equal(['data', 'accounts', '0']);
360+
});
361+
362+
it('throws on unexpected type', function () {
363+
expect(function () {
364+
yea.prop({});
365+
}).to.throw('Expected a string (e.g. "data.items[0]") or an array for prop');
366+
367+
expect(function () {
368+
yea.prop(function () {});
369+
}).to.throw('Expected a string (e.g. "data.items[0]") or an array for prop');
370+
371+
expect(function () {
372+
yea.prop(123);
373+
}).to.throw('Expected a string (e.g. "data.items[0]") or an array for prop');
374+
});
375+
376+
it('leaves no reference to the array', function () {
377+
var array = ['headers', 'content-type'];
378+
var req = yea.prop(array);
379+
expect(req.toObject().prop).to.not.equal(array);
380+
array.splice(0, 1);
381+
expect(req.toObject().prop).to.deep.equal(['headers', 'content-type']);
382+
});
383+
384+
it('is immutable', function () {
385+
var req = yea.prop('data');
386+
expect(req).to.not.equal(yea);
387+
expect(req.constructor).to.equal(yea.constructor);
388+
});
389+
});
390+
353391
describe('.send', function () {
354392
it('exists (see requests.spec.js for more tests)', function () {
355393
expect(yea.send).to.be.ok;

test/specs/requests.spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,30 @@ describe('Requests', function () {
224224
});
225225
});
226226

227+
it('returns property via .prop()', function () {
228+
return yea
229+
.method('get')
230+
.url('/json-payload')
231+
.prop('data')
232+
.send()
233+
.then(function (body) {
234+
expect(body).to.deep.equal({
235+
taker: 'believer'
236+
});
237+
});
238+
});
239+
240+
it('returns nested property via .prop()', function () {
241+
return yea
242+
.method('get')
243+
.url('/json-payload')
244+
.prop('data.taker')
245+
.send()
246+
.then(function (value) {
247+
expect(value).to.equal('believer');
248+
});
249+
});
250+
227251
it('throws on 404', function () {
228252
var error;
229253
return yea

test/specs/utils.spec.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,39 @@ describe('utils', function () {
8181
expect(yea.utils.createUrl('', 'accounts', 'foo=bar')).to.equal('accounts?foo=bar');
8282
});
8383
});
84+
85+
describe('.parsePropPath', function () {
86+
it('parses path', function () {
87+
// Simple dot notation
88+
expect(yea.utils.parsePropPath('data')).to.deep.equal(['data']);
89+
expect(yea.utils.parsePropPath('headers')).to.deep.equal(['headers']);
90+
expect(yea.utils.parsePropPath('data.accounts')).to.deep.equal(['data', 'accounts']);
91+
// Array indices
92+
expect(yea.utils.parsePropPath('data.accounts[0]')).to.deep.equal(['data', 'accounts', '0']);
93+
expect(yea.utils.parsePropPath('data.accounts[1]')).to.deep.equal(['data', 'accounts', '1']);
94+
});
95+
});
96+
97+
describe('.applyPropPath', function () {
98+
it('picks requested value from object', function () {
99+
var response = {
100+
headers: {
101+
'content-type': 'application/json'
102+
},
103+
data: {
104+
accounts: [
105+
{ name: 'Account 1' },
106+
{ name: 'Account 2' }
107+
]
108+
}
109+
};
110+
expect(yea.utils.applyPropPath(response, [])).to.equal(response);
111+
expect(yea.utils.applyPropPath(response, ['headers'])).to.equal(response.headers);
112+
expect(yea.utils.applyPropPath(response, ['data'])).to.equal(response.data);
113+
expect(yea.utils.applyPropPath(response, ['data', 'accounts'])).to.equal(response.data.accounts);
114+
expect(yea.utils.applyPropPath(response, ['data', 'accounts', '0'])).to.equal(response.data.accounts['0']);
115+
expect(yea.utils.applyPropPath(response, ['data', 'accounts', '1'])).to.equal(response.data.accounts['1']);
116+
expect(yea.utils.applyPropPath(response, ['headers', 'content-type'])).to.equal(response.headers['content-type']);
117+
});
118+
});
84119
});

0 commit comments

Comments
 (0)