1
1
{ {> licenseInfo} }
2
- import { HttpHeaders, HttpParams, HttpParameterCodec } from '@angular/common/http';
3
- import { CustomHttpParameterCodec } from './encoder';
2
+ import { HttpHeaders, HttpParams, HttpParameterCodec} from '@angular/common/http';
4
3
import { {{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
+ }
5
148
6
149
export class BaseService {
7
- protected basePath = ' {{{basePath}}} ' ;
150
+ protected basePath = ' http://localhost ' ;
8
151
public defaultHeaders = new HttpHeaders();
9
- public configuration: {{configurationClassName } } ;
152
+ public configuration: Configuration ;
10
153
public encoder: HttpParameterCodec;
11
154
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 ();
14
157
if (typeof this.configuration.basePath !== ' string' ) {
15
158
const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined;
16
159
if (firstBasePath != undefined) {
@@ -29,47 +172,58 @@ export class BaseService {
29
172
return consumes.indexOf(' multipart/form-data' ) !== -1;
30
173
}
31
174
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 {
41
176
if (value === null || value === undefined) {
42
177
return httpParams;
43
178
}
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`);
53
183
}
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());
57
197
} 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: ' ' });
60
205
} else {
61
- throw Error(" key may not be null if value is Date" );
206
+ // PipeDelimited
207
+ return httpParams.set(key, value, {explode: explode, delimiter: ' |' });
62
208
}
63
209
} 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
+ }
68
226
}
69
- return httpParams;
70
- } else if (key != null) {
71
- return httpParams.append(key, value);
72
227
}
73
- throw Error("key may not be null if value is not object or array");
74
228
}
75
229
}
0 commit comments