Skip to content

Commit

Permalink
feat(api): larger api-surface, just like request
Browse files Browse the repository at this point in the history
  • Loading branch information
FGRibreau committed Sep 3, 2016
1 parent b1fbef5 commit 1527f9d
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 43 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
npm-debug.log
.env
coverage
35 changes: 22 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Request-retry
[![Deps]( https://img.shields.io/david/FGRibreau/node-request-retry.svg)](https://david-dm.org/FGRibreau/node-request-retry) [![Build Status]( https://img.shields.io/circleci/project/FGRibreau/node-request-retry.svg)](https://drone.io/github.com/FGRibreau/node-request-retry/latest) [![Downloads](http://img.shields.io/npm/dm/requestretry.svg)](https://www.npmjs.com/package/requestretry) ![extra](https://img.shields.io/badge/actively%20maintained-yes-ff69b4.svg)
<div align="center">
<br><p><strong>request-retry</strong> - HTTP(s) request retry on recoverable errors.</p>
</div>

=========================================

[![Build Status](https://img.shields.io/circleci/project/FGRibreau/node-request-retry.svg)](https://circleci.com/gh/FGRibreau/node-request-retry/) [![Coverage Status](https://img.shields.io/coveralls/FGRibreau/node-request-retry/master.svg)](https://coveralls.io/github/FGRibreau/node-request-retry?branch=master) [![Deps]( https://img.shields.io/david/FGRibreau/node-request-retry.svg)](https://david-dm.org/FGRibreau/node-request-retry) [![NPM version](https://img.shields.io/npm/v/requestretry.svg)](http://badge.fury.io/js/requestretry) [![Downloads](http://img.shields.io/npm/dm/requestretry.svg)](https://www.npmjs.com/package/requestretry) ![extra](https://img.shields.io/badge/actively%20maintained-yes-ff69b4.svg)

![NPM](https://nodei.co/npm/requestretry.png?downloadRank=true) ![NPM](https://nodei.co/npm-dl/requestretry.png?months=3&height=2)

Expand Down Expand Up @@ -121,17 +126,21 @@ You can use the `defaults` method to provide default options like so:
var request = require('requestretry').defaults({ json: true, retryStrategy: myRetryStrategy });
```

## Convenience methods

As with `request`, several helpers are provided for various HTTP methods:

* `request.get(url)` - same as `request(options, callback)` or `request(options)`.
* `request.head(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `HEAD`.
* `request.post(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `POST`.
* `request.put(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `PUT`.
* `request.patch(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `PATCH`.
* `request.del(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `DELETE`.
* `request.delete(url)` - same as `request(options, callback)` or `request(options)`, but it defaults `options.method` to `DELETE`.
## API surface

As with `request`, several helpers are provided for various HTTP methods and usage:

* `request(options [, callback])`.
* `request(url [, callback])` - same as `request(options [, callback])`.
* `request(url, options [, callback])` - same as `request(options [, callback])`.
* `request.get(url [, callback])` - same as `request(options [, callback])`, defaults `options.method` to `GET`.
* `request.get(url, options [, callback])` - same as `request(options [, callback])`, defaults `options.method` to `GET`.
* `request.head(url)` - same as `request(options [, callback])`, defaults `options.method` to `HEAD`.
* `request.post(url)` - same as `request(options [, callback])`, defaults `options.method` to `POST`.
* `request.put(url)` - same as `request(options [, callback])`, defaults `options.method` to `PUT`.
* `request.patch(url)` - same as `request(options [, callback])`, defaults `options.method` to `PATCH`.
* `request.del(url)` - same as `request(options [, callback])`, defaults `options.method` to `DELETE`.
* `request.delete(url)` - same as `request(options [, callback])`, defaults `options.method` to `DELETE`.

## [Changelog](CHANGELOG.md)

Expand Down
10 changes: 10 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
machine:
node:
version: 6.3
environment:
COVERALLS_SERVICE_NAME: circleci

test:
override:
- npm run test
- npm run send-coverage
64 changes: 50 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
var extend = require('extend');
var when = require('when');
var request = require('request');
var _ = require('fg-lodash');
var RetryStrategies = require('./strategies');

var _ = require('lodash');

var DEFAULTS = {
maxAttempts: 5, // try 5 times
Expand Down Expand Up @@ -45,7 +44,29 @@ function makePromise(requestInstance, promiseFactoryFn) {
return promiseFactoryFn(Resolver.bind(requestInstance));
}

function Request(options, f, retryConfig) {
function Request(url, options, f, retryConfig) {
// ('url')
if(_.isString(url)){
// ('url', f)
if(_.isFunction(options)){
f = options;
}

if(!_.isObject(options)){
options = {};
}

// ('url', {object})
options.url = url;
}

if(_.isObject(url)){
if(_.isFunction(options)){
f = options;
}
options = url;
}

this.maxAttempts = retryConfig.maxAttempts;
this.retryDelay = retryConfig.retryDelay;
this.fullResponse = retryConfig.fullResponse;
Expand Down Expand Up @@ -132,24 +153,39 @@ Request.prototype.abort = function () {
};
});

function Factory(options, f) {
var retryConfig = _(options || {}).defaults(DEFAULTS).pick(Object.keys(DEFAULTS)).value();
var req = new Request(options, f, retryConfig);
function Factory(url, options, f) {
var retryConfig = _.chain(_.isObject(url) ? url : options || {}).defaults(DEFAULTS).pick(Object.keys(DEFAULTS)).value();
var req = new Request(url, options, f, retryConfig);
req._tryUntilFail();
return req;
}

// adds a helper for HTTP method `verb` to object `obj`
function makeHelper(obj, verb) {
obj[verb] = function helper(options, f) {
if (typeof options === 'object') {
options.method = verb.toUpperCase();
} else if (typeof options === 'string') {
options = {
method: verb.toUpperCase(),
uri: options
};
obj[verb] = function helper(url, options, f) {
// ('url')
if(_.isString(url)){
// ('url', f)
if(_.isFunction(options)){
f = options;
}

if(!_.isObject(options)){
options = {};
}

// ('url', {object})
options.url = url;
}

if(_.isObject(url)){
if(_.isFunction(options)){
f = options;
}
options = url;
}

options.method = verb.toUpperCase();
return obj(options, f);
};
}
Expand Down
21 changes: 17 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,40 @@
},
"main": "index.js",
"scripts": {
"test": "mocha test",
"test": "mocha -t 1000 -R spec $(find test -name '*.test.js')",
"test-watch": "mocha -t 100000 -R min -w $(find test -name '*.test.js')",
"test-coverage": "nyc --all --statements=100 --lines=100 --functions=100 --branches=100 --check-coverage --reporter=lcov --reporter=cobertura --report-dir=coverage -- mocha -R spec -t 100000 $(find test -name '*.test.js')",
"send-coverage": "cat ./coverage/lcov.info | coveralls",
"update": "updtr",
"changelog-init": "conventional-changelog -i CHANGELOG.md -s -r 0",
"changelog": "conventional-changelog -i CHANGELOG.md -s",
"changelog-git": "npm run changelog && git add CHANGELOG.md && git commit -m 'docs(changelog): updated' && git push origin master"
},
"license": "MIT",
"nyc": {
"exclude": [
"node_modules",
"dist",
"coverage",
"webpack.config.js",
"test"
]
},
"dependencies": {
"extend": "^3.0.0",
"fg-lodash": "0.0.2",
"lodash": "^4.15.0",
"request": "^2.74.0",
"when": "~3.7.5"
"when": "^3.7.7"
},
"devDependencies": {
"bluebird": "^3.4.1",
"chai": "^3.2.0",
"conventional-changelog": "^1.1.0",
"conventional-changelog-cli": "^1.2.0",
"coveralls": "^2.11.12",
"kew": "~0.7.0",
"mocha": "^3.0.2",
"nock": "^8.0.0",
"nyc": "^8.1.0",
"q": "~1.4.1",
"rsvp": "^3.2.1",
"updtr": "^0.2.1"
Expand Down
4 changes: 2 additions & 2 deletions strategies/NetworkError.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
'use strict';

var RETRIABLE_ERRORS = ['ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE', 'EAI_AGAIN'];
var _ = require('fg-lodash');
var _ = require('lodash');

/**
* @param {Null | Object} err
* @param {Object} response
* @return {Boolean} true if the request had a network error
*/
function NetworkError(err /*, response*/ ) {
return err && _.contains(RETRIABLE_ERRORS, err.code);
return err && _.includes(RETRIABLE_ERRORS, err.code);
}

NetworkError.RETRIABLE_ERRORS = RETRIABLE_ERRORS;
Expand Down
76 changes: 76 additions & 0 deletions test/api-surface.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use strict';

var request = require('../');
var t = require('chai').assert;

describe('API surface', function () {

describe('callback api', function(){
[['request', request], ['request.get', request.get]].forEach(function(pair){
it('should work with '+pair[0]+'(url, f)', function (done) {
pair[1]('http://www.filltext.com/?rows=1', function (err, response, body) {
t.strictEqual(response.statusCode, 200);
t.strictEqual(response.body, '[{}]');
done();
});
});

it('should work with '+pair[0]+'(url, object, f)', function (done) {
pair[1]('http://www.filltext.com/?rows=1', {
json:true,
}, function (err, response, body) {
t.strictEqual(response.statusCode, 200);
t.deepEqual(response.body, [{}]);
done();
});
});

it('should work with '+pair[0]+'(object, f)', function (done) {
pair[1]({
url: 'http://www.filltext.com/?rows=1',
json:true
}, function (err, response, body) {
t.strictEqual(response.statusCode, 200);
t.deepEqual(response.body, [{}]);
done();
});
});
});
});

describe('promise api', function(){
[['request', request], ['request.get', request.get]].forEach(function(pair){
it('should work with '+pair[0]+'(url)', function (done) {
pair[1]('http://www.filltext.com/?rows=1')
.then(function (response) {
t.strictEqual(response.statusCode, 200);
t.strictEqual(response.body, '[{}]');
done();
});
});

it('should work with request(url, object)', function (done) {
pair[1]('http://www.filltext.com/?rows=1', {
json:true,
})
.then(function (response) {
t.strictEqual(response.statusCode, 200);
t.deepEqual(response.body, [{}]);
done();
});
});

it('should work with '+pair[0]+'(object)', function (done) {
pair[1]({
url: 'http://www.filltext.com/?rows=1',
json:true
})
.then(function (response) {
t.strictEqual(response.statusCode, 200);
t.deepEqual(response.body, [{}]);
done();
});
});
});
});
});
13 changes: 3 additions & 10 deletions test/promises.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

var request = require('../');
var t = require('chai').assert;
var nock = require('nock');

describe('Promises support', function () {

Expand Down Expand Up @@ -60,24 +59,18 @@ describe('Promises support', function () {
});

it('should reject the response on any error', function (done) {
nock('http://www.filltext.com')
.get('/')
.query({rows: 1})
.replyWithError('Some error');

request({
url: 'http://www.filltext.com/?rows=1', // return 1 row of data
url: 'http://localhost:1', // return 1 row of data
maxAttempts: 1,
retryStrategy: request.RetryStrategies.HTTPOrNetworkError
})
.catch(function (err) {
t.strictEqual(err.message, 'Some error');
t.strictEqual(err.message, 'connect ECONNREFUSED 127.0.0.1:1');
done();
});
});

it('should still work with callbacks', function (done) {
nock.restore(); // Allow next requests

request({url: 'http://www.filltext.com/?rows=1'}, function requestCallback(err, response, body) {
t.strictEqual(response.statusCode, 200);
t.strictEqual(response.attempts, 1);
Expand Down

0 comments on commit 1527f9d

Please sign in to comment.