diff --git a/src/http/base-http.ts b/src/http/base-http.ts index 0f2ef027d..c0605e85e 100644 --- a/src/http/base-http.ts +++ b/src/http/base-http.ts @@ -1,10 +1,10 @@ import { MastoConfig } from '../config'; -import { MimeType, Serializer } from '../serializers'; +import { MimeType } from '../serializers'; +import { railsQueryString } from '../serializers/rails-querystring'; import { Data, Headers, Http, Request, Response } from './http'; export abstract class BaseHttp implements Http { abstract readonly config: MastoConfig; - abstract readonly serializer: Serializer; abstract request(request: Request): Promise>; @@ -19,11 +19,12 @@ export abstract class BaseHttp implements Http { return headers; } + encodeParams(params: Data = {}) { + return railsQueryString.stringify(params); + } + resolveUrl(path: string, params: Data = {}) { - const searchParams = this.serializer.serialize( - 'application/x-www-form-urlencoded', - params, - ); + const searchParams = this.encodeParams(params); return `${this.config.url}${path}${ searchParams !== '' ? `?${searchParams}` : '' diff --git a/src/http/http-axios-impl.ts b/src/http/http-axios-impl.ts index 59b4cca71..16e41c53d 100644 --- a/src/http/http-axios-impl.ts +++ b/src/http/http-axios-impl.ts @@ -57,11 +57,7 @@ export class HttpAxiosImpl extends BaseHttp implements Http { return this.serializer.deserialize(contentType, data); }, paramsSerializer: { - serialize: (params) => - this.serializer.serialize( - 'application/x-www-form-urlencoded', - params, - ) as string, + serialize: (params) => this.encodeParams(params), }, }); } diff --git a/src/serializers/rails-querystring.spec.ts b/src/serializers/rails-querystring.spec.ts new file mode 100644 index 000000000..c5f1f85fd --- /dev/null +++ b/src/serializers/rails-querystring.spec.ts @@ -0,0 +1,37 @@ +import { railsQueryString } from './rails-querystring'; + +describe('railsQueryString', () => { + it('encodes null', () => { + const result = railsQueryString.stringify(null); + expect(result).toBe(''); + }); + + it('encodes an empty object', () => { + const result = railsQueryString.stringify({}); + expect(result).toBe(''); + }); + + it('encodes a basic record', () => { + const result = railsQueryString.stringify({ key: 'value' }); + expect(result).toBe('key=value'); + }); + + it('encodes a record with multiple values', () => { + const result = railsQueryString.stringify({ + key1: 'value1', + key2: 'value2', + }); + expect(result).toBe('key1=value1&key2=value2'); + }); + + it('encodes an array inside a record', () => { + const result = railsQueryString.stringify({ + key1: 'value1', + key2: 'value2', + key3: ['apple', 'facebook', 'microsoft'], + }); + expect(result).toBe( + 'key1=value1&key2=value2&key3[]=apple&key3[]=facebook&key3[]=microsoft', + ); + }); +}); diff --git a/src/serializers/rails-querystring.ts b/src/serializers/rails-querystring.ts new file mode 100644 index 000000000..861bacbac --- /dev/null +++ b/src/serializers/rails-querystring.ts @@ -0,0 +1,30 @@ +import { isObject } from './is-object'; + +/** + * Encodes URI in Rails format + */ +const stringify = (object?: unknown): string => { + if (object == null) { + return ''; + } + if (!isObject(object)) { + return ''; + } + + const values = Object.entries(object) + .reduce((prev, [k, v]) => { + if (Array.isArray(v)) { + const xs = v.map((x) => `${k}[]=${x}`); + return prev.concat(...xs); + } + if (isObject(v)) { + throw new TypeError('Encoding nested object is not supported'); + } + return prev.concat(`${k}=${v}`); + }, []) + .join('&'); + + return values; +}; + +export const railsQueryString = { stringify }; diff --git a/src/ws/base-ws.ts b/src/ws/base-ws.ts index 5cc3e0914..b71ea3020 100644 --- a/src/ws/base-ws.ts +++ b/src/ws/base-ws.ts @@ -2,6 +2,7 @@ import semver from 'semver'; import { MastoConfig } from '../config'; import { Serializer } from '../serializers'; +import { railsQueryString } from '../serializers/rails-querystring'; import { Ws, WsEvents } from './ws'; export abstract class BaseWs implements Ws { @@ -30,11 +31,7 @@ export abstract class BaseWs implements Ws { if (!this.supportsSecureToken()) { params.accessToken = this.config.accessToken; } - - const query = this.serializer.serialize( - 'application/x-www-form-urlencoded', - params, - ); + const query = railsQueryString.stringify(params); return this.baseUrl + path + (query !== '' ? `?${query}` : ''); }