import {HttpParams, HttpParameterCodec} from '@angular/common/http'; import {CustomHttpParameterCodec, IdentityHttpParameterCodec} from './encoder'; export enum QueryParamStyle { Json, Form, DeepObject, SpaceDelimited, PipeDelimited, } export type Delimiter = "," | " " | "|" | "\t"; export interface ParamOptions { /** When true, serialized as multiple repeated key=value pairs. When false, serialized as a single key with joined values using `delimiter`. */ explode?: boolean; /** Delimiter used when explode=false. The delimiter itself is inserted unencoded between encoded values. */ delimiter?: Delimiter; } interface ParamEntry { values: string[]; options: Required; } export class OpenApiHttpParams { private params: Map = new Map(); private defaults: Required; private encoder: HttpParameterCodec; /** * @param encoder Parameter serializer * @param defaults Global defaults used when a specific parameter has no explicit options. * By OpenAPI default, explode is true for query params with style=form. */ constructor(encoder?: HttpParameterCodec, defaults?: { explode?: boolean; delimiter?: Delimiter }) { this.encoder = encoder || new CustomHttpParameterCodec(); this.defaults = { explode: defaults?.explode ?? true, delimiter: defaults?.delimiter ?? ",", }; } private resolveOptions(local?: ParamOptions): Required { return { explode: local?.explode ?? this.defaults.explode, delimiter: local?.delimiter ?? this.defaults.delimiter, }; } /** * Replace the parameter's values and (optionally) its options. * Options are stored per-parameter (not global). */ set(key: string, values: string[] | string, options?: ParamOptions): this { const arr = Array.isArray(values) ? values.slice() : [values]; const opts = this.resolveOptions(options); this.params.set(key, {values: arr, options: opts}); return this; } /** * Append a single value to the parameter. If the parameter didn't exist it will be created * and use resolved options (global defaults merged with any provided options). */ append(key: string, value: string, options?: ParamOptions): this { const entry = this.params.get(key); if (entry) { // If new options provided, override the stored options for subsequent serialization if (options) { entry.options = this.resolveOptions({...entry.options, ...options}); } entry.values.push(value); } else { this.set(key, [value], options); } return this; } /** * Serialize to a query string according to per-parameter OpenAPI options. * - If explode=true for that parameter → repeated key=value pairs (each value encoded). * - If explode=false for that parameter → single key=value where values are individually encoded * and joined using the configured delimiter. The delimiter character is inserted AS-IS * (not percent-encoded). */ toString(): string { const records = this.toRecord(); const parts: string[] = []; for (const key in records) { parts.push(`${key}=${records[key]}`); } return parts.join("&"); } /** * Return parameters as a plain record. * - If a parameter has exactly one value, returns that value directly. * - If a parameter has multiple values, returns a readonly array of values. */ toRecord(): Record> { const parts: Record> = {}; for (const [key, entry] of this.params.entries()) { const encodedKey = this.encoder.encodeKey(key); if (entry.options.explode) { parts[encodedKey] = entry.values.map((v) => this.encoder.encodeValue(v)); } else { const encodedValues = entry.values.map((v) => this.encoder.encodeValue(v)); // join with the delimiter *unencoded* parts[encodedKey] = encodedValues.join(entry.options.delimiter); } } return parts; } /** * Return an Angular's HttpParams with an identity parameter codec as the parameters are already encoded. */ toHttpParams(): HttpParams { const records = this.toRecord(); let httpParams = new HttpParams({encoder: new IdentityHttpParameterCodec()}); return httpParams.appendAll(records); } } export function concatHttpParamsObject(httpParams: OpenApiHttpParams, key: string, item: { [index: string]: any }, delimiter: Delimiter): OpenApiHttpParams { let keyAndValues: string[] = []; for (const k in item) { keyAndValues.push(k); const value = item[k]; if (Array.isArray(value)) { keyAndValues.push(...value.map(convertToString)); } else { keyAndValues.push(convertToString(value)); } } return httpParams.set(key, keyAndValues, {explode: false, delimiter: delimiter}); } function convertToString(value: any): string { if (value instanceof Date) { return value.toISOString(); } else { return value.toString(); } }