Skip to content

Commit

Permalink
fix: properly serialize query params (using ASPNET way for now)
Browse files Browse the repository at this point in the history
  • Loading branch information
yhnavein committed Jul 8, 2024
1 parent d86a2ab commit cfddb7c
Show file tree
Hide file tree
Showing 27 changed files with 518 additions and 343 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@

"files.associations": {
"templates/**/*.ejs": "plaintext"
},
"[html]": {
"editor.formatOnSave": false
}
}
70 changes: 70 additions & 0 deletions src/utils/paramSerializer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { expect } from 'chai';

/** This file tests a code that will be generated and is hardcoded into the templates */

function paramsSerializer<T = any>(params: T, parentKey: string | null = null): string {
if (params === undefined || params === null) return '';
const encodedParams: string[] = [];
const encodeValue = (value: any) =>
encodeURIComponent(value instanceof Date && !Number.isNaN(value) ? value.toISOString() : value);

for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = (params as any)[key];
if (value !== undefined) {
const fullKey = parentKey ? `${parentKey}.${key}` : key;

if (Array.isArray(value)) {
for (const element of value) {
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(element)}`);
}
} else if (value instanceof Date && !Number.isNaN(value)) {
// If the value is a Date, convert it to ISO format
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(value)}`);
} else if (typeof value === 'object') {
// If the value is an object or array, recursively encode its contents
const result = paramsSerializer(value, fullKey);
if (result !== '') encodedParams.push(result);
} else {
// Otherwise, encode the key-value pair
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(value)}`);
}
}
}
}

return encodedParams.join('&');
}

const date = new Date('2020-04-16T00:00:00.000Z');

describe('paramsSerializer', () => {
const testCases = [
{ input: '', expected: '' },
{ input: null, expected: '' },
{ input: undefined, expected: '' },
{ input: {}, expected: '' },
{ input: { a: 1, b: 'test' }, expected: 'a=1&b=test' },
{ input: { a: 1, b: [1, 2, 3, 'test'] }, expected: 'a=1&b=1&b=2&b=3&b=test' },
{ input: { a: { b: { c: 1, d: 'test' } } }, expected: 'a.b.c=1&a.b.d=test' },
{
input: { a: { b: { c: undefined, d: 'test', e: null, f: '' } } },
expected: 'a.b.d=test&a.b.f=',
},
{ input: { a: [1, 2, 3] }, expected: 'a=1&a=2&a=3' },
{ input: { a: date }, expected: 'a=2020-04-16T00%3A00%3A00.000Z' },
{ input: { a: [date] }, expected: 'a=2020-04-16T00%3A00%3A00.000Z' },
{
input: { a: [date, date] },
expected: 'a=2020-04-16T00%3A00%3A00.000Z&a=2020-04-16T00%3A00%3A00.000Z',
},
];

for (const el of testCases) {
it(`should handle ${JSON.stringify(el.input)}`, () => {
const res = paramsSerializer(el.input);

expect(res).to.be.equal(el.expected);
});
}
});
63 changes: 0 additions & 63 deletions src/utils/serializeQueryParam.angular1.spec.ts

This file was deleted.

73 changes: 0 additions & 73 deletions src/utils/serializeQueryParam.spec.ts

This file was deleted.

39 changes: 32 additions & 7 deletions templates/axios/barrel.ejs
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@

function serializeQueryParam(obj: any) {
if (obj === null || obj === undefined) return '';
if (obj instanceof Date) return obj.toJSON();
if (typeof obj !== 'object' || Array.isArray(obj)) return obj;
return Object.keys(obj)
.reduce((a: any, b) => a.push(b + '=' + obj[b]) && a, [])
.join('&');
function paramsSerializer<T = any>(params: T, parentKey: string | null = null): string {
if (params === undefined || params === null) return '';
const encodedParams: string[] = [];
const encodeValue = (value: any) =>
encodeURIComponent(value instanceof Date && !Number.isNaN(value) ? value.toISOString() : value);

for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = (params as any)[key];
if (value !== undefined) {
const fullKey = parentKey ? `${parentKey}.${key}` : key;

if (Array.isArray(value)) {
for (const element of value) {
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(element)}`);
}
} else if (value instanceof Date && !Number.isNaN(value)) {
// If the value is a Date, convert it to ISO format
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(value)}`);
} else if (typeof value === 'object') {
// If the value is an object or array, recursively encode its contents
const result = paramsSerializer(value, fullKey);
if (result !== '') encodedParams.push(result);
} else {
// Otherwise, encode the key-value pair
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(value)}`);
}
}
}
}

