From 934f390cc367fd13cca6acad050d5f47194a8abb Mon Sep 17 00:00:00 2001 From: Dmitriy Mozgovoy Date: Wed, 25 May 2022 09:16:38 +0300 Subject: [PATCH] URL params serializer; (#4734) * Refactored BuildURL helper to use URLSearchParams serializer; * Updated typings; Added TS test; --- index.d.ts | 22 ++++++-- lib/helpers/AxiosURLSearchParams.js | 42 +++++++++++++++ lib/helpers/buildURL.js | 52 +++++-------------- lib/helpers/toFormData.js | 4 ++ .../browser/classes/URLSearchParams.js | 35 +------------ test/specs/helpers/buildURL.spec.js | 11 +--- test/typescript/axios.ts | 5 +- 7 files changed, 84 insertions(+), 87 deletions(-) create mode 100644 lib/helpers/AxiosURLSearchParams.js diff --git a/index.d.ts b/index.d.ts index 1f82f50..b98a5c6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -90,22 +90,34 @@ export interface GenericAbortSignal { } export interface FormDataVisitorHelpers { - defaultVisitor: FormDataVisitor; + defaultVisitor: SerializerVisitor; convertValue: (value: any) => any; isVisitable: (value: any) => boolean; } -export interface FormDataVisitor { +export interface SerializerVisitor { (value: any, key: string | number, path: null | Array, helpers: FormDataVisitorHelpers): boolean; } -export interface FormSerializerOptions { - visitor?: FormDataVisitor; +export interface SerializerOptions { + visitor?: SerializerVisitor; dots?: boolean; metaTokens?: boolean; indexes?: boolean; } +// tslint:disable-next-line +export interface FormSerializerOptions extends SerializerOptions { +} + +export interface ParamEncoder { + (value: any, defaultEncoder: (value: any) => any): any; +} + +export interface ParamsSerializerOptions extends SerializerOptions { + encode?: ParamEncoder; +} + export interface AxiosRequestConfig { url?: string; method?: Method | string; @@ -114,7 +126,7 @@ export interface AxiosRequestConfig { transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[]; headers?: AxiosRequestHeaders; params?: any; - paramsSerializer?: (params: any) => string; + paramsSerializer?: ParamsSerializerOptions; data?: D; timeout?: number; timeoutErrorMessage?: string; diff --git a/lib/helpers/AxiosURLSearchParams.js b/lib/helpers/AxiosURLSearchParams.js new file mode 100644 index 0000000..7c49f18 --- /dev/null +++ b/lib/helpers/AxiosURLSearchParams.js @@ -0,0 +1,42 @@ +'use strict'; + +var toFormData = require('./toFormData'); + +function encode(str) { + var charMap = { + '!': '%21', + "'": '%27', + '(': '%28', + ')': '%29', + '~': '%7E', + '%20': '+', + '%00': '\x00' + }; + return encodeURIComponent(str).replace(/[!'\(\)~]|%20|%00/g, function replacer(match) { + return charMap[match]; + }); +} + +function AxiosURLSearchParams(params, options) { + this._pairs = []; + + params && toFormData(params, this, options); +} + +var prototype = AxiosURLSearchParams.prototype; + +prototype.append = function append(name, value) { + this._pairs.push([name, value]); +}; + +prototype.toString = function toString(encoder) { + var _encode = encoder ? function(value) { + return encoder.call(this, value, encode); + } : encode; + + return this._pairs.map(function each(pair) { + return _encode(pair[0]) + '=' + _encode(pair[1]); + }, '').join('&'); +}; + +module.exports = AxiosURLSearchParams; diff --git a/lib/helpers/buildURL.js b/lib/helpers/buildURL.js index 4afa237..bebacb3 100644 --- a/lib/helpers/buildURL.js +++ b/lib/helpers/buildURL.js @@ -1,6 +1,7 @@ 'use strict'; -var utils = require('./../utils'); +var utils = require('../utils'); +var AxiosURLSearchParams = require('../helpers/AxiosURLSearchParams'); function encode(val) { return encodeURIComponent(val). @@ -17,54 +18,29 @@ function encode(val) { * * @param {string} url The base of the url (e.g., http://www.google.com) * @param {object} [params] The params to be appended - * @param {?object} paramsSerializer + * @param {?object} options * @returns {string} The formatted url */ -module.exports = function buildURL(url, params, paramsSerializer) { +module.exports = function buildURL(url, params, options) { /*eslint no-param-reassign:0*/ if (!params) { return url; } - var serializedParams; - if (paramsSerializer) { - serializedParams = paramsSerializer(params); - } else if (utils.isURLSearchParams(params)) { - serializedParams = params.toString(); - } else { - var parts = []; + var hashmarkIndex = url.indexOf('#'); - utils.forEach(params, function serialize(val, key) { - if (val === null || typeof val === 'undefined') { - return; - } - - if (utils.isArray(val)) { - key = key + '[]'; - } else { - val = [val]; - } - - utils.forEach(val, function parseValue(v) { - if (utils.isDate(v)) { - v = v.toISOString(); - } else if (utils.isObject(v)) { - v = JSON.stringify(v); - } - parts.push(encode(key) + '=' + encode(v)); - }); - }); - - serializedParams = parts.join('&'); + if (hashmarkIndex !== -1) { + url = url.slice(0, hashmarkIndex); } - if (serializedParams) { - var hashmarkIndex = url.indexOf('#'); - if (hashmarkIndex !== -1) { - url = url.slice(0, hashmarkIndex); - } + var _encode = options && options.encode || encode; - url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; + var serializerParams = utils.isURLSearchParams(params) ? + params.toString() : + new AxiosURLSearchParams(params, options).toString(_encode); + + if (serializerParams) { + url += (url.indexOf('?') === -1 ? '?' : '&') + serializerParams; } return url; diff --git a/lib/helpers/toFormData.js b/lib/helpers/toFormData.js index a2ab569..6dcb9d2 100644 --- a/lib/helpers/toFormData.js +++ b/lib/helpers/toFormData.js @@ -46,6 +46,10 @@ function isSpecCompliant(thing) { **/ function toFormData(obj, formData, options) { + if (!utils.isObject(obj)) { + throw new TypeError('target must be an object'); + } + // eslint-disable-next-line no-param-reassign formData = formData || new (envFormData || FormData)(); diff --git a/lib/platform/browser/classes/URLSearchParams.js b/lib/platform/browser/classes/URLSearchParams.js index db4ac78..65f63d8 100644 --- a/lib/platform/browser/classes/URLSearchParams.js +++ b/lib/platform/browser/classes/URLSearchParams.js @@ -1,36 +1,5 @@ 'use strict'; -module.exports = typeof URLSearchParams !== 'undefined' ? URLSearchParams : (function defineURLSearchParams() { - function encode(str) { - var charMap = { - '!': '%21', - "'": '%27', - '(': '%28', - ')': '%29', - '~': '%7E', - '%20': '+', - '%00': '\x00' - }; - return encodeURIComponent(str).replace(/[!'\(\)~]|%20|%00/g, function replacer(match) { - return charMap[match]; - }); - } +var AxiosURLSearchParams = require('../../../helpers/AxiosURLSearchParams'); - function URLSearchParams() { - this.pairs = []; - } - - var prototype = URLSearchParams.prototype; - - prototype.append = function append(name, value) { - this.pairs.push([name, value]); - }; - - prototype.toString = function toString() { - return this.pairs.map(function each(pair) { - return pair[0] + '=' + encode(pair[1]); - }, '').join('&'); - }; - - return URLSearchParams; -})(); +module.exports = typeof URLSearchParams !== 'undefined' ? URLSearchParams : AxiosURLSearchParams; diff --git a/test/specs/helpers/buildURL.spec.js b/test/specs/helpers/buildURL.spec.js index 7adf574..0bee3e9 100644 --- a/test/specs/helpers/buildURL.spec.js +++ b/test/specs/helpers/buildURL.spec.js @@ -17,7 +17,7 @@ describe('helpers::buildURL', function () { foo: { bar: 'baz' } - })).toEqual('/foo?foo=' + encodeURI('{"bar":"baz"}')); + })).toEqual('/foo?foo[bar]=baz'); }); it('should support date params', function () { @@ -60,15 +60,6 @@ describe('helpers::buildURL', function () { })).toEqual('/foo?foo=bar&query=baz'); }); - it('should use serializer if provided', function () { - serializer = sinon.stub(); - params = {foo: 'bar'}; - serializer.returns('foo=bar'); - expect(buildURL('/foo', params, serializer)).toEqual('/foo?foo=bar'); - expect(serializer.calledOnce).toBe(true); - expect(serializer.calledWith(params)).toBe(true); - }); - it('should support URLSearchParams', function () { expect(buildURL('/foo', new URLSearchParams('bar=baz'))).toEqual('/foo?bar=baz'); }); diff --git a/test/typescript/axios.ts b/test/typescript/axios.ts index 94d4160..c0c6159 100644 --- a/test/typescript/axios.ts +++ b/test/typescript/axios.ts @@ -20,7 +20,10 @@ const config: AxiosRequestConfig = { ], headers: { 'X-FOO': 'bar' }, params: { id: 12345 }, - paramsSerializer: (params: any) => 'id=12345', + paramsSerializer: { + indexes: true, + encode: (value) => value + }, data: { foo: 'bar' }, timeout: 10000, withCredentials: true,