import type { ApiOperation } from "../../contracts/api-operations.js"; export type OperationRequestInput = Readonly<{ operationId: string; routeId: string; pathParams?: Readonly>; searchParams?: unknown; body?: unknown; signal?: AbortSignal; idempotencyKey?: string; correlationId?: string; }>; export type RequestTargetResult = | Readonly<{ success: true; url: URL }> | Readonly<{ success: false; code: "PATH_PARAMETER_MISSING" | "SEARCH_PARAMETER_INVALID"; }>; const pathParameterPattern = /:([A-Za-z][A-Za-z0-9_]*)|\{([A-Za-z][A-Za-z0-9_]*)\}/g; export function buildRequestTarget( baseUrl: string, operation: ApiOperation, pathParams: Readonly> = {}, parsedSearch: unknown = {}, ): RequestTargetResult { let missingPathParameter = false; const pathname = operation.path.replace( pathParameterPattern, (_token, colonName: string | undefined, braceName: string | undefined) => { const name = colonName ?? braceName ?? ""; const value = pathParams[name]; if (value === undefined) { missingPathParameter = true; return ""; } return encodeURIComponent(String(value)); }, ); if (missingPathParameter) { return { success: false, code: "PATH_PARAMETER_MISSING" }; } if ( parsedSearch === null || typeof parsedSearch !== "object" || Array.isArray(parsedSearch) ) { return { success: false, code: "SEARCH_PARAMETER_INVALID" }; } const url = new URL(pathname, baseUrl); const search = parsedSearch as Readonly>; for (const key of Object.keys(search).sort((left, right) => left.localeCompare(right), )) { const value = search[key]; if (value === undefined || value === null) continue; const values = Array.isArray(value) ? value : [value]; for (const item of values) { if ( typeof item !== "string" && typeof item !== "number" && typeof item !== "boolean" ) { return { success: false, code: "SEARCH_PARAMETER_INVALID" }; } url.searchParams.append(key, String(item)); } } return { success: true, url }; }