diff --git a/lib/http-axios.ts b/lib/http-axios.ts index 1783be71..aa558e0a 100644 --- a/lib/http-axios.ts +++ b/lib/http-axios.ts @@ -7,6 +7,7 @@ import axios, { import { Readable } from "node:stream"; import { HTTPError, ReadError, RequestError } from "./exceptions.js"; import { USER_AGENT } from "./version.js"; +import { createURLSearchParams } from "./utils.js"; interface httpClientConfig extends Partial { baseURL?: string; @@ -86,12 +87,7 @@ export default class HTTPClient { } public async postForm(url: string, body?: any): Promise { - const params = new URLSearchParams(); - for (const key in body) { - if (body.hasOwnProperty(key)) { - params.append(key, body[key]); - } - } + const params = body ? createURLSearchParams(body) : new URLSearchParams(); const res = await this.instance.post(url, params.toString(), { headers: { "Content-Type": "application/x-www-form-urlencoded" }, }); diff --git a/lib/http-fetch.ts b/lib/http-fetch.ts index 257f975f..4e65efe3 100644 --- a/lib/http-fetch.ts +++ b/lib/http-fetch.ts @@ -2,6 +2,7 @@ import { Buffer } from "node:buffer"; import { Readable } from "node:stream"; import { HTTPFetchError } from "./exceptions.js"; import { USER_AGENT } from "./version.js"; +import { createURLSearchParams } from "./utils.js"; export interface FetchRequestConfig { headers?: Record; @@ -63,12 +64,7 @@ export default class HTTPFetchClient { public async get(url: string, params?: any): Promise { const requestUrl = new URL(url, this.baseURL); if (params) { - const searchParams = new URLSearchParams(); - for (const key in params) { - if (params.hasOwnProperty(key)) { - searchParams.append(key, params[key]); - } - } + const searchParams = createURLSearchParams(params); requestUrl.search = searchParams.toString(); } const response = await fetch(requestUrl, { @@ -118,12 +114,7 @@ export default class HTTPFetchClient { public async postForm(url: string, body?: any): Promise { const requestUrl = new URL(url, this.baseURL); - const params = new URLSearchParams(); - for (const key in body) { - if (body.hasOwnProperty(key)) { - params.append(key, body[key]); - } - } + const params = body ? createURLSearchParams(body) : new URLSearchParams(); const response = await fetch(requestUrl, { method: "POST", headers: { @@ -186,7 +177,8 @@ export default class HTTPFetchClient { public async delete(url: string, params?: any): Promise { const requestUrl = new URL(url, this.baseURL); if (params) { - requestUrl.search = new URLSearchParams(params).toString(); + const searchParams = createURLSearchParams(params); + requestUrl.search = searchParams.toString(); } const response = await fetch(requestUrl, { method: "DELETE", diff --git a/lib/utils.ts b/lib/utils.ts index d3e29903..1569bd17 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -25,6 +25,18 @@ function toArrayBuffer(input: Uint8Array | Buffer): ArrayBuffer { return arrayBuffer; } +export function createURLSearchParams( + params: Record, +): URLSearchParams { + const searchParams = new URLSearchParams(); + for (const [key, value] of Object.entries(params)) { + if (value != null) { + searchParams.append(key, String(value)); + } + } + return searchParams; +} + export function createMultipartFormData( this: FormData | void, formBody: Record, diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 0028d620..5af92925 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -1,4 +1,4 @@ -import { ensureJSON } from "../lib/utils.js"; +import { ensureJSON, createURLSearchParams } from "../lib/utils.js"; import { JSONParseError } from "../lib/exceptions.js"; import { equal, ok } from "node:assert"; @@ -19,4 +19,69 @@ describe("utils", () => { } }); }); + + describe("createURLSearchParams", () => { + it("creates URLSearchParams from object with string values", () => { + const params = { key1: "value1", key2: "value2" }; + const result = createURLSearchParams(params); + equal(result.get("key1"), "value1"); + equal(result.get("key2"), "value2"); + }); + + it("converts number values to strings", () => { + const params = { num: 123, float: 45.67 }; + const result = createURLSearchParams(params); + equal(result.get("num"), "123"); + equal(result.get("float"), "45.67"); + }); + + it("converts boolean values to strings", () => { + const params = { bool1: true, bool2: false }; + const result = createURLSearchParams(params); + equal(result.get("bool1"), "true"); + equal(result.get("bool2"), "false"); + }); + + it("filters out null values", () => { + const params = { key1: "value1", key2: null, key3: "value3" }; + const result = createURLSearchParams(params); + equal(result.get("key1"), "value1"); + equal(result.get("key2"), null); + equal(result.get("key3"), "value3"); + }); + + it("filters out undefined values", () => { + const params = { key1: "value1", key2: undefined, key3: "value3" }; + const result = createURLSearchParams(params); + equal(result.get("key1"), "value1"); + equal(result.get("key2"), null); + equal(result.get("key3"), "value3"); + }); + + it("handles empty object", () => { + const params = {}; + const result = createURLSearchParams(params); + equal(result.toString(), ""); + }); + + it("handles mixed types and null/undefined", () => { + const params = { + str: "test", + num: 42, + bool: true, + nullVal: null, + undefinedVal: undefined, + zero: 0, + emptyStr: "", + }; + const result = createURLSearchParams(params); + equal(result.get("str"), "test"); + equal(result.get("num"), "42"); + equal(result.get("bool"), "true"); + equal(result.get("zero"), "0"); + equal(result.get("emptyStr"), ""); + equal(result.get("nullVal"), null); + equal(result.get("undefinedVal"), null); + }); + }); });