11{ {> licenseInfo} }
2- import { HttpHeaders, HttpParams, HttpParameterCodec } from '@angular/common/http';
3- import { CustomHttpParameterCodec } from './encoder';
2+ import { HttpHeaders, HttpParams, HttpParameterCodec} from '@angular/common/http';
43import { {{configurationClassName} } } from './configuration';
4+ import { CustomHttpParameterCodec, NoOpHttpParameterCodec} from './encoder';
5+
6+ export enum QueryParamStyle {
7+ Json,
8+ Form,
9+ DeepObject,
10+ SpaceDelimited,
11+ PipeDelimited,
12+ }
13+
14+ export type Delimiter = "," | " " | "|" | "\t";
15+
16+ export interface ParamOptions {
17+ /** When true , serialized as multiple repeated key= value pairs. When false , serialized as a single key with joined values using `delimiter`. */
18+ explode?: boolean;
19+ /** Delimiter used when explode= false . The delimiter itself is inserted unencoded between encoded values. */
20+ delimiter?: Delimiter;
21+ }
22+
23+ interface ParamEntry {
24+ values: string[];
25+ options: Required< ParamOptions> ;
26+ }
27+
28+ export class OpenApiHttpParams {
29+ private params: Map< string, ParamEntry> = new Map();
30+ private defaults: Required< ParamOptions> ;
31+ private encoder: HttpParameterCodec;
32+
33+ /**
34+ * @param encoder Parameter serializer
35+ * @param defaults Global defaults used when a specific parameter has no explicit options.
36+ * By OpenAPI default , explode is true for query params with style= form.
37+ */
38+ constructor(encoder?: HttpParameterCodec, defaults?: { explode?: boolean; delimiter?: Delimiter } ) {
39+ this.encoder = encoder || new CustomHttpParameterCodec();
40+ this.defaults = {
41+ explode: defaults?.explode ?? true ,
42+ delimiter: defaults?.delimiter ?? " ," ,
43+ } ;
44+ }
45+
46+ private resolveOptions(local?: ParamOptions): Required<ParamOptions > {
47+ return {
48+ explode: local?.explode ?? this.defaults.explode,
49+ delimiter: local?.delimiter ?? this.defaults.delimiter,
50+ } ;
51+ }
52+
53+ /**
54+ * Replace the parameter's values and (optionally) its options.
55+ * Options are stored per-parameter (not global).
56+ */
57+ set(key: string, values: string[] | string, options?: ParamOptions): this {
58+ const arr = Array.isArray(values) ? values.slice() : [values];
59+ const opts = this.resolveOptions(options);
60+ this.params.set(key, {values: arr, options: opts} );
61+ return this;
62+ }
63+
64+ /**
65+ * Append a single value to the parameter. If the parameter didn't exist it will be created
66+ * and use resolved options (global defaults merged with any provided options).
67+ */
68+ append(key: string, value: string, options?: ParamOptions): this {
69+ const entry = this.params.get(key);
70+ if (entry) {
71+ // If new options provided, override the stored options for subsequent serialization
72+ if (options) {
73+ entry.options = this.resolveOptions({...entry.options, ...options} );
74+ }
75+ entry.values.push(value);
76+ } else {
77+ this.set(key, [value], options);
78+ }
79+ return this;
80+ }
81+
82+ /**
83+ * Serialize to a query string according to per-parameter OpenAPI options.
84+ * - If explode=true for that parameter → repeated key=value pairs (each value encoded).
85+ * - If explode=false for that parameter → single key=value where values are individually encoded
86+ * and joined using the configured delimiter. The delimiter character is inserted AS-IS
87+ * (not percent-encoded).
88+ */
89+ toString(): string {
90+ const records = this.toRecord();
91+ const parts: string[] = [];
92+
93+ for (const key in records) {
94+ parts.push(`${key} =${ records[key]} `);
95+ }
96+
97+ return parts.join("& ");
98+ }
99+
100+ /**
101+ * Return parameters as a plain record.
102+ * - If a parameter has exactly one value, returns that value directly.
103+ * - If a parameter has multiple values, returns a readonly array of values.
104+ */
105+ toRecord(): Record<string , string | number | boolean | ReadonlyArray <string | number | boolean >> {
106+ const parts: Record< string, string | number | boolean | ReadonlyArray< string | number | boolean>> = {} ;
107+
108+ for (const [key, entry] of this.params.entries()) {
109+ const encodedKey = this.encoder.encodeKey(key);
110+
111+ if (entry.options.explode) {
112+ parts[encodedKey] = entry.values.map((v) => this.encoder.encodeValue(v));
113+ } else {
114+ const encodedValues = entry.values.map((v) => this.encoder.encodeValue(v));
115+
116+ // join with the delimiter *unencoded*
117+ parts[encodedKey] = encodedValues.join(entry.options.delimiter);
118+ }
119+ }
120+
121+ return parts;
122+ }
123+
124+ /**
125+ * Return an Angular's HttpParams with a NoOp parameter codec as the parameters are already encoded.
126+ */
127+ toHttpParams(): HttpParams {
128+ const records = this.toRecord();
129+
130+ let httpParams = new HttpParams({encoder: new NoOpHttpParameterCodec()} );
131+
132+ return httpParams.appendAll(records);
133+ }
134+ }
135+
136+ function concatHttpParamsObject(httpParams: OpenApiHttpParams, key: string, item: {
137+ [index: string]: any
138+ } , delimiter: Delimiter): OpenApiHttpParams {
139+ let keyAndValues: string[] = [];
140+
141+ for (const k in item) {
142+ keyAndValues.push(k);
143+ keyAndValues.push(item[k].toString());
144+ }
145+
146+ return httpParams.set(key, keyAndValues, { explode: false , delimiter: delimiter} );
147+ }
5148
6149export class BaseService {
7- protected basePath = ' {{{basePath}}} ' ;
150+ protected basePath = ' http://localhost ' ;
8151 public defaultHeaders = new HttpHeaders();
9- public configuration: {{configurationClassName } } ;
10- public encoder: HttpParameterCodec;
152+ public configuration: Configuration ;
153+ public encoder? : HttpParameterCodec;
11154
12- constructor(basePath?: string| string[], configuration?: { {configurationClassName } } ) {
13- this.configuration = configuration || new {{configurationClassName } } ();
155+ constructor(basePath?: string | string[], configuration?: Configuration ) {
156+ this.configuration = configuration || new Configuration ();
14157 if (typeof this.configuration.basePath !== ' string' ) {
15158 const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined;
16159 if (firstBasePath != undefined) {
@@ -29,47 +172,58 @@ export class BaseService {
29172 return consumes.indexOf(' multipart/form-data' ) !== -1;
30173 }
31174
32- protected addToHttpParams(httpParams: HttpParams, value: any, key?: string, isDeep: boolean = false): HttpParams {
33- // If the value is an object (but not a Date), recursively add its keys.
34- if (typeof value === ' object' && ! (value instanceof Date)) {
35- return this.addToHttpParamsRecursive(httpParams, value, isDeep ? key : undefined, isDeep);
36- }
37- return this.addToHttpParamsRecursive(httpParams, value, key);
38- }
39-
40- protected addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string, isDeep: boolean = false): HttpParams {
175+ protected addToHttpParams(httpParams: OpenApiHttpParams, key: string, value: any | null | undefined, paramStyle: QueryParamStyle, explode: boolean): OpenApiHttpParams {
41176 if (value === null || value === undefined) {
42177 return httpParams;
43178 }
44- if (typeof value === 'object') {
45- // If JSON format is preferred, key must be provided.
46- if (key != null) {
47- return isDeep
48- ? Object.keys(value as Record< string, any> ).reduce(
49- (hp, k) => hp.append(`${key} [${ k} ]`, value[k]),
50- httpParams,
51- )
52- : httpParams.append(key, JSON.stringify(value));
179+
180+ if (paramStyle === QueryParamStyle.DeepObject) {
181+ if (typeof value !== ' object' ) {
182+ throw Error(`An object must be provided for key ${key} as it is a deep object`);
53183 }
54- // Otherwise, if it's an array, add each element.
55- if (Array.isArray(value)) {
56- value.forEach(elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
184+
185+ return Object.keys(value as Record<string , any >).reduce(
186+ (hp, k) => hp.append(`${ key} [${ k} ]`, value[k]),
187+ httpParams,
188+ );
189+ } else if (paramStyle === QueryParamStyle.Json) {
190+ return httpParams.append(key, JSON.stringify(value));
191+ } else {
192+ // Form-style, SpaceDelimited or PipeDelimited
193+
194+ if (Object(value) !== value) {
195+ // If it is a primitive type, add its string representation
196+ return httpParams.append(key, value.toString());
57197 } else if (value instanceof Date) {
58- if (key != null) {
59- httpParams = httpParams.append(key, value.toISOString());
198+ return httpParams.append(key, value.toISOString());
199+ } else if (Array.isArray(value)) {
200+ // Otherwise, if it' s an array, add each element.
201+ if (paramStyle === QueryParamStyle.Form) {
202+ return httpParams.set(key, value, {explode: explode, delimiter: ' ,' });
203+ } else if (paramStyle === QueryParamStyle.SpaceDelimited) {
204+ return httpParams.set(key, value, {explode: explode, delimiter: ' ' });
60205 } else {
61- throw Error(" key may not be null if value is Date" );
206+ // PipeDelimited
207+ return httpParams.set(key, value, {explode: explode, delimiter: ' |' });
62208 }
63209 } else {
64- Object.keys(value).forEach(k => {
65- const paramKey = key ? `${key} .${ k} ` : k;
66- httpParams = this.addToHttpParamsRecursive(httpParams, value[k], paramKey);
67- });
210+ // Otherwise, if it' s an object, add each field.
211+ if (paramStyle === QueryParamStyle.Form) {
212+ if (explode) {
213+ Object.keys(value).forEach(k => {
214+ httpParams = httpParams.append(k, value[k]);
215+ } );
216+ return httpParams;
217+ } else {
218+ return concatHttpParamsObject(httpParams, key, value, ' ,' );
219+ }
220+ } else if (paramStyle === QueryParamStyle.SpaceDelimited) {
221+ return concatHttpParamsObject(httpParams, key, value, ' ' );
222+ } else {
223+ // PipeDelimited
224+ return concatHttpParamsObject(httpParams, key, value, ' |' );
225+ }
68226 }
69- return httpParams;
70- } else if (key != null) {
71- return httpParams.append(key, value);
72227 }
73- throw Error("key may not be null if value is not object or array");
74228 }
75229}
0 commit comments