From 7f1236652adb813ff884be008fe73ddf0590c664 Mon Sep 17 00:00:00 2001 From: DigitalBrainJS Date: Thu, 14 Oct 2021 18:53:46 +0300 Subject: [PATCH] Refactored AxiosError to a constructor; Refactored `Cancel` to a constructor, a subclass of the `AxiosError`; Expose CanceledError class; Refactored axios error codes; Added `toFlatObject` util; --- index.d.ts | 23 ++++++- lib/adapters/http.js | 30 ++++----- lib/adapters/xhr.js | 14 ++-- lib/axios.js | 8 ++- lib/cancel/Cancel.js | 19 ------ lib/cancel/CancelToken.js | 6 +- lib/cancel/CanceledError.js | 22 +++++++ lib/core/AxiosError.js | 86 +++++++++++++++++++++++++ lib/core/createError.js | 18 ------ lib/core/dispatchRequest.js | 6 +- lib/core/enhanceError.js | 43 ------------- lib/core/settle.js | 6 +- lib/defaults.js | 4 +- lib/helpers/validator.js | 12 ++-- lib/utils.js | 50 +++++++++++++- test/specs/api.spec.js | 2 +- test/specs/cancel.spec.js | 4 +- test/specs/cancel/Cancel.spec.js | 15 ----- test/specs/cancel/CancelToken.spec.js | 16 ++--- test/specs/cancel/CanceledError.spec.js | 15 +++++ test/specs/cancel/isCancel.spec.js | 8 +-- test/specs/core/AxiosError.spec.js | 50 ++++++++++++++ test/specs/core/createError.spec.js | 30 --------- test/specs/core/enhanceError.spec.js | 31 --------- test/specs/helpers/isAxiosError.spec.js | 15 ++--- test/specs/instance.spec.js | 2 + test/specs/transform.spec.js | 3 + test/specs/utils/toFlatObject.js | 10 +++ test/unit/adapters/http.js | 5 +- 29 files changed, 332 insertions(+), 221 deletions(-) delete mode 100644 lib/cancel/Cancel.js create mode 100644 lib/cancel/CanceledError.js create mode 100644 lib/core/AxiosError.js delete mode 100644 lib/core/createError.js delete mode 100644 test/specs/cancel/Cancel.spec.js create mode 100644 test/specs/cancel/CanceledError.spec.js create mode 100644 test/specs/core/AxiosError.spec.js delete mode 100644 test/specs/core/createError.spec.js create mode 100644 test/specs/utils/toFlatObject.js diff --git a/index.d.ts b/index.d.ts index 4255a7295c..e61bfb73be 100644 --- a/index.d.ts +++ b/index.d.ts @@ -121,13 +121,34 @@ export interface AxiosResponse { request?: any; } -export interface AxiosError extends Error { +export class AxiosError extends Error { + constructor( + message?: string, + code?: string, + config?: AxiosRequestConfig, + request?: any, + response?: AxiosResponse + ); config: AxiosRequestConfig; code?: string; request?: any; response?: AxiosResponse; isAxiosError: boolean; + status?: string; toJSON: () => object; + static readonly ERR_FR_TOO_MANY_REDIRECTS = "ERR_FR_TOO_MANY_REDIRECTS"; + static readonly ERR_BAD_OPTION_VALUE = "ERR_BAD_OPTION_VALUE"; + static readonly ERR_BAD_OPTION = "ERR_BAD_OPTION"; + static readonly ERR_NETWORK = "ERR_NETWORK"; + static readonly ERR_DEPRECATED = "ERR_DEPRECATED"; + static readonly ERR_BAD_RESPONSE = "ERR_BAD_RESPONSE"; + static readonly ERR_BAD_REQUEST = "ERR_BAD_REQUEST"; + static readonly ERR_CANCELED = "ERR_CANCELED"; + static readonly ECONNABORTED = "ECONNABORTED"; + static readonly ETIMEDOUT = "ETIMEDOUT"; +} + +export class CanceledError extends AxiosError { } export interface AxiosPromise extends Promise> { diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 85f3061027..351076e183 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -11,10 +11,9 @@ var httpsFollow = require('follow-redirects').https; var url = require('url'); var zlib = require('zlib'); var VERSION = require('./../env/data').version; -var createError = require('../core/createError'); -var enhanceError = require('../core/enhanceError'); var defaults = require('../defaults'); -var Cancel = require('../cancel/Cancel'); +var AxiosError = require('../core/AxiosError'); +var CanceledError = require('../cancel/CanceledError'); var isHttps = /https:?/; @@ -93,8 +92,9 @@ module.exports = function httpAdapter(config) { } else if (utils.isString(data)) { data = Buffer.from(data, 'utf-8'); } else { - return reject(createError( + return reject(new AxiosError( 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream', + AxiosError.ERR_BAD_REQUEST, config )); } @@ -270,14 +270,14 @@ module.exports = function httpAdapter(config) { // make sure the content length is not over the maxContentLength if specified if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) { stream.destroy(); - reject(createError('maxContentLength size of ' + config.maxContentLength + ' exceeded', - config, null, lastRequest)); + reject(new AxiosError('maxContentLength size of ' + config.maxContentLength + ' exceeded', + AxiosError.ERR_BAD_RESPONSE, config, lastRequest)); } }); stream.on('error', function handleStreamError(err) { if (req.aborted) return; - reject(enhanceError(err, config, null, lastRequest)); + reject(AxiosError.from(err, null, config, lastRequest)); }); stream.on('end', function handleStreamEnd() { @@ -297,8 +297,8 @@ module.exports = function httpAdapter(config) { // Handle errors req.on('error', function handleRequestError(err) { - if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return; - reject(enhanceError(err, config, null, req)); + if (req.aborted && err.code !== AxiosError.ERR_FR_TOO_MANY_REDIRECTS) return; + reject(AxiosError.from(err, null, config, req)); }); // Handle request timeout @@ -307,10 +307,10 @@ module.exports = function httpAdapter(config) { var timeout = parseInt(config.timeout, 10); if (isNaN(timeout)) { - reject(createError( + reject(new AxiosError( 'error trying to parse `config.timeout` to int', + AxiosError.ERR_BAD_OPTION_VALUE, config, - 'ERR_PARSE_TIMEOUT', req )); @@ -325,10 +325,10 @@ module.exports = function httpAdapter(config) { req.setTimeout(timeout, function handleRequestTimeout() { req.abort(); var transitional = config.transitional || defaults.transitional; - reject(createError( + reject(new AxiosError( 'timeout of ' + timeout + 'ms exceeded', + transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, - transitional.clarifyTimeoutError ? 'ETIMEDOUT' : 'ECONNABORTED', req )); }); @@ -341,7 +341,7 @@ module.exports = function httpAdapter(config) { if (req.aborted) return; req.abort(); - reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel); + reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel); }; config.cancelToken && config.cancelToken.subscribe(onCanceled); @@ -354,7 +354,7 @@ module.exports = function httpAdapter(config) { // Send the request if (utils.isStream(data)) { data.on('error', function handleStreamError(err) { - reject(enhanceError(err, config, null, req)); + reject(AxiosError.from(err, config, null, req)); }).pipe(req); } else { req.end(data); diff --git a/lib/adapters/xhr.js b/lib/adapters/xhr.js index 77246f8cb5..cb64fba92a 100644 --- a/lib/adapters/xhr.js +++ b/lib/adapters/xhr.js @@ -7,9 +7,9 @@ var buildURL = require('./../helpers/buildURL'); var buildFullPath = require('../core/buildFullPath'); var parseHeaders = require('./../helpers/parseHeaders'); var isURLSameOrigin = require('./../helpers/isURLSameOrigin'); -var createError = require('../core/createError'); var defaults = require('../defaults'); -var Cancel = require('../cancel/Cancel'); +var AxiosError = require('../core/AxiosError'); +var CanceledError = require('../cancel/CanceledError'); module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { @@ -104,7 +104,7 @@ module.exports = function xhrAdapter(config) { return; } - reject(createError('Request aborted', config, 'ECONNABORTED', request)); + reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request)); // Clean up request request = null; @@ -114,7 +114,7 @@ module.exports = function xhrAdapter(config) { request.onerror = function handleError() { // Real errors are hidden from us by the browser // onerror should only fire if it's a network error - reject(createError('Network Error', config, null, request)); + reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config, request, request)); // Clean up request request = null; @@ -127,10 +127,10 @@ module.exports = function xhrAdapter(config) { if (config.timeoutErrorMessage) { timeoutErrorMessage = config.timeoutErrorMessage; } - reject(createError( + reject(new AxiosError( timeoutErrorMessage, + transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED, config, - transitional.clarifyTimeoutError ? 'ETIMEDOUT' : 'ECONNABORTED', request)); // Clean up request @@ -191,7 +191,7 @@ module.exports = function xhrAdapter(config) { if (!request) { return; } - reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel); + reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel); request.abort(); request = null; }; diff --git a/lib/axios.js b/lib/axios.js index c032327257..30b48093d9 100644 --- a/lib/axios.js +++ b/lib/axios.js @@ -37,11 +37,17 @@ var axios = createInstance(defaults); axios.Axios = Axios; // Expose Cancel & CancelToken -axios.Cancel = require('./cancel/Cancel'); +axios.CanceledError = require('./cancel/CanceledError'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); axios.VERSION = require('./env/data').version; +// Expose AxiosError class +axios.AxiosError = require('../lib/core/AxiosError'); + +// alias for CanceledError for backward compatibility +axios.Cancel = axios.CanceledError; + // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); diff --git a/lib/cancel/Cancel.js b/lib/cancel/Cancel.js deleted file mode 100644 index e0de4003f9..0000000000 --- a/lib/cancel/Cancel.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -/** - * A `Cancel` is an object that is thrown when an operation is canceled. - * - * @class - * @param {string=} message The message. - */ -function Cancel(message) { - this.message = message; -} - -Cancel.prototype.toString = function toString() { - return 'Cancel' + (this.message ? ': ' + this.message : ''); -}; - -Cancel.prototype.__CANCEL__ = true; - -module.exports = Cancel; diff --git a/lib/cancel/CancelToken.js b/lib/cancel/CancelToken.js index 089d6b9034..ee7989f918 100644 --- a/lib/cancel/CancelToken.js +++ b/lib/cancel/CancelToken.js @@ -1,6 +1,6 @@ 'use strict'; -var Cancel = require('./Cancel'); +var CanceledError = require('./CanceledError'); /** * A `CancelToken` is an object that can be used to request cancellation of an operation. @@ -56,13 +56,13 @@ function CancelToken(executor) { return; } - token.reason = new Cancel(message); + token.reason = new CanceledError(message); resolvePromise(token.reason); }); } /** - * Throws a `Cancel` if cancellation has been requested. + * Throws a `CanceledError` if cancellation has been requested. */ CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { diff --git a/lib/cancel/CanceledError.js b/lib/cancel/CanceledError.js new file mode 100644 index 0000000000..b7ceb02567 --- /dev/null +++ b/lib/cancel/CanceledError.js @@ -0,0 +1,22 @@ +'use strict'; + +var AxiosError = require('../core/AxiosError'); +var utils = require('../utils'); + +/** + * A `CanceledError` is an object that is thrown when an operation is canceled. + * + * @class + * @param {string=} message The message. + */ +function CanceledError(message) { + // eslint-disable-next-line no-eq-null,eqeqeq + AxiosError.call(this, message == null ? 'canceled' : message, AxiosError.ERR_CANCELED); + this.name = 'CanceledError'; +} + +utils.inherits(CanceledError, AxiosError, { + __CANCEL__: true +}); + +module.exports = CanceledError; diff --git a/lib/core/AxiosError.js b/lib/core/AxiosError.js new file mode 100644 index 0000000000..52c806a817 --- /dev/null +++ b/lib/core/AxiosError.js @@ -0,0 +1,86 @@ +'use strict'; + +var utils = require('../utils'); + +/** + * Create an Error with the specified message, config, error code, request and response. + * + * @param {string} message The error message. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [config] The config. + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The created error. + */ +function AxiosError(message, code, config, request, response) { + Error.call(this); + this.message = message; + this.name = 'AxiosError'; + code && (this.code = code); + config && (this.config = config); + request && (this.request = request); + response && (this.response = response); +} + +utils.inherits(AxiosError, Error, { + toJSON: function toJSON() { + return { + // Standard + message: this.message, + name: this.name, + // Microsoft + description: this.description, + number: this.number, + // Mozilla + fileName: this.fileName, + lineNumber: this.lineNumber, + columnNumber: this.columnNumber, + stack: this.stack, + // Axios + config: this.config, + code: this.code, + status: this.response && this.response.status ? this.response.status : null + }; + } +}); + +var prototype = AxiosError.prototype; +var descriptors = {}; + +[ + 'ERR_BAD_OPTION_VALUE', + 'ERR_BAD_OPTION', + 'ECONNABORTED', + 'ETIMEDOUT', + 'ERR_NETWORK', + 'ERR_FR_TOO_MANY_REDIRECTS', + 'ERR_DEPRECATED', + 'ERR_BAD_RESPONSE', + 'ERR_BAD_REQUEST', + 'ERR_CANCELED' +// eslint-disable-next-line func-names +].forEach(function(code) { + descriptors[code] = {value: code}; +}); + +Object.defineProperties(AxiosError, descriptors); +Object.defineProperty(prototype, 'isAxiosError', {value: true}); + +// eslint-disable-next-line func-names +AxiosError.from = function(error, code, config, request, response, customProps) { + var axiosError = Object.create(prototype); + + utils.toFlatObject(error, axiosError, function filter(obj) { + return obj !== Error.prototype; + }); + + AxiosError.call(axiosError, error.message, code, config, request, response); + + axiosError.name = error.name; + + customProps && Object.assign(axiosError, customProps); + + return axiosError; +}; + +module.exports = AxiosError; diff --git a/lib/core/createError.js b/lib/core/createError.js deleted file mode 100644 index 933680f694..0000000000 --- a/lib/core/createError.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -var enhanceError = require('./enhanceError'); - -/** - * Create an Error with the specified message, config, error code, request and response. - * - * @param {string} message The error message. - * @param {Object} config The config. - * @param {string} [code] The error code (for example, 'ECONNABORTED'). - * @param {Object} [request] The request. - * @param {Object} [response] The response. - * @returns {Error} The created error. - */ -module.exports = function createError(message, config, code, request, response) { - var error = new Error(message); - return enhanceError(error, config, code, request, response); -}; diff --git a/lib/core/dispatchRequest.js b/lib/core/dispatchRequest.js index 36da48bec7..fa1ad95765 100644 --- a/lib/core/dispatchRequest.js +++ b/lib/core/dispatchRequest.js @@ -4,10 +4,10 @@ var utils = require('./../utils'); var transformData = require('./transformData'); var isCancel = require('../cancel/isCancel'); var defaults = require('../defaults'); -var Cancel = require('../cancel/Cancel'); +var CanceledError = require('../cancel/CanceledError'); /** - * Throws a `Cancel` if cancellation has been requested. + * Throws a `CanceledError` if cancellation has been requested. */ function throwIfCancellationRequested(config) { if (config.cancelToken) { @@ -15,7 +15,7 @@ function throwIfCancellationRequested(config) { } if (config.signal && config.signal.aborted) { - throw new Cancel('canceled'); + throw new CanceledError(); } } diff --git a/lib/core/enhanceError.js b/lib/core/enhanceError.js index db04ec8ea0..e69de29bb2 100644 --- a/lib/core/enhanceError.js +++ b/lib/core/enhanceError.js @@ -1,43 +0,0 @@ -'use strict'; - -/** - * Update an Error with the specified config, error code, and response. - * - * @param {Error} error The error to update. - * @param {Object} config The config. - * @param {string} [code] The error code (for example, 'ECONNABORTED'). - * @param {Object} [request] The request. - * @param {Object} [response] The response. - * @returns {Error} The error. - */ -module.exports = function enhanceError(error, config, code, request, response) { - error.config = config; - if (code) { - error.code = code; - } - - error.request = request; - error.response = response; - error.isAxiosError = true; - - error.toJSON = function toJSON() { - return { - // Standard - message: this.message, - name: this.name, - // Microsoft - description: this.description, - number: this.number, - // Mozilla - fileName: this.fileName, - lineNumber: this.lineNumber, - columnNumber: this.columnNumber, - stack: this.stack, - // Axios - config: this.config, - code: this.code, - status: this.response && this.response.status ? this.response.status : null - }; - }; - return error; -}; diff --git a/lib/core/settle.js b/lib/core/settle.js index 886adb0c1f..bbb9afa8cb 100644 --- a/lib/core/settle.js +++ b/lib/core/settle.js @@ -1,6 +1,6 @@ 'use strict'; -var createError = require('./createError'); +var AxiosError = require('./AxiosError'); /** * Resolve or reject a Promise based on response status. @@ -14,10 +14,10 @@ module.exports = function settle(resolve, reject, response) { if (!response.status || !validateStatus || validateStatus(response.status)) { resolve(response); } else { - reject(createError( + reject(new AxiosError( 'Request failed with status code ' + response.status, + [AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4], response.config, - null, response.request, response )); diff --git a/lib/defaults.js b/lib/defaults.js index eaee1898a0..33877e57ce 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -2,7 +2,7 @@ var utils = require('./utils'); var normalizeHeaderName = require('./helpers/normalizeHeaderName'); -var enhanceError = require('./core/enhanceError'); +var AxiosError = require('./core/AxiosError'); var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' @@ -90,7 +90,7 @@ var defaults = { } catch (e) { if (strictJSONParsing) { if (e.name === 'SyntaxError') { - throw enhanceError(e, this, 'E_JSON_PARSE'); + throw AxiosError.from(e, AxiosError.ERR_BAD_RESPONSE, this, null, this.response); } throw e; } diff --git a/lib/helpers/validator.js b/lib/helpers/validator.js index a4ec4133e0..8095b90e10 100644 --- a/lib/helpers/validator.js +++ b/lib/helpers/validator.js @@ -1,6 +1,7 @@ 'use strict'; var VERSION = require('../env/data').version; +var AxiosError = require('../core/AxiosError'); var validators = {}; @@ -28,7 +29,10 @@ validators.transitional = function transitional(validator, version, message) { // eslint-disable-next-line func-names return function(value, opt, opts) { if (validator === false) { - throw new Error(formatMessage(opt, ' has been removed' + (version ? ' in ' + version : ''))); + throw new AxiosError( + formatMessage(opt, ' has been removed' + (version ? ' in ' + version : '')), + AxiosError.ERR_DEPRECATED + ); } if (version && !deprecatedWarnings[opt]) { @@ -55,7 +59,7 @@ validators.transitional = function transitional(validator, version, message) { function assertOptions(options, schema, allowUnknown) { if (typeof options !== 'object') { - throw new TypeError('options must be an object'); + throw new AxiosError('options must be an object', AxiosError.ERR_BAD_OPTION_VALUE); } var keys = Object.keys(options); var i = keys.length; @@ -66,12 +70,12 @@ function assertOptions(options, schema, allowUnknown) { var value = options[opt]; var result = value === undefined || validator(value, opt, options); if (result !== true) { - throw new TypeError('option ' + opt + ' must be ' + result); + throw new AxiosError('option ' + opt + ' must be ' + result, AxiosError.ERR_BAD_OPTION_VALUE); } continue; } if (allowUnknown !== true) { - throw Error('Unknown option ' + opt); + throw new AxiosError('Unknown option ' + opt, AxiosError.ERR_BAD_OPTION); } } } diff --git a/lib/utils.js b/lib/utils.js index 5d966f4448..140a37e4a7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -323,6 +323,52 @@ function stripBOM(content) { return content; } +/** + * Inherit the prototype methods from one constructor into another + * @param {function} constructor + * @param {function} superConstructor + * @param {object} [props] + * @param {object} [descriptors] + */ + +function inherits(constructor, superConstructor, props, descriptors) { + constructor.prototype = Object.create(superConstructor.prototype, descriptors); + constructor.prototype.constructor = constructor; + props && Object.assign(constructor.prototype, props); +} + +/** + * Resolve object with deep prototype chain to a flat object + * @param {Object} sourceObj source object + * @param {Object} [destObj] + * @param {Function} [filter] + * @returns {Object} + */ + +function toFlatObject(sourceObj, destObj, filter) { + var props; + var i; + var prop; + var merged = {}; + + destObj = destObj || {}; + + do { + props = Object.getOwnPropertyNames(sourceObj); + i = props.length; + while (i-- > 0) { + prop = props[i]; + if (!merged[prop]) { + destObj[prop] = sourceObj[prop]; + merged[prop] = true; + } + } + sourceObj = Object.getPrototypeOf(sourceObj); + } while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype); + + return destObj; +} + module.exports = { isArray: isArray, isArrayBuffer: isArrayBuffer, @@ -345,5 +391,7 @@ module.exports = { merge: merge, extend: extend, trim: trim, - stripBOM: stripBOM + stripBOM: stripBOM, + inherits: inherits, + toFlatObject: toFlatObject }; diff --git a/test/specs/api.spec.js b/test/specs/api.spec.js index eaeadfa2d7..73ef1d8829 100644 --- a/test/specs/api.spec.js +++ b/test/specs/api.spec.js @@ -36,7 +36,7 @@ describe('static api', function () { expect(typeof axios.create).toEqual('function'); }); - it('should have Cancel, CancelToken, and isCancel properties', function () { + it('should have CanceledError, CancelToken, and isCancel properties', function () { expect(typeof axios.Cancel).toEqual('function'); expect(typeof axios.CancelToken).toEqual('function'); expect(typeof axios.isCancel).toEqual('function'); diff --git a/test/specs/cancel.spec.js b/test/specs/cancel.spec.js index 793ce0d0bf..d4056eb48c 100644 --- a/test/specs/cancel.spec.js +++ b/test/specs/cancel.spec.js @@ -14,7 +14,7 @@ describe('cancel', function() { }); describe('when called before sending request', function() { - it('rejects Promise with a Cancel object', function(done) { + it('rejects Promise with a CanceledError object', function(done) { var source = CancelToken.source(); source.cancel('Operation has been canceled.'); axios.get('/foo', { @@ -28,7 +28,7 @@ describe('cancel', function() { }); describe('when called after request has been sent', function() { - it('rejects Promise with a Cancel object', function(done) { + it('rejects Promise with a CanceledError object', function(done) { var source = CancelToken.source(); axios.get('/foo/bar', { cancelToken: source.token diff --git a/test/specs/cancel/Cancel.spec.js b/test/specs/cancel/Cancel.spec.js deleted file mode 100644 index 0e0de805bf..0000000000 --- a/test/specs/cancel/Cancel.spec.js +++ /dev/null @@ -1,15 +0,0 @@ -var Cancel = require('../../../lib/cancel/Cancel'); - -describe('Cancel', function() { - describe('toString', function() { - it('returns correct result when message is not specified', function() { - var cancel = new Cancel(); - expect(cancel.toString()).toBe('Cancel'); - }); - - it('returns correct result when message is specified', function() { - var cancel = new Cancel('Operation has been canceled.'); - expect(cancel.toString()).toBe('Cancel: Operation has been canceled.'); - }); - }); -}); diff --git a/test/specs/cancel/CancelToken.spec.js b/test/specs/cancel/CancelToken.spec.js index dd72327174..2fa067321e 100644 --- a/test/specs/cancel/CancelToken.spec.js +++ b/test/specs/cancel/CancelToken.spec.js @@ -1,5 +1,5 @@ var CancelToken = require('../../../lib/cancel/CancelToken'); -var Cancel = require('../../../lib/cancel/Cancel'); +var CanceledError = require('../../../lib/cancel/CanceledError'); describe('CancelToken', function() { describe('constructor', function() { @@ -17,13 +17,13 @@ describe('CancelToken', function() { }); describe('reason', function() { - it('returns a Cancel if cancellation has been requested', function() { + it('returns a CanceledError if cancellation has been requested', function() { var cancel; var token = new CancelToken(function(c) { cancel = c; }); cancel('Operation has been canceled.'); - expect(token.reason).toEqual(jasmine.any(Cancel)); + expect(token.reason).toEqual(jasmine.any(CanceledError)); expect(token.reason.message).toBe('Operation has been canceled.'); }); @@ -40,7 +40,7 @@ describe('CancelToken', function() { cancel = c; }); token.promise.then(function onFulfilled(value) { - expect(value).toEqual(jasmine.any(Cancel)); + expect(value).toEqual(jasmine.any(CanceledError)); expect(value.message).toBe('Operation has been canceled.'); done(); }); @@ -50,7 +50,7 @@ describe('CancelToken', function() { describe('throwIfRequested', function() { it('throws if cancellation has been requested', function() { - // Note: we cannot use expect.toThrowError here as Cancel does not inherit from Error + // Note: we cannot use expect.toThrowError here as CanceledError does not inherit from Error var cancel; var token = new CancelToken(function(c) { cancel = c; @@ -60,8 +60,8 @@ describe('CancelToken', function() { token.throwIfRequested(); fail('Expected throwIfRequested to throw.'); } catch (thrown) { - if (!(thrown instanceof Cancel)) { - fail('Expected throwIfRequested to throw a Cancel, but it threw ' + thrown + '.'); + if (!(thrown instanceof CanceledError)) { + fail('Expected throwIfRequested to throw a CanceledError, but it threw ' + thrown + '.'); } expect(thrown.message).toBe('Operation has been canceled.'); } @@ -80,7 +80,7 @@ describe('CancelToken', function() { expect(source.cancel).toEqual(jasmine.any(Function)); expect(source.token.reason).toBeUndefined(); source.cancel('Operation has been canceled.'); - expect(source.token.reason).toEqual(jasmine.any(Cancel)); + expect(source.token.reason).toEqual(jasmine.any(CanceledError)); expect(source.token.reason.message).toBe('Operation has been canceled.'); }); }); diff --git a/test/specs/cancel/CanceledError.spec.js b/test/specs/cancel/CanceledError.spec.js new file mode 100644 index 0000000000..de996d581a --- /dev/null +++ b/test/specs/cancel/CanceledError.spec.js @@ -0,0 +1,15 @@ +var CanceledError = require('../../../lib/cancel/CanceledError'); + +describe('Cancel', function() { + describe('toString', function() { + it('returns correct result when message is not specified', function() { + var cancel = new CanceledError(); + expect(cancel.toString()).toBe('CanceledError: canceled'); + }); + + it('returns correct result when message is specified', function() { + var cancel = new CanceledError('Operation has been canceled.'); + expect(cancel.toString()).toBe('CanceledError: Operation has been canceled.'); + }); + }); +}); diff --git a/test/specs/cancel/isCancel.spec.js b/test/specs/cancel/isCancel.spec.js index e6be40dac9..aac264bdc4 100644 --- a/test/specs/cancel/isCancel.spec.js +++ b/test/specs/cancel/isCancel.spec.js @@ -1,12 +1,12 @@ var isCancel = require('../../../lib/cancel/isCancel'); -var Cancel = require('../../../lib/cancel/Cancel'); +var CanceledError = require('../../../lib/cancel/CanceledError'); describe('isCancel', function() { - it('returns true if value is a Cancel', function() { - expect(isCancel(new Cancel())).toBe(true); + it('returns true if value is a CanceledError', function() { + expect(isCancel(new CanceledError())).toBe(true); }); - it('returns false if value is not a Cancel', function() { + it('returns false if value is not a CanceledError', function() { expect(isCancel({ foo: 'bar' })).toBe(false); }); }); diff --git a/test/specs/core/AxiosError.spec.js b/test/specs/core/AxiosError.spec.js new file mode 100644 index 0000000000..49a9a6fb0d --- /dev/null +++ b/test/specs/core/AxiosError.spec.js @@ -0,0 +1,50 @@ +var AxiosError = require('../../../lib/core/AxiosError'); + +describe('core::AxiosError', function() { + it('should create an Error with message, config, code, request, response and isAxiosError', function() { + var request = { path: '/foo' }; + var response = { status: 200, data: { foo: 'bar' } }; + var error = new AxiosError('Boom!', 'ESOMETHING', { foo: 'bar' }, request, response); + expect(error instanceof Error).toBe(true); + expect(error.message).toBe('Boom!'); + expect(error.config).toEqual({ foo: 'bar' }); + expect(error.code).toBe('ESOMETHING'); + expect(error.request).toBe(request); + expect(error.response).toBe(response); + expect(error.isAxiosError).toBe(true); + }); + it('should create an Error that can be serialized to JSON', function() { + // Attempting to serialize request and response results in + // TypeError: Converting circular structure to JSON + var request = { path: '/foo' }; + var response = { status: 200, data: { foo: 'bar' } }; + var error = new AxiosError('Boom!', 'ESOMETHING', { foo: 'bar' }, request, response); + var json = error.toJSON(); + expect(json.message).toBe('Boom!'); + expect(json.config).toEqual({ foo: 'bar' }); + expect(json.code).toBe('ESOMETHING'); + expect(json.status).toBe(200); + expect(json.request).toBe(undefined); + expect(json.response).toBe(undefined); + }); + + describe('core::createError.from', function() { + it('should add config, config, request and response to error', function() { + var error = new Error('Boom!'); + var request = { path: '/foo' }; + var response = { status: 200, data: { foo: 'bar' } }; + + var axiosError = AxiosError.from(error, 'ESOMETHING', { foo: 'bar' }, request, response); + expect(axiosError.config).toEqual({ foo: 'bar' }); + expect(axiosError.code).toBe('ESOMETHING'); + expect(axiosError.request).toBe(request); + expect(axiosError.response).toBe(response); + expect(axiosError.isAxiosError).toBe(true); + }); + + it('should return error', function() { + var error = new Error('Boom!'); + expect(AxiosError.from(error, 'ESOMETHING', { foo: 'bar' }) instanceof AxiosError).toBeTruthy(); + }); + }); +}); diff --git a/test/specs/core/createError.spec.js b/test/specs/core/createError.spec.js deleted file mode 100644 index 7b1f6c699c..0000000000 --- a/test/specs/core/createError.spec.js +++ /dev/null @@ -1,30 +0,0 @@ -var createError = require('../../../lib/core/createError'); - -describe('core::createError', function() { - it('should create an Error with message, config, code, request, response and isAxiosError', function() { - var request = { path: '/foo' }; - var response = { status: 200, data: { foo: 'bar' } }; - var error = createError('Boom!', { foo: 'bar' }, 'ESOMETHING', request, response); - expect(error instanceof Error).toBe(true); - expect(error.message).toBe('Boom!'); - expect(error.config).toEqual({ foo: 'bar' }); - expect(error.code).toBe('ESOMETHING'); - expect(error.request).toBe(request); - expect(error.response).toBe(response); - expect(error.isAxiosError).toBe(true); - }); - it('should create an Error that can be serialized to JSON', function() { - // Attempting to serialize request and response results in - // TypeError: Converting circular structure to JSON - var request = { path: '/foo' }; - var response = { status: 200, data: { foo: 'bar' } }; - var error = createError('Boom!', { foo: 'bar' }, 'ESOMETHING', request, response); - var json = error.toJSON(); - expect(json.message).toBe('Boom!'); - expect(json.config).toEqual({ foo: 'bar' }); - expect(json.code).toBe('ESOMETHING'); - expect(json.status).toBe(200); - expect(json.request).toBe(undefined); - expect(json.response).toBe(undefined); - }); -}); diff --git a/test/specs/core/enhanceError.spec.js b/test/specs/core/enhanceError.spec.js index 767ebfdde7..e69de29bb2 100644 --- a/test/specs/core/enhanceError.spec.js +++ b/test/specs/core/enhanceError.spec.js @@ -1,31 +0,0 @@ -var enhanceError = require('../../../lib/core/enhanceError'); - -describe('core::enhanceError', function() { - it('should add config, code, request, response, and toJSON function to error', function() { - var error = new Error('Boom!'); - var request = { path: '/foo' }; - var response = { status: 200, data: { foo: 'bar' } }; - - enhanceError(error, { foo: 'bar' }, 'ESOMETHING', request, response); - expect(error.config).toEqual({ foo: 'bar' }); - expect(error.code).toBe('ESOMETHING'); - expect(error.request).toBe(request); - expect(error.response).toBe(response); - expect(typeof error.toJSON).toBe('function'); - expect(error.isAxiosError).toBe(true); - }); - - it('should serialize to JSON with a status of null when there is no response', function() { - var error = new Error('Boom!'); - var request = { path: '/foo' }; - var response = undefined; - - var errorAsJson = enhanceError(error, { foo: 'bar' }, 'ESOMETHING', request, response).toJSON(); - expect(errorAsJson.status).toEqual(null); - }); - - it('should return error', function() { - var error = new Error('Boom!'); - expect(enhanceError(error, { foo: 'bar' }, 'ESOMETHING')).toBe(error); - }); -}); diff --git a/test/specs/helpers/isAxiosError.spec.js b/test/specs/helpers/isAxiosError.spec.js index 7aeef85f53..1f8b3a15db 100644 --- a/test/specs/helpers/isAxiosError.spec.js +++ b/test/specs/helpers/isAxiosError.spec.js @@ -1,19 +1,18 @@ -var createError = require('../../../lib/core/createError'); -var enhanceError = require('../../../lib/core/enhanceError'); +var AxiosError = require('../../../lib/core/AxiosError'); var isAxiosError = require('../../../lib/helpers/isAxiosError'); -describe('helpers::isAxiosError', function () { - it('should return true if the error is created by core::createError', function () { - expect(isAxiosError(createError('Boom!', { foo: 'bar' }))) +describe('helpers::isAxiosError', function() { + it('should return true if the error is created by core::createError', function() { + expect(isAxiosError(new AxiosError('Boom!', null, { foo: 'bar' }))) .toBe(true); }); - it('should return true if the error is enhanced by core::enhanceError', function () { - expect(isAxiosError(enhanceError(new Error('Boom!'), { foo: 'bar' }))) + it('should return true if the error is enhanced by core::enhanceError', function() { + expect(isAxiosError(AxiosError.from(new Error('Boom!'), null, { foo: 'bar' }))) .toBe(true); }); - it('should return false if the error is a normal Error instance', function () { + it('should return false if the error is a normal Error instance', function() { expect(isAxiosError(new Error('Boom!'))) .toBe(false); }); diff --git a/test/specs/instance.spec.js b/test/specs/instance.spec.js index f424892053..e579141c29 100644 --- a/test/specs/instance.spec.js +++ b/test/specs/instance.spec.js @@ -13,8 +13,10 @@ describe('instance', function () { for (var prop in axios) { if ([ 'Axios', + 'AxiosError', 'create', 'Cancel', + 'CanceledError', 'CancelToken', 'isCancel', 'all', diff --git a/test/specs/transform.spec.js b/test/specs/transform.spec.js index 6f81f46944..f7e62cb957 100644 --- a/test/specs/transform.spec.js +++ b/test/specs/transform.spec.js @@ -1,3 +1,5 @@ +var AxiosError = require("../../lib/core/AxiosError"); + describe('transform', function () { beforeEach(function () { jasmine.Ajax.install(); @@ -64,6 +66,7 @@ describe('transform', function () { setTimeout(function () { expect(thrown).toBeTruthy(); expect(thrown.name).toContain('SyntaxError'); + expect(thrown.code).toEqual(AxiosError.ERR_BAD_RESPONSE); done(); }, 100); }); diff --git a/test/specs/utils/toFlatObject.js b/test/specs/utils/toFlatObject.js new file mode 100644 index 0000000000..90b04d017b --- /dev/null +++ b/test/specs/utils/toFlatObject.js @@ -0,0 +1,10 @@ +var toFlatObject = require('../../../lib/utils').toFlatObject; + +describe('utils::toFlatObject', function () { + it('should resolve object proto chain to a flat object representation', function () { + var a = {x: 1}; + var b = Object.create(a, {y: {value: 2}}); + var c = Object.create(b, {z: {value: 3}}); + expect(toFlatObject(c)).toEqual({x: 1, y: 2, z: 3}); + }); +}); diff --git a/test/unit/adapters/http.js b/test/unit/adapters/http.js index c413b56bbc..0a1bab8a61 100644 --- a/test/unit/adapters/http.js +++ b/test/unit/adapters/http.js @@ -9,6 +9,7 @@ var fs = require('fs'); var path = require('path'); var pkg = require('./../../../package.json'); var server, proxy; +var AxiosError = require('../../../lib/core/AxiosError'); describe('supports http with nodejs', function () { @@ -51,7 +52,7 @@ describe('supports http with nodejs', function () { setTimeout(function () { assert.equal(success, false, 'request should not succeed'); assert.equal(failure, true, 'request should fail'); - assert.equal(error.code, 'ERR_PARSE_TIMEOUT'); + assert.equal(error.code, AxiosError.ERR_BAD_OPTION_VALUE); assert.equal(error.message, 'error trying to parse `config.timeout` to int'); done(); }, 300); @@ -953,7 +954,7 @@ describe('supports http with nodejs', function () { axios.get('http://localhost:4444/', { cancelToken: source.token }).catch(function (thrown) { - assert.ok(thrown instanceof axios.Cancel, 'Promise must be rejected with a Cancel object'); + assert.ok(thrown instanceof axios.Cancel, 'Promise must be rejected with a CanceledError object'); assert.equal(thrown.message, 'Operation has been canceled.'); done(); });