From fa9444e0babdbf87cc6e04eb72da420c0f2ffbc5 Mon Sep 17 00:00:00 2001 From: Nick Uraltsev Date: Tue, 26 Apr 2016 13:18:58 -0700 Subject: [PATCH] Allow custom HTTP status code error ranges (#308) Adding support for custom HTTP status code error ranges --- README.md | 22 ++++++++- lib/adapters/http.js | 8 +--- lib/adapters/xhr.js | 7 +-- lib/defaults.js | 6 ++- lib/helpers/settle.js | 18 ++++++++ test/specs/helpers/settle.spec.js | 75 +++++++++++++++++++++++++++++++ test/specs/requests.spec.js | 50 ++++++++++++++++++++- 7 files changed, 169 insertions(+), 17 deletions(-) create mode 100644 lib/helpers/settle.js create mode 100644 test/specs/helpers/settle.spec.js diff --git a/README.md b/README.md index 1a72061..d727500 100644 --- a/README.md +++ b/README.md @@ -261,12 +261,20 @@ These are the available config options for making requests. Only the `url` is re // `progress` allows handling of progress events for 'POST' and 'PUT uploads' // as well as 'GET' downloads - progress: function(progressEvent) { + progress: function (progressEvent) { // Do whatever you want with the native progress event }, // `maxContentLength` defines the max size of the http response content allowed - maxContentLength: 2000 + maxContentLength: 2000, + + // `validateStatus` defines whether to resolve or reject the promise for a given + // HTTP response status code. If `validateStatus` returns `true` (or is set to `null` + // or `undefined`), the promise will be resolved; otherwise, the promise will be + // rejected. + validateStatus: function (status) { + return status >= 200 && status < 300; // default + } } ``` @@ -406,6 +414,16 @@ axios.get('/user/12345') }); ``` +You can define a custom HTTP status code error range using the `validateStatus` config option. + +```js +axios.get('/user/12345', { + validateStatus: function (status) { + return status < 500; // Reject only if the status code is greater than or equal to 500 + } +}) +``` + ## Semver Until axios reaches a `1.0` release, breaking changes will be released with a new minor version. For example `0.5.1`, and `0.5.4` will have the same API, but `0.6.0` will have breaking changes. diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 84be87b..f5e40ed 100644 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -9,13 +9,7 @@ var url = require('url'); var zlib = require('zlib'); var pkg = require('./../../package.json'); var Buffer = require('buffer').Buffer; - -// Resolve or reject the Promise based on the status -function settle(resolve, reject, response) { - (response.status >= 200 && response.status < 300 ? - resolve : - reject)(response); -} +var settle = require('../helpers/settle'); /*eslint consistent-return:0*/ module.exports = function httpAdapter(resolve, reject, config) { diff --git a/lib/adapters/xhr.js b/lib/adapters/xhr.js index 86bb087..e967a85 100644 --- a/lib/adapters/xhr.js +++ b/lib/adapters/xhr.js @@ -6,6 +6,7 @@ var parseHeaders = require('./../helpers/parseHeaders'); var transformData = require('./../helpers/transformData'); var isURLSameOrigin = require('./../helpers/isURLSameOrigin'); var btoa = (typeof window !== 'undefined' && window.btoa) || require('./../helpers/btoa'); +var settle = require('../helpers/settle'); module.exports = function xhrAdapter(resolve, reject, config) { var requestData = config.data; @@ -73,11 +74,7 @@ module.exports = function xhrAdapter(resolve, reject, config) { request: request }; - // Resolve or reject the Promise based on the status - ((response.status >= 200 && response.status < 300) || - (!('status' in request) && request.responseText) ? - resolve : - reject)(response); + settle(resolve, reject, response); // Clean up request request = null; diff --git a/lib/defaults.js b/lib/defaults.js index 6bbfcbf..1942ce0 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -58,5 +58,9 @@ module.exports = { xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', - maxContentLength: -1 + maxContentLength: -1, + + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300; + } }; diff --git a/lib/helpers/settle.js b/lib/helpers/settle.js new file mode 100644 index 0000000..35027f5 --- /dev/null +++ b/lib/helpers/settle.js @@ -0,0 +1,18 @@ +'use strict'; + +/** + * Resolve or reject a Promise based on response status. + * + * @param {Function} resolve A function that resolves the promise. + * @param {Function} reject A function that rejects the promise. + * @param {object} response The response. + */ +module.exports = function settle(resolve, reject, response) { + var validateStatus = response.config.validateStatus; + // Note: status is not exposed by XDomainRequest + if (!response.status || !validateStatus || validateStatus(response.status)) { + resolve(response); + } else { + reject(response); + } +}; diff --git a/test/specs/helpers/settle.spec.js b/test/specs/helpers/settle.spec.js new file mode 100644 index 0000000..b89d12a --- /dev/null +++ b/test/specs/helpers/settle.spec.js @@ -0,0 +1,75 @@ +var settle = require('../../../lib/helpers/settle'); + +describe('helpers::settle', function() { + var resolve; + var reject; + + beforeEach(function() { + resolve = jasmine.createSpy('resolve'); + reject = jasmine.createSpy('reject'); + }); + + it('should resolve promise if status is not set', function() { + var response = { + config: { + validateStatus: function() { + return true; + } + } + }; + settle(resolve, reject, response); + expect(resolve).toHaveBeenCalledWith(response); + expect(reject).not.toHaveBeenCalled(); + }); + + it('should resolve promise if validateStatus is not set', function() { + var response = { + status: 500, + config: { + } + }; + settle(resolve, reject, response); + expect(resolve).toHaveBeenCalledWith(response); + expect(reject).not.toHaveBeenCalled(); + }); + + it('should resolve promise if validateStatus returns true', function() { + var response = { + status: 500, + config: { + validateStatus: function() { + return true; + } + } + }; + settle(resolve, reject, response); + expect(resolve).toHaveBeenCalledWith(response); + expect(reject).not.toHaveBeenCalled(); + }); + + it('should reject promise if validateStatus returns false', function() { + var response = { + status: 500, + config: { + validateStatus: function() { + return false; + } + } + }; + settle(resolve, reject, response); + expect(resolve).not.toHaveBeenCalled(); + expect(reject).toHaveBeenCalledWith(response); + }); + + it('should pass status to validateStatus', function() { + var validateStatus = jasmine.createSpy('validateStatus'); + var response = { + status: 500, + config: { + validateStatus: validateStatus + } + }; + settle(resolve, reject, response); + expect(validateStatus).toHaveBeenCalledWith(500); + }); +}); diff --git a/test/specs/requests.spec.js b/test/specs/requests.spec.js index 28da0c6..03f50de 100644 --- a/test/specs/requests.spec.js +++ b/test/specs/requests.spec.js @@ -55,6 +55,52 @@ describe('requests', function () { .then(finish, finish); }); + it('should reject when validateStatus returns false', function (done) { + var resolveSpy = jasmine.createSpy('resolve'); + var rejectSpy = jasmine.createSpy('reject'); + + axios('/foo', { + validateStatus: function (status) { + return status !== 500; + } + }).then(resolveSpy) + .catch(rejectSpy) + .then(function () { + expect(resolveSpy).not.toHaveBeenCalled(); + expect(rejectSpy).toHaveBeenCalled(); + done(); + }); + + getAjaxRequest().then(function (request) { + request.respondWith({ + status: 500 + }); + }); + }); + + it('should resolve when validateStatus returns true', function (done) { + var resolveSpy = jasmine.createSpy('resolve'); + var rejectSpy = jasmine.createSpy('reject'); + + axios('/foo', { + validateStatus: function (status) { + return status === 500; + } + }).then(resolveSpy) + .catch(rejectSpy) + .then(function () { + expect(resolveSpy).toHaveBeenCalled(); + expect(rejectSpy).not.toHaveBeenCalled(); + done(); + }); + + getAjaxRequest().then(function (request) { + request.respondWith({ + status: 500 + }); + }); + }); + it('should make cross domian http request', function (done) { var response; @@ -71,7 +117,7 @@ describe('requests', function () { 'Content-Type': 'application/json' } }); - + setTimeout(function () { expect(response.data.foo).toEqual('bar'); expect(response.status).toEqual(200); @@ -200,7 +246,7 @@ describe('requests', function () { done(); return; } - + var response; function str2ab(str) {