Skip to content

Commit 64f3460

Browse files
committed
refactor: separated request creation logic into a new builder
1 parent 26be56e commit 64f3460

File tree

6 files changed

+99
-108
lines changed

6 files changed

+99
-108
lines changed

lib/builders/http-request.builder.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { type PathVariableBuilder } from "./path-variable.builder";
2+
import { type RequestParamBuilder } from "./request-param.builder";
3+
import { TupleArrayBuilder } from "./tuple-array.builder";
4+
import { UrlBuilder } from "./url.builder";
5+
import {
6+
PATH_VARIABLE_METADATA,
7+
REQUEST_BODY_METADATA,
8+
REQUEST_PARAM_METADATA,
9+
type RequestBodyMetadata,
10+
} from "../decorators";
11+
import { type HttpMethod } from "../types/http-method";
12+
13+
export class HttpRequestBuilder {
14+
private baseUrl = "";
15+
private readonly pathVariableBuilder: PathVariableBuilder | undefined;
16+
private readonly requestParamBuilder: RequestParamBuilder | undefined;
17+
private readonly requestBodyMetadata: RequestBodyMetadata | undefined;
18+
19+
constructor(
20+
readonly target: object,
21+
readonly propertyKey: string,
22+
readonly method: HttpMethod,
23+
readonly url: string
24+
) {
25+
this.pathVariableBuilder = this.getMetadata(PATH_VARIABLE_METADATA);
26+
this.requestParamBuilder = this.getMetadata(REQUEST_PARAM_METADATA);
27+
this.requestBodyMetadata = this.getMetadata(REQUEST_BODY_METADATA);
28+
}
29+
30+
setBaseUrl(baseUrl: string): void {
31+
this.baseUrl = baseUrl;
32+
}
33+
34+
build(args: any[]): Request {
35+
const payload = this.requestBodyMetadata
36+
?.toArray()
37+
.reduce((acc: Record<string, unknown>, [index, value]: [number, any]) => {
38+
if (typeof value !== "undefined") {
39+
acc[value] = args[index];
40+
return acc;
41+
}
42+
43+
TupleArrayBuilder.of<string, unknown>(args[index]).forEach(([k, v]) => {
44+
acc[k] = v;
45+
});
46+
47+
return acc;
48+
}, {});
49+
const urlBuilder = new UrlBuilder(this.baseUrl, this.url, args, {
50+
pathParam: this.pathVariableBuilder,
51+
queryParam: this.requestParamBuilder,
52+
});
53+
54+
return new Request(urlBuilder.build(), {
55+
method: this.method,
56+
headers:
57+
typeof payload !== "undefined"
58+
? { "Content-Type": "application/json" }
59+
: undefined,
60+
body:
61+
typeof payload !== "undefined" ? JSON.stringify(payload) : undefined,
62+
});
63+
}
64+
65+
getMetadata<T>(key: symbol): T | undefined {
66+
return Reflect.getMetadata(key, this.target, this.propertyKey);
67+
}
68+
}

lib/builders/url.builder.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { type PathVariableBuilder } from "./path-variable.builder";
22
import { type RequestParamBuilder } from "./request-param.builder";
33

