|
| 1 | +import { |
| 2 | + type PathVariableMetadata, |
| 3 | + type RequestParamMetadata, |
| 4 | +} from "../decorators"; |
| 5 | + |
| 6 | +export class URLBuilder { |
| 7 | + #pathParams: Array<[number, string]> = []; |
| 8 | + #queryParams: Array<[number, string | undefined]> = []; |
| 9 | + |
| 10 | + constructor( |
| 11 | + private readonly host: string, |
| 12 | + private readonly path: string, |
| 13 | + private readonly args: any[], |
| 14 | + metadata: { |
| 15 | + pathParam?: PathVariableMetadata; |
| 16 | + queryParam?: RequestParamMetadata; |
| 17 | + } = {} |
| 18 | + ) { |
| 19 | + this.#pathParams = this.toArray(metadata.pathParam); |
| 20 | + this.#queryParams = this.toArray(metadata.queryParam); |
| 21 | + } |
| 22 | + |
| 23 | + build(): string { |
| 24 | + return this.replacePathVariable() + this.appendQueryParams(); |
| 25 | + } |
| 26 | + |
| 27 | + private replacePathVariable(): string { |
| 28 | + return this.#pathParams.reduce( |
| 29 | + (url, [index, value]) => |
| 30 | + url.replace(new RegExp(`{${value}}`, "g"), this.args[index]), |
| 31 | + this.url |
| 32 | + ); |
| 33 | + } |
| 34 | + |
| 35 | + private appendQueryParams(): string { |
| 36 | + if (this.#queryParams.length === 0) { |
| 37 | + return ""; |
| 38 | + } |
| 39 | + |
| 40 | + const searchParams = new URLSearchParams(); |
| 41 | + this.#queryParams.forEach(([paramIndex, queryParamKey]) => { |
| 42 | + if (typeof queryParamKey !== "undefined") { |
| 43 | + searchParams.set(queryParamKey, this.args[paramIndex]); |
| 44 | + return; |
| 45 | + } |
| 46 | + |
| 47 | + this.toArray<string, unknown>(this.args[paramIndex]).forEach( |
| 48 | + ([key, value]) => { |
| 49 | + searchParams.set(key, `${value?.toString() ?? ""}`); |
| 50 | + } |
| 51 | + ); |
| 52 | + }); |
| 53 | + |
| 54 | + return "?" + searchParams.toString(); |
| 55 | + } |
| 56 | + |
| 57 | + get url(): string { |
| 58 | + if (this.path === "") { |
| 59 | + return this.host; |
| 60 | + } |
| 61 | + |
| 62 | + if (this.isStartProtocol()) { |
| 63 | + const [protocol, host] = this.host.split("://"); |
| 64 | + |
| 65 | + return protocol + "://" + this.replaceSlash(`${host}/${this.path}`); |
| 66 | + } |
| 67 | + |
| 68 | + return this.replaceSlash(`${this.host}/${this.path}`); |
| 69 | + } |
| 70 | + |
| 71 | + private isStartProtocol(): boolean { |
| 72 | + return this.host.match(/^https?:\/\//) != null; |
| 73 | + } |
| 74 | + |
| 75 | + private replaceSlash(url: string): string { |
| 76 | + return url.replace(/\/{2,}/g, "/"); |
| 77 | + } |
| 78 | + |
| 79 | + private toArray<A, B>(value: unknown): Array<[A, B]> { |
| 80 | + if (typeof value === "undefined") { |
| 81 | + return []; |
| 82 | + } |
| 83 | + |
| 84 | + if (value instanceof Map) { |
| 85 | + return [...value.entries()]; |
| 86 | + } |
| 87 | + |
| 88 | + if (typeof value === "object" && value !== null) { |
| 89 | + return Object.entries(value) as Array<[A, B]>; |
| 90 | + } |
| 91 | + |
| 92 | + return []; |
| 93 | + } |
| 94 | +} |
0 commit comments