Skip to content

Commit

Permalink
Merge pull request axios#1395 from codeclown/instance-options
Browse files Browse the repository at this point in the history
Fixing axios#385 - Keep defaults local to instance
  • Loading branch information
nickuraltsev authored Apr 10, 2018
2 parents dd16944 + 4e8039e commit ec97c68
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 10 deletions.
3 changes: 2 additions & 1 deletion lib/axios.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

/**
Expand Down Expand Up @@ -32,7 +33,7 @@ axios.Axios = Axios;

// Factory for creating new instances
axios.create = function create(instanceConfig) {
return createInstance(utils.merge(defaults, instanceConfig));
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose Cancel & CancelToken
Expand Down
13 changes: 7 additions & 6 deletions lib/core/Axios.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use strict';

var defaults = require('./../defaults');
var utils = require('./../utils');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');

/**
* Create a new instance of Axios
Expand All @@ -27,13 +27,14 @@ Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}

config = utils.merge(defaults, this.defaults, config);
config.method = config.method.toLowerCase();
config = mergeConfig(this.defaults, config);
config.method = config.method ? config.method.toLowerCase() : 'get';

// Hook up interceptors middleware
var chain = [dispatchRequest, undefined];
Expand Down
51 changes: 51 additions & 0 deletions lib/core/mergeConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

var utils = require('../utils');

/**
* Config-specific merge-function which creates a new config-object
* by merging two configuration objects together.
*
* @param {Object} config1
* @param {Object} config2
* @returns {Object} New object resulting from merging config2 to config1
*/
module.exports = function mergeConfig(config1, config2) {
// eslint-disable-next-line no-param-reassign
config2 = config2 || {};
var config = {};

utils.forEach(['url', 'method', 'params', 'data'], function valueFromConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
}
});

utils.forEach(['headers', 'auth', 'proxy'], function mergeDeepProperties(prop) {
if (utils.isObject(config2[prop])) {
config[prop] = utils.deepMerge(config1[prop], config2[prop]);
} else if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (utils.isObject(config1[prop])) {
config[prop] = utils.deepMerge(config1[prop]);
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});

utils.forEach([
'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer',
'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',
'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'maxContentLength',
'validateStatus', 'maxRedirects', 'httpAgent', 'httpsAgent', 'cancelToken',
'socketPath'
], function defaultToConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});

return config;
};
1 change: 0 additions & 1 deletion lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ function getDefaultAdapter() {
}