44
export class UrlBuilder {
5-
#pathVariableBuilder: PathVariableBuilder | undefined;
6-
#requestParamBuilder: RequestParamBuilder | undefined;
5+
private readonly pathVariableBuilder: PathVariableBuilder | undefined;
6+
private readonly requestParamBuilder: RequestParamBuilder | undefined;
77

88
constructor(
99
private readonly host: string,
@@ -18,14 +18,14 @@ export class UrlBuilder {
1818
this.host = this.path;
1919
this.path = "";
2020
}
21-
this.#pathVariableBuilder = metadata.pathParam;
22-
this.#requestParamBuilder = metadata.queryParam;
21+
this.pathVariableBuilder = metadata.pathParam;
22+
this.requestParamBuilder = metadata.queryParam;
2323
}
2424

2525
build(): string {
2626
return (
27-
(this.#pathVariableBuilder?.build(this.url, this.args) ?? this.url) +
28-
(this.#requestParamBuilder?.build(this.args) ?? "")
27+
(this.pathVariableBuilder?.build(this.url, this.args) ?? this.url) +
28+
(this.requestParamBuilder?.build(this.args) ?? "")
2929
);
3030
}
3131

lib/decorators/http-exchange.decorator.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { describe, test, expect } from "vitest";
22
import { HTTP_EXCHANGE_METADATA } from "./constants";
33
import {
4-
type HttpExchangeMetadata,
54
GetExchange,
65
PostExchange,
76
PutExchange,
@@ -10,6 +9,7 @@ import {
109
PatchExchange,
1110
HeadExchange,
1211
} from "./http-exchange.decorator";
12+
import { type HttpRequestBuilder } from "../builders/http-request.builder";
1313

1414
describe("HttpExchange", () => {
1515
test.each([
@@ -30,13 +30,15 @@ describe("HttpExchange", () => {
3030
}
3131

3232
// when
33-
const result: HttpExchangeMetadata = Reflect.getMetadata(
33+
const result: HttpRequestBuilder = Reflect.getMetadata(
3434
HTTP_EXCHANGE_METADATA,
3535
TestService.prototype,
3636
"request"
3737
);
3838

3939
// then
40+
expect(result.target).toBe(TestService.prototype);
41+
expect(result.propertyKey).toBe("request");
4042
expect(result.method).toBe(method);
4143
expect(result.url).toBe("/api/v1/sample");
4244
});

lib/decorators/http-exchange.decorator.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
import { HTTP_EXCHANGE_METADATA } from "./constants";
2-
3-
type HttpMethod =
4-
| "GET"
5-
| "POST"
6-
| "PUT"
7-
| "DELETE"
8-
| "PATCH"
9-
| "HEAD"
10-
| "OPTIONS";
11-
12-
export interface HttpExchangeMetadata {
13-
method: HttpMethod;
14-
url: string;
15-
}
2+
import { HttpRequestBuilder } from "../builders/http-request.builder";
3+
import { type HttpMethod } from "../types/http-method";
164

175
type AsyncFunction = (...args: any[]) => Promise<unknown>;
186

@@ -23,7 +11,7 @@ export function HttpExchange(method: HttpMethod, url: string) {
2311
) {
2412
Reflect.defineMetadata(
2513
HTTP_EXCHANGE_METADATA,
26-
{ method, url },
14+
new HttpRequestBuilder(target, propertyKey, method, url),
2715
target,
2816
propertyKey
2917
);

lib/node-fetch.injector.ts

Lines changed: 10 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
import { Injectable, type OnModuleInit } from "@nestjs/common";
22
import { DiscoveryService, MetadataScanner } from "@nestjs/core";
33
import { type InstanceWrapper } from "@nestjs/core/injector/instance-wrapper";
4-
import { type PathVariableBuilder } from "./builders/path-variable.builder";
5-
import { type RequestParamBuilder } from "./builders/request-param.builder";
6-
import { TupleArrayBuilder } from "./builders/tuple-array.builder";
7-
import { UrlBuilder } from "./builders/url.builder";
8-
import {
9-
HTTP_EXCHANGE_METADATA,
10-
HTTP_INTERFACE_METADATA,
11-
type HttpExchangeMetadata,
12-
PATH_VARIABLE_METADATA,
13-
REQUEST_BODY_METADATA,
14-
REQUEST_PARAM_METADATA,
15-
type RequestBodyMetadata,
16-
} from "./decorators";
4+
import { type HttpRequestBuilder } from "./builders/http-request.builder";
5+
import { HTTP_EXCHANGE_METADATA, HTTP_INTERFACE_METADATA } from "./decorators";
176
import { HttpClient } from "./types/http-client.interface";
187

198
@Injectable()
@@ -34,82 +23,26 @@ export class NodeFetchInjector implements OnModuleInit {
3423
prototype
3524
);
3625

37-
if (typeof baseUrl === "undefined") {
26+
if (baseUrl == null) {
3827
return;
3928
}
4029

4130
this.metadataScanner
4231
.getAllMethodNames(prototype)
4332
.forEach((methodName) => {
44-
const getMetadata = this.makeMetadataGetter(prototype, methodName);
45-
const httpExchangeMetadata = getMetadata<HttpExchangeMetadata>(
46-
HTTP_EXCHANGE_METADATA
47-
);
33+
const httpRequestBuilder: HttpRequestBuilder | undefined =
34+
Reflect.getMetadata(HTTP_EXCHANGE_METADATA, prototype, methodName);
4835

49-
if (typeof httpExchangeMetadata === "undefined") {
36+
if (httpRequestBuilder == null) {
5037
return;
5138
}
5239

53-
const pathMetadata = getMetadata<PathVariableBuilder>(
54-
PATH_VARIABLE_METADATA
55-
);
56-
const requestParamMetadata = getMetadata<RequestParamBuilder>(
57-
REQUEST_PARAM_METADATA
58-
);
59-
const requestBodyMetadata = getMetadata<RequestBodyMetadata>(
60-
REQUEST_BODY_METADATA
61-
);
40+
httpRequestBuilder.setBaseUrl(baseUrl);
6241

63-
wrapper.instance[methodName] = async (...args: never[]) => {
64-
const payload = requestBodyMetadata
65-
?.toArray()
66-
.reduce(
67-
(
68-
acc: Record<string, unknown>,
69-
[index, value]: [number, any]
70-
) => {
71-
if (typeof value !== "undefined") {
72-
acc[value] = args[index];
73-
return acc;
74-
}
75-
76-
TupleArrayBuilder.of<string, unknown>(args[index]).forEach(
77-
([k, v]) => {
78-
acc[k] = v;
79-
}
80-
);
81-
82-
return acc;
83-
},
84-
{}
85-
);
86-
87-
const urlBuilder = new UrlBuilder(
88-
baseUrl,
89-
httpExchangeMetadata.url,
90-
args,
91-
{
92-
pathParam: pathMetadata,
93-
queryParam: requestParamMetadata,
94-
}
95-
);
96-
97-
return await this.httpClient
98-
.request(
99-
new Request(urlBuilder.build(), {
100-
method: httpExchangeMetadata.method,
101-
headers:
102-
typeof payload !== "undefined"
103-
? { "Content-Type": "application/json" }
104-
: undefined,
105-
body:
106-
typeof payload !== "undefined"
107-
? JSON.stringify(payload)
108-
: undefined,
109-
})
110-
)
42+
wrapper.instance[methodName] = async (...args: never[]) =>
43+
await this.httpClient
44+
.request(httpRequestBuilder.build(args))
11145
.then(async (response) => await response.json());
112-
};
11346
});
11447
});
11548
}
@@ -123,12 +56,4 @@ export class NodeFetchInjector implements OnModuleInit {
12356
return typeof metadata !== "undefined" || metadata !== null;
12457
});
12558
}
126-
127-
private makeMetadataGetter(
128-
prototype: object,
129-
methodName: string
130-
): <T>(key: symbol) => T | undefined {
131-
return (metadataKey: symbol) =>
132-
Reflect.getMetadata(metadataKey, prototype, methodName);
133-
}
13459
}

lib/types/http-method.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export type HttpMethod =
2+
| "GET"
3+
| "POST"
4+
| "PUT"
5+
| "DELETE"
6+
| "PATCH"
7+
| "HEAD"
8+
| "OPTIONS";

0 commit comments

Comments
 (0)