return encodedParams.join('&');
}

3 changes: 2 additions & 1 deletion templates/axios/baseClient.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
// ReSharper disable InconsistentNaming
// deno-lint-ignore-file

import Axios, { AxiosPromise, AxiosRequestConfig } from "axios";
import Axios, { type AxiosPromise, AxiosRequestConfig } from "axios";

export const axios = Axios.create({
baseURL: '<%= it.baseUrl || '' %>',
paramsSerializer: (params: any) => paramsSerializer(params),
});

2 changes: 1 addition & 1 deletion templates/axios/operation.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ $config?: AxiosRequestConfig
<% if(it.query && it.query.length > 0) { %>
params: {
<% it.query.forEach((parameter) => { %>
'<%= parameter.originalName %>': serializeQueryParam(<%= parameter.name %>),
'<%= parameter.originalName %>': <%= parameter.name %>,
<% }); %>
},
<% } %>
Expand Down
39 changes: 32 additions & 7 deletions templates/fetch/barrel.ejs
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@

function serializeQueryParam(obj: any) {
if (obj === null || obj === undefined) return '';
if (obj instanceof Date) return encodeURIComponent(obj.toJSON());
if (typeof obj !== 'object' || Array.isArray(obj)) return encodeURIComponent(obj);
return Object.keys(obj)
.reduce((a: any, b) => a.push(`${encodeURIComponent(b)}=${encodeURIComponent(obj[b])}`) && a, [])
.join('&');
function paramsSerializer<T = any>(params: T, parentKey: string | null = null): string {
if (params === undefined || params === null) return '';
const encodedParams: string[] = [];
const encodeValue = (value: any) =>
encodeURIComponent(value instanceof Date && !Number.isNaN(value) ? value.toISOString() : value);

for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = (params as any)[key];
if (value !== undefined) {
const fullKey = parentKey ? `${parentKey}.${key}` : key;

if (Array.isArray(value)) {
for (const element of value) {
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(element)}`);
}
} else if (value instanceof Date && !Number.isNaN(value)) {
// If the value is a Date, convert it to ISO format
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(value)}`);
} else if (typeof value === 'object') {
// If the value is an object or array, recursively encode its contents
const result = paramsSerializer(value, fullKey);
if (result !== '') encodedParams.push(result);
} else {
// Otherwise, encode the key-value pair
encodedParams.push(`${encodeURIComponent(fullKey)}=${encodeValue(value)}`);
}
}
}
}

return encodedParams.join('&');
}

1 change: 1 addition & 0 deletions templates/fetch/baseClient.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@

export const defaults = {
baseUrl: '<%= it.baseUrl || '' %>',
paramsSerializer: (params: any) => paramsSerializer(params),
};

17 changes: 5 additions & 12 deletions templates/fetch/operation.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,11 @@
<% }); %>
$config?: RequestInit
): Promise<<%~ it.returnType %>> {
let url = `${defaults.baseUrl}<%= it.url %>?`;
<% if(it.query && it.query.length > 0) { %>
<% it.query.forEach((parameter) => { %>
if (<%= parameter.name %> !== undefined) {
<% if(!!parameter.original && parameter.original.type === 'array') { %>
<%= parameter.name %>.forEach(item => { url += `<%= parameter.originalName %>=${serializeQueryParam(item)}&`; });
<% } else {%>
url += `<%= parameter.originalName %>=${serializeQueryParam(<%= parameter.name %>)}&`;
<% } %>
}
<% }); %>
<% } %>
const url = `${defaults.baseUrl}<%= it.url %>?<%
if(it.query && it.query.length > 0) { %>${defaults.paramsSerializer({<%
it.query.forEach((parameter) => { %>
'<%= parameter.originalName %>': <%= parameter.name %>,
<% }); %>})}<% } %>`;

return fetch(url, {
method: '<%= it.method %>',
Expand Down
2 changes: 1 addition & 1 deletion templates/ng1/baseClient.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// ReSharper disable InconsistentNaming
// deno-lint-ignore-file

import { IHttpService, IRequestShortcutConfig, IPromise } from 'angular';
import type { IHttpService, IRequestShortcutConfig, IPromise } from 'angular';

abstract class BaseService {
constructor(protected readonly $http: IHttpService, public baseUrl: string) { }
Expand Down
Loading

0 comments on commit cfddb7c

Please sign in to comment.