| 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<ParamOptions>; |
| } |
| |
| export class OpenApiHttpParams { |
| private params: Map<string, ParamEntry> = new Map(); |
| private defaults: Required<ParamOptions>; |
| 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<ParamOptions> { |
| 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<string, string | number | boolean | ReadonlyArray<string | number | boolean>> { |
| const parts: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>> = {}; |
| |
| 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(); |
| } |
| } |