var defaults = {
method: 'get',
adapter: getDefaultAdapter(),

transformRequest: [function transformRequest(data, headers) {
Expand Down
27 changes: 27 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,32 @@ function merge(/* obj1, obj2, obj3, ... */) {
return result;
}

/**
* Function equal to merge with the difference being that no reference
* to original objects is kept.
*
* @see merge
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
*/
function deepMerge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (typeof result[key] === 'object' && typeof val === 'object') {
result[key] = deepMerge(result[key], val);
} else if (typeof val === 'object') {
result[key] = deepMerge({}, val);
} else {
result[key] = val;
}
}

for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}

/**
* Extends object a by mutably adding to it the properties of object b.
*
Expand Down Expand Up @@ -298,6 +324,7 @@ module.exports = {
isStandardBrowserEnv: isStandardBrowserEnv,
forEach: forEach,
merge: merge,
deepMerge: deepMerge,
extend: extend,
trim: trim
};
69 changes: 69 additions & 0 deletions test/specs/core/mergeConfig.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
var defaults = require('../../../lib/defaults');
var mergeConfig = require('../../../lib/core/mergeConfig');

describe('core::mergeConfig', function() {
it('should accept undefined for second argument', function() {
expect(mergeConfig(defaults, undefined)).toEqual(defaults);
});

it('should accept an object for second argument', function() {
expect(mergeConfig(defaults, {})).toEqual(defaults);
});

it('should not leave references', function() {
var merged = mergeConfig(defaults, {});
expect(merged).not.toBe(defaults);
expect(merged.headers).not.toBe(defaults.headers);
});

it('should allow setting request options', function() {
var config = {
url: '__sample url__',
method: '__sample method__',
params: '__sample params__',
data: { foo: true }
};
var merged = mergeConfig(defaults, config);
expect(merged.url).toEqual(config.url);
expect(merged.method).toEqual(config.method);
expect(merged.params).toEqual(config.params);
expect(merged.data).toEqual(config.data);
});

it('should not inherit request options', function() {
var localDefaults = {
url: '__sample url__',
method: '__sample method__',
params: '__sample params__',
data: { foo: true }
};
var merged = mergeConfig(localDefaults, {});
expect(merged.url).toEqual(undefined);
expect(merged.method).toEqual(undefined);
expect(merged.params).toEqual(undefined);
expect(merged.data).toEqual(undefined);
});

it('should merge auth, headers, proxy with defaults', function() {
expect(mergeConfig({ auth: undefined }, { auth: { user: 'foo', pass: 'test' } })).toEqual({
auth: { user: 'foo', pass: 'test' }
});
expect(mergeConfig({ auth: { user: 'foo', pass: 'test' } }, { auth: { pass: 'foobar' } })).toEqual({
auth: { user: 'foo', pass: 'foobar' }
});
});

it('should overwrite auth, headers, proxy with a non-object value', function() {
expect(mergeConfig({ auth: { user: 'foo', pass: 'test' } }, { auth: false })).toEqual({
auth: false
});
expect(mergeConfig({ auth: { user: 'foo', pass: 'test' } }, { auth: null })).toEqual({
auth: null
});
});

it('should allow setting other options', function() {
var merged = mergeConfig(defaults, { timeout: 123 });
expect(merged.timeout).toEqual(123);
});
});
4 changes: 2 additions & 2 deletions test/specs/defaults.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ describe('defaults', function () {
});
});

it('should be used by custom instance if set after instance created', function (done) {
it('should not be used by custom instance if set after instance created', function (done) {
var instance = axios.create();
axios.defaults.baseURL = 'http://example.org/';

instance.get('/foo');

getAjaxRequest().then(function (request) {
expect(request.url).toBe('http://example.org/foo');
expect(request.url).toBe('/foo');
done();
});
});
Expand Down
28 changes: 28 additions & 0 deletions test/specs/options.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,32 @@ describe('options', function () {
done();
});
});

it('should change only the baseURL of the specified instance', function() {
var instance1 = axios.create();
var instance2 = axios.create();

instance1.defaults.baseURL = 'http://instance1.example.com/';

expect(instance2.defaults.baseURL).not.toBe('http://instance1.example.com/');
});

it('should change only the headers of the specified instance', function() {
var instance1 = axios.create();
var instance2 = axios.create();

instance1.defaults.headers.common.Authorization = 'faketoken';
instance2.defaults.headers.common.Authorization = 'differentfaketoken';

instance1.defaults.headers.common['Content-Type'] = 'application/xml';
instance2.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded';

expect(axios.defaults.headers.common.Authorization).toBe(undefined);
expect(instance1.defaults.headers.common.Authorization).toBe('faketoken');
expect(instance2.defaults.headers.common.Authorization).toBe('differentfaketoken');

expect(axios.defaults.headers.common['Content-Type']).toBe(undefined);
expect(instance1.defaults.headers.common['Content-Type']).toBe('application/xml');
expect(instance2.defaults.headers.common['Content-Type']).toBe('application/x-www-form-urlencoded');
});
});
66 changes: 66 additions & 0 deletions test/specs/utils/deepMerge.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
var deepMerge = require('../../../lib/utils').deepMerge;

describe('utils::deepMerge', function () {
it('should be immutable', function () {
var a = {};
var b = {foo: 123};
var c = {bar: 456};

deepMerge(a, b, c);

expect(typeof a.foo).toEqual('undefined');
expect(typeof a.bar).toEqual('undefined');
expect(typeof b.bar).toEqual('undefined');
expect(typeof c.foo).toEqual('undefined');
});

it('should deepMerge properties', function () {
var a = {foo: 123};
var b = {bar: 456};
var c = {foo: 789};
var d = deepMerge(a, b, c);

expect(d.foo).toEqual(789);
expect(d.bar).toEqual(456);
});

it('should deepMerge recursively', function () {
var a = {foo: {bar: 123}};
var b = {foo: {baz: 456}, bar: {qux: 789}};

expect(deepMerge(a, b)).toEqual({
foo: {
bar: 123,
baz: 456
},
bar: {
qux: 789
}
});
});

it('should remove all references from nested objects', function () {
var a = {foo: {bar: 123}};
var b = {};
var d = deepMerge(a, b);

expect(d).toEqual({
foo: {
bar: 123
}
});

expect(d.foo).not.toBe(a.foo);
});

it('handles null and undefined arguments', function () {
expect(deepMerge(undefined, undefined)).toEqual({});
expect(deepMerge(undefined, {foo: 123})).toEqual({foo: 123});
expect(deepMerge({foo: 123}, undefined)).toEqual({foo: 123});

expect(deepMerge(null, null)).toEqual({});
expect(deepMerge(null, {foo: 123})).toEqual({foo: 123});
expect(deepMerge({foo: 123}, null)).toEqual({foo: 123});
});
});

0 comments on commit ec97c68

Please sign in to comment.