@@ -22,13 +22,34 @@ export class HttpParamsBuilderGenerator {
2222 {
2323 namedImports : [ "HttpParams" ] ,
2424 moduleSpecifier : "@angular/common/http" ,
25- } ,
26- {
27- namedImports : [ "Parameter" ] ,
28- moduleSpecifier : "../models" ,
29- } ,
25+ }
3026 ] ) ;
3127
28+ sourceFile . addInterface ( {
29+ name : "ParameterStub" ,
30+ isExported : true ,
31+ properties : [
32+ { name : "name" , type : "string" } ,
33+ { name : "in" , type : "string" } ,
34+ { name : "style" , type : "string" , hasQuestionToken : true } ,
35+ { name : "explode" , type : "boolean" , hasQuestionToken : true } ,
36+ { name : "allowEmptyValue" , type : "boolean" , hasQuestionToken : true } ,
37+ { name : "content" , type : "Record<string, any>" , hasQuestionToken : true } ,
38+ { name : "schema" , type : "any" , hasQuestionToken : true }
39+ ]
40+ } ) ;
41+
42+ sourceFile . addInterface ( {
43+ name : "EncodingConfig" ,
44+ isExported : true ,
45+ properties : [
46+ { name : "contentType" , type : "string" , hasQuestionToken : true } ,
47+ { name : "style" , type : "string" , hasQuestionToken : true } ,
48+ { name : "explode" , type : "boolean" , hasQuestionToken : true } ,
49+ { name : "allowReserved" , type : "boolean" , hasQuestionToken : true }
50+ ]
51+ } ) ;
52+
3253 const classDeclaration = sourceFile . addClass ( {
3354 name : "HttpParamsBuilder" ,
3455 isExported : true ,
@@ -41,7 +62,7 @@ export class HttpParamsBuilderGenerator {
4162 scope : Scope . Public ,
4263 parameters : [
4364 { name : "params" , type : "HttpParams" } ,
44- { name : "parameter" , type : "Parameter " } ,
65+ { name : "parameter" , type : "ParameterStub " } ,
4566 { name : "value" , type : "any" } ,
4667 ] ,
4768 returnType : "HttpParams" ,
@@ -55,6 +76,25 @@ export class HttpParamsBuilderGenerator {
5576 statements : this . getSerializeQueryParamBody ( ) ,
5677 } ) ;
5778
79+ classDeclaration . addMethod ( {
80+ name : "serializeUrlEncodedBody" ,
81+ isStatic : true ,
82+ scope : Scope . Public ,
83+ parameters : [
84+ { name : "body" , type : "any" } ,
85+ { name : "encodings" , type : "Record<string, EncodingConfig>" , initializer : "{}" }
86+ ] ,
87+ returnType : "HttpParams" ,
88+ docs : [
89+ "Serializes a body object into HttpParams for application/x-www-form-urlencoded requests." ,
90+ "Applies OpenAPI 'encoding' rules (style, explode) per property." ,
91+ "@param body The object to serialize." ,
92+ "@param encodings A map of property names to encoding configurations." ,
93+ "@returns An HttpParams object representing the form body."
94+ ] ,
95+ statements : this . getSerializeUrlEncodedBodyBody ( )
96+ } ) ;
97+
5898 classDeclaration . addMethod ( {
5999 name : "serializePathParam" ,
60100 isStatic : true ,
@@ -165,6 +205,36 @@ return encodeURIComponent(value);
165205`
166206 } ) ;
167207
208+ // Add private helper for recursive deepObject serialization
209+ classDeclaration . addMethod ( {
210+ name : "appendDeepObject" ,
211+ isStatic : true ,
212+ scope : Scope . Private ,
213+ parameters : [
214+ { name : "params" , type : "HttpParams" } ,
215+ { name : "key" , type : "string" } ,
216+ { name : "value" , type : "any" }
217+ ] ,
218+ returnType : "HttpParams" ,
219+ statements : `
220+ if (value === null || value === undefined) {
221+ return params;
222+ }
223+
224+ if (Array.isArray(value)) {
225+ value.forEach((item, index) => {
226+ params = this.appendDeepObject(params, \`\${key}[\${index}]\`, item);
227+ });
228+ } else if (typeof value === 'object' && !(value instanceof Date)) {
229+ Object.entries(value).forEach(([prop, val]) => {
230+ params = this.appendDeepObject(params, \`\${key}[\${prop}]\`, val);
231+ });
232+ } else {
233+ params = params.append(key, this.formatValue(value));
234+ }
235+ return params;`
236+ } ) ;
237+
168238 sourceFile . formatText ( ) ;
169239 }
170240
@@ -176,10 +246,10 @@ return encodeURIComponent(value);
176246
177247 const name = parameter.name;
178248
179- // Handle OAS 3.2 deprecated allowEmptyValue logic.
180- if (value === '' && parameter.allowEmptyValue === false) {
181- return params;
182- }
249+ // Handle OAS 3.2 deprecated allowEmptyValue logic.
250+ if (value === '' && parameter.allowEmptyValue === false) {
251+ return params;
252+ }
183253
184254 // Handle content-based serialization (mutually exclusive with style/explode in OAS3)
185255 if (parameter.content) {
@@ -193,10 +263,10 @@ return encodeURIComponent(value);
193263 // Defaulting logic from OAS spec
194264 const style = parameter.style ?? 'form';
195265 const explode = parameter.explode ?? (style === 'form');
196- const schema = parameter.schema ?? { type: parameter.type };
266+ const schema = parameter.schema ?? { type: typeof value }; // Fallback if schema is missing
197267
198- const isArray = schema.type === 'array' ;
199- const isObject = schema.type === 'object';
268+ const isArray = Array.isArray(value) ;
269+ const isObject = typeof value === 'object' && value !== null && !isArray && !(value instanceof Date) ;
200270
201271 switch (style) {
202272 case 'form':
@@ -245,23 +315,13 @@ return encodeURIComponent(value);
245315
246316 case 'deepObject':
247317 if (isObject && explode) {
248- Object.entries(value as Record<string, any>).forEach(([key, propValue]) => {
249- if (propValue != null) {
250- if (Array.isArray(propValue)) {
251- propValue.forEach(item => {
252- if (item != null) params = params.append(\`\${name}[\${key}]\`, this.formatValue(item));
253- });
254- } else {
255- params = params.append(\`\${name}[\${key}]\`, this.formatValue(propValue));
256- }
257- }
258- });
259- return params;
318+ return this.appendDeepObject(params, name, value);
260319 }
261320 break;
262321 }
263322
264323 if (Array.isArray(value)) {
324+ // Fallback/default for array if style didnt match
265325 value.forEach(item => {
266326 if (item != null) params = params.append(name, this.formatValue(item));
267327 });
@@ -271,6 +331,35 @@ return encodeURIComponent(value);
271331 return params;` ;
272332 }
273333
334+ private getSerializeUrlEncodedBodyBody ( ) : string {
335+ return `
336+ let params = new HttpParams();
337+ if (body === null || body === undefined) return params;
338+
339+ Object.entries(body).forEach(([key, value]) => {
340+ if (value === undefined || value === null) return;
341+
342+ const encoding = encodings[key] || {};
343+ // Default content-type for form-urlencoded is form style, explode true
344+ const style = encoding.style ?? 'form';
345+ const explode = encoding.explode ?? true;
346+
347+ // Use existing serializeQueryParam logic by constructing a minimal parameter definition
348+ const paramDef: ParameterStub = {
349+ name: key,
350+ in: 'query', // Reuse query logic (same as form-urlencoded)
351+ style,
352+ explode,
353+ schema: { type: typeof value }
354+ };
355+
356+ params = this.serializeQueryParam(params, paramDef, value);
357+ });
358+
359+ return params;
360+ ` ;
361+ }
362+
274363 private getSerializePathParamBody ( ) : string {
275364 return `
276365 if (value === null || value === undefined) return '';
0 commit comments