Skip to content

Commit 26be56e

Browse files
committed
refactor: replace path variable metadata type
1 parent 3419174 commit 26be56e

9 files changed

+117
-47
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { describe, it, expect } from "vitest";
2+
import { PathVariableBuilder } from "./path-variable.builder";
3+
4+
describe("PathVariableBuilder", () => {
5+
it("should replace url with given variable", () => {
6+
// given
7+
const builder = new PathVariableBuilder(0, "id");
8+
const args = [123];
9+
10+
// when
11+
const actual = builder.build("/user/{id}", args);
12+
13+
// then
14+
expect(actual).toBe("/user/123");
15+
});
16+
17+
it("should replace multiple variables in url", () => {
18+
// given
19+
const builder = new PathVariableBuilder(0, "id");
20+
builder.add(1, "name");
21+
const url = "/user/{id}/profile/{name}";
22+
const args = [123, "john"];
23+
24+
// when
25+
const actual = builder.build(url, args);
26+
27+
// then
28+
expect(actual).toBe("/user/123/profile/john");
29+
});
30+
31+
it("should handle url without variables", () => {
32+
// given
33+
const builder = new PathVariableBuilder(0, "id");
34+
const args = [123];
35+
36+
// when
37+
const actual = builder.build("/user/profile", args);
38+
39+
// then
40+
expect(actual).toBe("/user/profile");
41+
});
42+
43+
it("should handle variables not replaced in url", () => {
44+
// given
45+
const builder = new PathVariableBuilder(0, "id");
46+
const args = [123];
47+
48+
// when
49+
const actual = builder.build("/user/{id}/profile/{name}", args);
50+
51+
// then
52+
expect(actual).toBe("/user/123/profile/{name}");
53+
});
54+
55+
it("should replace multiple variables with same name", () => {
56+
// given
57+
const builder = new PathVariableBuilder(0, "id");
58+
const args = [123];
59+
60+
// when
61+
const actual = builder.build("/user/{id}/profile/{id}", args);
62+
63+
// then
64+
expect(actual).toBe("/user/123/profile/123");
65+
});
66+
});

lib/builders/path-variable.builder.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export class PathVariableBuilder {
2+
metadata: Array<[index: number, key: string]> = [];
3+
4+
constructor(index: number, key: string) {
5+
this.add(index, key);
6+
}
7+
8+
add(index: number, key: string): void {
9+
this.metadata.push([index, key]);
10+
}
11+
12+
build(url: string, args: any[]): string {
13+
return this.metadata.reduce<string>(
14+
(acc, [index, key]) =>
15+
acc.replace(new RegExp(`{${key}}`, "g"), args[index]),
16+
url
17+
);
18+
}
19+
}

