Skip to content

Commit 5be6910

Browse files
committed
refactor: separated body creation logic into a new builder
1 parent 64f3460 commit 5be6910

9 files changed

+123
-158
lines changed

lib/builders/http-request.builder.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import { type PathVariableBuilder } from "./path-variable.builder";
2+
import { type RequestBodyBuilder } from "./request-body.builder";
23
import { type RequestParamBuilder } from "./request-param.builder";
3-
import { TupleArrayBuilder } from "./tuple-array.builder";
44
import { UrlBuilder } from "./url.builder";
55
import {
66
PATH_VARIABLE_METADATA,
77
REQUEST_BODY_METADATA,
88
REQUEST_PARAM_METADATA,
9-
type RequestBodyMetadata,
109
} from "../decorators";
1110
import { type HttpMethod } from "../types/http-method";
1211

1312
export class HttpRequestBuilder {
1413
private baseUrl = "";
1514
private readonly pathVariableBuilder: PathVariableBuilder | undefined;
1615
private readonly requestParamBuilder: RequestParamBuilder | undefined;
17-
private readonly requestBodyMetadata: RequestBodyMetadata | undefined;
16+
private readonly requestBodyBuilder: RequestBodyBuilder | undefined;
1817

1918
constructor(
2019
readonly target: object,
@@ -24,28 +23,15 @@ export class HttpRequestBuilder {
2423
) {
2524
this.pathVariableBuilder = this.getMetadata(PATH_VARIABLE_METADATA);
2625
this.requestParamBuilder = this.getMetadata(REQUEST_PARAM_METADATA);
27-
this.requestBodyMetadata = this.getMetadata(REQUEST_BODY_METADATA);
26+
this.requestBodyBuilder = this.getMetadata(REQUEST_BODY_METADATA);
2827
}
2928

3029
setBaseUrl(baseUrl: string): void {
3130
this.baseUrl = baseUrl;
3231
}
3332

3433
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-
}, {});
34+
const payload = this.requestBodyBuilder?.build(args);
4935
const urlBuilder = new UrlBuilder(this.baseUrl, this.url, args, {
5036
pathParam: this.pathVariableBuilder,
5137
queryParam: this.requestParamBuilder,
@@ -54,11 +40,10 @@ export class HttpRequestBuilder {
5440
return new Request(urlBuilder.build(), {
5541
method: this.method,
5642
headers:
57-
typeof payload !== "undefined"
43+
payload !== undefined
5844
? { "Content-Type": "application/json" }
5945
: undefined,
60-
body:
61-
typeof payload !== "undefined" ? JSON.stringify(payload) : undefined,
46+
body: payload,
6247
});
6348
}
6449

lib/builders/path-variable.builder.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { describe, it, expect } from "vitest";
1+
import { describe, test, expect } from "vitest";
22
import { PathVariableBuilder } from "./path-variable.builder";
33

44
describe("PathVariableBuilder", () => {
5-
it("should replace url with given variable", () => {
5+
test("should replace url with given variable", () => {
66
// given
77
const builder = new PathVariableBuilder(0, "id");
88
const args = [123];
@@ -14,7 +14,7 @@ describe("PathVariableBuilder", () => {
1414
expect(actual).toBe("/user/123");
1515
});
1616

17-
it("should replace multiple variables in url", () => {
17+
test("should replace multiple variables in url", () => {
1818
// given
1919
const builder = new PathVariableBuilder(0, "id");
2020
builder.add(1, "name");
@@ -28,7 +28,7 @@ describe("PathVariableBuilder", () => {
2828
expect(actual).toBe("/user/123/profile/john");
2929
});
3030

31-
it("should handle url without variables", () => {
31+
test("should handle url without variables", () => {
3232
// given
3333
const builder = new PathVariableBuilder(0, "id");
3434
const args = [123];
@@ -40,7 +40,7 @@ describe("PathVariableBuilder", () => {
4040
expect(actual).toBe("/user/profile");
4141
});
4242

43-
it("should handle variables not replaced in url", () => {
43+
test("should handle variables not replaced in url", () => {
4444
// given
4545
const builder = new PathVariableBuilder(0, "id");
4646
const args = [123];
@@ -52,7 +52,7 @@ describe("PathVariableBuilder", () => {
5252
expect(actual).toBe("/user/123/profile/{name}");
5353
});
5454

55-
it("should replace multiple variables with same name", () => {
55+
test("should replace multiple variables with same name", () => {
5656
// given
5757
const builder = new PathVariableBuilder(0, "id");
5858
const args = [123];

lib/builders/payload-builder.spec.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

lib/builders/payload-builder.ts

Lines changed: 0 additions & 92 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, test, expect } from "vitest";
2+
import { RequestBodyBuilder } from "./request-body.builder";
3+
4+
describe("RequestBodyBuilder", () => {
5+
test("should build json string with explicit key", () => {
6+
// given
7+
const builder = new RequestBodyBuilder(0, "keyword");
8+
const args = ["search"];
9+
10+
// when
11+
const actual = builder.build(args);
12+
13+
// then
14+
expect(actual).toBe('{"keyword":"search"}');
15+
});
16+
17+
test("should build json string without key", () => {
18+
// given
19+
const builder = new RequestBodyBuilder(1);
20+
const args = ["invalid", { foo: "bar" }];
21+
22+
// when
23+
const actual = builder.build(args);
24+
25+
// then
26+
expect(actual).toBe('{"foo":"bar"}');
27+
});
28+
});

lib/builders/request-body.builder.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { TupleArrayBuilder } from "./tuple-array.builder";
2+
3+
export class RequestBodyBuilder {
4+
metadata: Array<[index: number, key: string | undefined]> = [];
5+
6+
constructor(index: number, key?: string) {
7+
this.add(index, key);
8+
}
9+
10+
add(index: number, key?: string): void {
11+
this.metadata.push([index, key]);
12+
}
13+
14+
build(args: any[]): string {
15+
const result = this.metadata.reduce<Record<string, any>>(
16+
(acc, [index, key]) => {
17+
if (key != null) {
18+
acc[key] = args[index];
19+
return acc;
20+
}
21+
22+
TupleArrayBuilder.of<string, unknown>(args[index]).forEach(
23+
([key, value]) => {
24+
acc[key] = value;
25+
}
26+
);
27+
28+
return acc;
29+
},
30+
{}
31+
);
32+
33+
return JSON.stringify(result);
34+
}
35+
}

lib/builders/request-param.builder.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { describe, it, expect } from "vitest";
1+
import { describe, test, expect } from "vitest";
22
import { RequestParamBuilder } from "./request-param.builder";
33

44
describe("RequestParamBuilder", () => {
5-
it("should build query string with explicit key", () => {
5+
test("should build query string with explicit key", () => {
66
// given
77
const builder = new RequestParamBuilder(0, "keyword");
88
const args = ["search"];
@@ -14,7 +14,7 @@ describe("RequestParamBuilder", () => {
1414
expect(actual).toBe("?keyword=search");
1515
});
1616

17-
it("should build query string without key", () => {
17+
test("should build query string without key", () => {
1818
// given
1919
const builder = new RequestParamBuilder(1);
2020
const args = ["invalid", { foo: "bar" }];
@@ -26,7 +26,7 @@ describe("RequestParamBuilder", () => {
2626
expect(actual).toBe("?foo=bar");
2727
});
2828

29-
it("should encode query string", () => {
29+
test("should encode query string", () => {
3030
// given
3131
const builder = new RequestParamBuilder(0, "keyword");
3232
const args = ["?@#$%^&+ "];
Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { describe, test, expect } from "vitest";
22
import { REQUEST_BODY_METADATA } from "./constants";
3-
import {
4-
RequestBody,
5-
type RequestBodyMetadata,
6-
} from "./request-body.decorator";
3+
import { RequestBody } from "./request-body.decorator";
4+
import { type RequestBodyBuilder } from "../builders/request-body.builder";
5+
import { type RequestParamBuilder } from "../builders/request-param.builder";
76

87
describe("RequestBody", () => {
98
test("should set request body metadata with empty key", () => {
@@ -15,15 +14,14 @@ describe("RequestBody", () => {
1514
}
1615

1716
// when
18-
const result: RequestBodyMetadata = Reflect.getMetadata(
17+
const result: RequestBodyBuilder = Reflect.getMetadata(
1918
REQUEST_BODY_METADATA,
2019
TestService.prototype,
2120
"request"
2221
);
2322

2423
// then
25-
expect(result).toHaveLength(1);
26-
expect(result.get(0)).toBeUndefined();
24+
expect(result.metadata).toEqual([[0, undefined]]);
2725
});
2826

2927
test("should set request body metadata with key", () => {
@@ -35,14 +33,38 @@ describe("RequestBody", () => {
3533
}
3634

3735
// when
38-
const result: RequestBodyMetadata = Reflect.getMetadata(
36+
const result: RequestBodyBuilder = Reflect.getMetadata(
3937
REQUEST_BODY_METADATA,
4038
TestService.prototype,
4139
"request"
4240
);
4341

4442
// then
45-
expect(result).toHaveLength(1);
46-
expect(result.get(0)).toBe("foo");
43+
expect(result.metadata).toEqual([[0, "foo"]]);
44+
});
45+
46+
test("should set request body metadata with multiple decorator", () => {
47+
// given
48+
class TestService {
49+
request(
50+
@RequestBody("foo") foo: string,
51+
@RequestBody() bar: { bar: string }
52+
): string {
53+
return foo;
54+
}
55+
}
56+
57+
// when
58+
const result: RequestParamBuilder = Reflect.getMetadata(
59+
REQUEST_BODY_METADATA,
60+
TestService.prototype,
61+
"request"
62+
);
63+
64+
// then
65+
expect(result.metadata).toEqual([
66+
[1, undefined],
67+
[0, "foo"],
68+
]);
4769
});
4870
});

0 commit comments

Comments
 (0)