Skip to content

Commit 13795b0

Browse files
jbl428imdudu1
andcommitted
feat: implement appending query string
Co-authored-by: imdudu1 <[email protected]>
1 parent c1c0200 commit 13795b0

File tree

2 files changed

+90
-8
lines changed

2 files changed

+90
-8
lines changed

lib/node-fetch.injector.spec.ts

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { MetadataScanner } from "@nestjs/core";
22
import { beforeEach, describe, test, expect } from "vitest";
3-
import { GetExchange, HttpInterface, PathVariable } from "./decorators";
3+
import {
4+
GetExchange,
5+
HttpInterface,
6+
PathVariable,
7+
RequestParam,
8+
} from "./decorators";
49
import { StubDiscoveryService } from "./fixture/stub-discovery.service";
510
import { StubHttpClient } from "./fixture/stub-http-client";
611
import { NodeFetchInjector } from "./node-fetch.injector";
@@ -53,10 +58,9 @@ describe("NodeFetchInjector", () => {
5358
nodeFetchInjector.onModuleInit();
5459

5560
// when
56-
const actual = await instance.request();
61+
await instance.request();
5762

5863
// then
59-
expect(actual).toEqual({ status: "ok" });
6064
expect(httpClient.requestInfo).toHaveLength(1);
6165
expect(httpClient.requestInfo[0].url).toBe("https://example.com/api");
6266
});
@@ -75,10 +79,9 @@ describe("NodeFetchInjector", () => {
7579
nodeFetchInjector.onModuleInit();
7680

7781
// when
78-
const actual = await instance.request("1");
82+
await instance.request("1");
7983

8084
// then
81-
expect(actual).toEqual({ status: "ok" });
8285
expect(httpClient.requestInfo).toHaveLength(1);
8386
expect(httpClient.requestInfo[0].url).toBe(
8487
"https://example.com/api/users/1/1"
@@ -102,13 +105,60 @@ describe("NodeFetchInjector", () => {
102105
nodeFetchInjector.onModuleInit();
103106

104107
// when
105-
const actual = await instance.request("123", "active");
108+
await instance.request("123", "active");
106109

107110
// then
108-
expect(actual).toEqual({ status: "ok" });
109111
expect(httpClient.requestInfo).toHaveLength(1);
110112
expect(httpClient.requestInfo[0].url).toBe(
111113
"https://example.com/api/users/123/active"
112114
);
113115
});
116+
117+
test("should append query sting to url", async () => {
118+
// given
119+
@HttpInterface()
120+
class SampleClient {
121+
@GetExchange("https://example.com/api")
122+
async request(@RequestParam("keyword") keyword: string): Promise<string> {
123+
return "request";
124+
}
125+
}
126+
const instance = discoveryService.addProvider(SampleClient);
127+
httpClient.addResponse({ status: "ok" });
128+
nodeFetchInjector.onModuleInit();
129+
130+
// when
131+
await instance.request("search");
132+
133+
// then
134+
expect(httpClient.requestInfo).toHaveLength(1);
135+
expect(httpClient.requestInfo[0].url).toBe(
136+
"https://example.com/api?keyword=search"
137+
);
138+
});
139+
140+
test("should append query sting to url when provided as json", async () => {
141+
// given
142+
@HttpInterface()
143+
class SampleClient {
144+
@GetExchange("https://example.com/api")
145+
async request(
146+
@RequestParam() params: Record<string, unknown>
147+
): Promise<string> {
148+
return "request";
149+
}
150+
}
151+
const instance = discoveryService.addProvider(SampleClient);
152+
httpClient.addResponse({ status: "ok" });
153+
nodeFetchInjector.onModuleInit();
154+
155+
// when
156+
await instance.request({ keyword: "search", page: 1, isActive: true });
157+
158+
// then
159+
expect(httpClient.requestInfo).toHaveLength(1);
160+
expect(httpClient.requestInfo[0].url).toBe(
161+
"https://example.com/api?keyword=search&page=1&isActive=true"
162+
);
163+
});
114164
});

lib/node-fetch.injector.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
type HttpExchangeMetadata,
88
PATH_VARIABLE_METADATA,
99
type PathVariableMetadata,
10+
REQUEST_PARAM_METADATA,
11+
type RequestParamMetadata,
1012
} from "./decorators";
1113
import { HttpClient } from "./types/http-client.interface";
1214

@@ -43,13 +45,31 @@ export class NodeFetchInjector implements OnModuleInit {
4345
PATH_VARIABLE_METADATA
4446
);
4547

48+
const requestParamMetadata = getMetadata<RequestParamMetadata>(
49+
REQUEST_PARAM_METADATA
50+
);
51+
4652
wrapper.instance[methodName] = async (...args: any[]) => {
4753
const url = [...(pathMetadata?.entries() ?? [])].reduce(
4854
(url, [index, value]) =>
4955
url.replace(new RegExp(`{${value}}`, "g"), args[index]),
5056
`${baseUrl}${httpExchangeMetadata.url}`
5157
);
52-
const request = new Request(url);
58+
const searchParams = new URLSearchParams();
59+
requestParamMetadata?.forEach((queryParamKey, paramIndex) => {
60+
if (typeof queryParamKey === "undefined") {
61+
this.toArray(args[paramIndex]).forEach(([key, value]) => {
62+
searchParams.set(key, `${value?.toString() ?? ""}`);
63+
});
64+
65+
return;
66+
}
67+
searchParams.set(queryParamKey, args[paramIndex]);
68+
});
69+
70+
const request = new Request(
71+
[url, searchParams.toString()].filter(Boolean).join("?")
72+
);
5373

5474
return await this.httpClient
5575
.request(request)
@@ -59,6 +79,18 @@ export class NodeFetchInjector implements OnModuleInit {
5979
});
6080
}
6181

82+
private toArray<T>(value: T): Array<[string, unknown]> {
83+
if (value instanceof Map) {
84+
return [...value.values()];
85+
}
86+
87+
if (typeof value === "object" && value !== null) {
88+
return Object.entries(value);
89+
}
90+
91+
return [];
92+
}
93+
6294
private getHttpProviders(): InstanceWrapper[] {
6395
return this.discoveryService.getProviders().filter((wrapper) => {
6496
const metadata = Reflect.getMetadata(

0 commit comments

Comments
 (0)