lib/builders/request-param.builder.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,14 @@ export class RequestParamBuilder {
55
metadata: Array<[index: number, key: string | undefined]> = [];
66

77
constructor(index: number, key?: string) {
8-
this.metadata.push([index, key]);
9-
}
10-
11-
get length(): number {
12-
return this.metadata.length;
8+
this.add(index, key);
139
}
1410

1511
add(index: number, key?: string): void {
1612
this.metadata.push([index, key]);
1713
}
1814

1915
build(args: any[]): string {
20-
if (this.metadata.length === 0) {
21-
return "";
22-
}
23-
2416
const result = this.metadata.reduce<Record<string, any>>(
2517
(acc, [index, key]) => {
2618
if (key != null) {

lib/builders/url.builder.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { describe, test, expect } from "vitest";
2+
import { PathVariableBuilder } from "./path-variable.builder";
23
import { RequestParamBuilder } from "./request-param.builder";
34
import { UrlBuilder } from "./url.builder";
4-
import { type PathVariableMetadata } from "../decorators";
5-
import { MetadataMap } from "../types/metadata-map";
65

76
describe("UrlBuilder", () => {
87
test("should build with base url", () => {
@@ -24,7 +23,7 @@ describe("UrlBuilder", () => {
2423
const host = "https://example.com";
2524
const path = "api/users/{id}";
2625
const args = [1, 2];
27-
const pathParam: PathVariableMetadata = new MetadataMap([[1, "id"]]);
26+
const pathParam = new PathVariableBuilder(1, "id");
2827
const urlBuilder = new UrlBuilder(host, path, args, { pathParam });
2928

3029
// when

lib/builders/url.builder.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,34 @@
1+
import { type PathVariableBuilder } from "./path-variable.builder";
12
import { type RequestParamBuilder } from "./request-param.builder";
2-
import { type PathVariableMetadata } from "../decorators";
33

44
export class UrlBuilder {
5-
#pathParams: Array<[number, string]> = [];
5+
#pathVariableBuilder: PathVariableBuilder | undefined;
66
#requestParamBuilder: RequestParamBuilder | undefined;
77

88
constructor(
99
private readonly host: string,
1010
private readonly path: string,
1111
private readonly args: any[],
1212
metadata: {
13-
pathParam?: PathVariableMetadata;
13+
pathParam?: PathVariableBuilder;
1414
queryParam?: RequestParamBuilder;
1515
} = {}
1616
) {
1717
if (this.host.length === 0) {
1818
this.host = this.path;
1919
this.path = "";
2020
}
21-
this.#pathParams = metadata.pathParam?.toArray() ?? [];
21+
this.#pathVariableBuilder = metadata.pathParam;
2222
this.#requestParamBuilder = metadata.queryParam;
2323
}
2424

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

32-
private replacePathVariable(): string {
33-
return this.#pathParams.reduce(
34-
(url, [index, value]) =>
35-
url.replace(new RegExp(`{${value}}`, "g"), this.args[index]),
36-
this.url
37-
);
38-
}
39-
4032
get url(): string {
4133
if (this.path === "") {
4234
return this.host;
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { describe, test, expect } from "vitest";
22
import { PATH_VARIABLE_METADATA } from "./constants";
3-
import {
4-
PathVariable,
5-
type PathVariableMetadata,
6-
} from "./path-variable.decorator";
3+
import { PathVariable } from "./path-variable.decorator";
4+
import { type PathVariableBuilder } from "../builders/path-variable.builder";
75

86
describe("PathVariable", () => {
97
test("should set path variable metadata", () => {
@@ -18,15 +16,16 @@ describe("PathVariable", () => {
1816
}
1917

2018
// when
21-
const result: PathVariableMetadata = Reflect.getMetadata(
19+
const result: PathVariableBuilder = Reflect.getMetadata(
2220
PATH_VARIABLE_METADATA,
2321
TestService.prototype,
2422
"request"
2523
);
2624

2725
// then
28-
expect(result).toHaveLength(2);
29-
expect(result.get(0)).toBe("foo");
30-
expect(result.get(1)).toBe("bar");
26+
expect(result.metadata).toEqual([
27+
[1, "bar"],
28+
[0, "foo"],
29+
]);
3130
});
3231
});

lib/decorators/path-variable.decorator.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
import { PATH_VARIABLE_METADATA } from "./constants";
2-
import { MetadataMap } from "../types/metadata-map";
3-
4-
export type PathVariableMetadata = MetadataMap<number, string>;
2+
import { PathVariableBuilder } from "../builders/path-variable.builder";
53

64
export function PathVariable(name: string): ParameterDecorator {
75
return (target, propertyKey, parameterIndex) => {
8-
if (typeof propertyKey === "undefined") {
6+
if (propertyKey == null) {
97
return;
108
}
119

12-
const metadata: PathVariableMetadata =
13-
Reflect.getMetadata(PATH_VARIABLE_METADATA, target, propertyKey) ??
14-
new MetadataMap();
10+
const builder: PathVariableBuilder | undefined = Reflect.getMetadata(
11+
PATH_VARIABLE_METADATA,
12+
target,
13+
propertyKey
14+
);
1515

16-
metadata.set(parameterIndex, name);
16+
if (builder != null) {
17+
builder.add(parameterIndex, name);
18+
return;
19+
}
1720

1821
Reflect.defineMetadata(
1922
PATH_VARIABLE_METADATA,
20-
metadata,
23+
new PathVariableBuilder(parameterIndex, name),
2124
target,
2225
propertyKey
2326
);

lib/decorators/request-param.decorator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ export function RequestParam(key?: string): ParameterDecorator {
77
return;
88
}
99

10-
const metadata: RequestParamBuilder | undefined = Reflect.getMetadata(
10+
const builder: RequestParamBuilder | undefined = Reflect.getMetadata(
1111
REQUEST_PARAM_METADATA,
1212
target,
1313
propertyKey
1414
);
1515

16-
if (metadata != null) {
17-
metadata.add(parameterIndex, key);
16+
if (builder != null) {
17+
builder.add(parameterIndex, key);
1818
return;
1919
}
2020

lib/node-fetch.injector.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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";
45
import { type RequestParamBuilder } from "./builders/request-param.builder";
56
import { TupleArrayBuilder } from "./builders/tuple-array.builder";
67
import { UrlBuilder } from "./builders/url.builder";
@@ -9,7 +10,6 @@ import {
910
HTTP_INTERFACE_METADATA,
1011
type HttpExchangeMetadata,
1112
PATH_VARIABLE_METADATA,
12-
type PathVariableMetadata,
1313
REQUEST_BODY_METADATA,
1414
REQUEST_PARAM_METADATA,
1515
type RequestBodyMetadata,
@@ -50,7 +50,7 @@ export class NodeFetchInjector implements OnModuleInit {
5050
return;
5151
}
5252

53-
const pathMetadata = getMetadata<PathVariableMetadata>(
53+
const pathMetadata = getMetadata<PathVariableBuilder>(
5454
PATH_VARIABLE_METADATA
5555
);
5656
const requestParamMetadata = getMetadata<RequestParamBuilder>(

0 commit comments

Comments
 (0)