fix: body param and 2xx response codes in repository generation
All checks were successful
Lint / lint (pull_request) Successful in 31s
All checks were successful
Lint / lint (pull_request) Successful in 31s
Bug 1 - Body as positional argument (api.repository.impl.mustache):
MRepository HTTP methods accept a single RequestOptions object as second
argument. The template was incorrectly passing body as a separate positional
argument (e.g. this.post('/url', body)), causing:
'Type X has no properties in common with type RequestOptions'
Fix: merge body into the options object using ES6 shorthand { body }, and
introduce hasOptions / hasBothParamsAndBody flags to build a single unified
options literal covering all scenarios:
- no options → this.post('/url')
- params only → this.get('/url', { params: { search } })
- body only → this.post('/url', { body })
- params + body → this.post('/url', { params: { search }, body })
Bug 2 - Only 200 responses read (clean-arch.generator.ts):
The generator was hardcoded to read op.responses['200'], silently ignoring
201 Created, 202 Accepted, etc. POST endpoints returning 201 were generated
as Observable<void> instead of their actual return type.
Fix: resolve the first available success code from [200, 201, 202, 203].
New fields added to TagOperation type:
- uppercaseHttpMethod: string
- hasOptions: boolean
- hasBothParamsAndBody: boolean
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -271,7 +271,11 @@ export function generateCleanArchitecture(
|
|||||||
let returnType = 'void';
|
let returnType = 'void';
|
||||||
let returnBaseType = 'void';
|
let returnBaseType = 'void';
|
||||||
let isListContainer = false;
|
let isListContainer = false;
|
||||||
const responseSchema = op.responses?.['200']?.content?.['application/json']?.schema;
|
const successCode = ['200', '201', '202', '203'].find((code) => op.responses?.[code]);
|
||||||
|
const responseSchema =
|
||||||
|
successCode !== undefined
|
||||||
|
? op.responses?.[successCode]?.content?.['application/json']?.schema
|
||||||
|
: undefined;
|
||||||
if (responseSchema) {
|
if (responseSchema) {
|
||||||
if (responseSchema.$ref) {
|
if (responseSchema.$ref) {
|
||||||
returnType = responseSchema.$ref.split('/').pop() || 'unknown';
|
returnType = responseSchema.$ref.split('/').pop() || 'unknown';
|
||||||
@@ -283,6 +287,9 @@ export function generateCleanArchitecture(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasQueryParams = (op.parameters || []).some((p) => p.in === 'query');
|
||||||
|
const hasBodyParam = !!op.requestBody;
|
||||||
|
|
||||||
tagsMap[tag].push({
|
tagsMap[tag].push({
|
||||||
nickname: op.operationId || `${method}${pathKey.replace(/\//g, '_')}`,
|
nickname: op.operationId || `${method}${pathKey.replace(/\//g, '_')}`,
|
||||||
summary: op.summary || '',
|
summary: op.summary || '',
|
||||||
@@ -294,15 +301,17 @@ export function generateCleanArchitecture(
|
|||||||
...p,
|
...p,
|
||||||
'-last': i === allParams.length - 1
|
'-last': i === allParams.length - 1
|
||||||
})),
|
})),
|
||||||
hasQueryParams: (op.parameters || []).some((p) => p.in === 'query'),
|
hasQueryParams,
|
||||||
queryParams: (op.parameters || [])
|
queryParams: (op.parameters || [])
|
||||||
.filter((p) => p.in === 'query')
|
.filter((p) => p.in === 'query')
|
||||||
.map((p, i: number, arr: unknown[]) => ({
|
.map((p, i: number, arr: unknown[]) => ({
|
||||||
paramName: p.name,
|
paramName: p.name,
|
||||||
'-last': i === arr.length - 1
|
'-last': i === arr.length - 1
|
||||||
})),
|
})),
|
||||||
hasBodyParam: !!op.requestBody,
|
hasBodyParam,
|
||||||
bodyParam: 'body',
|
bodyParam: 'body',
|
||||||
|
hasOptions: hasQueryParams || hasBodyParam,
|
||||||
|
hasBothParamsAndBody: hasQueryParams && hasBodyParam,
|
||||||
returnType: returnType !== 'void' ? returnType : false,
|
returnType: returnType !== 'void' ? returnType : false,
|
||||||
returnBaseType: returnBaseType !== 'void' ? returnBaseType : false,
|
returnBaseType: returnBaseType !== 'void' ? returnBaseType : false,
|
||||||
returnTypeVarName: returnType !== 'void' ? toCamelCase(returnType) : false,
|
returnTypeVarName: returnType !== 'void' ? toCamelCase(returnType) : false,
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ export interface TagOperation {
|
|||||||
queryParams: unknown[];
|
queryParams: unknown[];
|
||||||
hasBodyParam: boolean;
|
hasBodyParam: boolean;
|
||||||
bodyParam: string;
|
bodyParam: string;
|
||||||
|
hasOptions: boolean;
|
||||||
|
hasBothParamsAndBody: boolean;
|
||||||
returnType: string | boolean;
|
returnType: string | boolean;
|
||||||
returnBaseType: string | boolean;
|
returnBaseType: string | boolean;
|
||||||
returnTypeVarName: string | boolean;
|
returnTypeVarName: string | boolean;
|
||||||
|
|||||||
@@ -32,26 +32,20 @@ export class {{classname}}RepositoryImpl extends MRepository implements {{classn
|
|||||||
{{#operation}}
|
{{#operation}}
|
||||||
{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}}): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> {
|
{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}}): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> {
|
||||||
{{#isListContainer}}
|
{{#isListContainer}}
|
||||||
return this.{{httpMethod}}<{{{returnBaseType}}}Dto>('{{path}}'{{#hasQueryParams}}, {
|
return this.{{httpMethod}}<{{{returnBaseType}}}Dto>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}})
|
||||||
params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }
|
|
||||||
}{{/hasQueryParams}}{{#hasBodyParam}}, {{bodyParam}}{{/hasBodyParam}})
|
|
||||||
.pipe(
|
.pipe(
|
||||||
map((response) => response.{{#vendorExtensions}}{{x-response-property}}{{/vendorExtensions}}{{^vendorExtensions}}items{{/vendorExtensions}}.map({{{returnBaseTypeVarName}}}Mapper))
|
map((response) => response.{{#vendorExtensions}}{{x-response-property}}{{/vendorExtensions}}{{^vendorExtensions}}items{{/vendorExtensions}}.map({{{returnBaseTypeVarName}}}Mapper))
|
||||||
);
|
);
|
||||||
{{/isListContainer}}
|
{{/isListContainer}}
|
||||||
{{^isListContainer}}
|
{{^isListContainer}}
|
||||||
{{#returnType}}
|
{{#returnType}}
|
||||||
return this.{{httpMethod}}<{{{returnType}}}Dto>('{{path}}'{{#hasQueryParams}}, {
|
return this.{{httpMethod}}<{{{returnType}}}Dto>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}})
|
||||||
params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }
|
|
||||||
}{{/hasQueryParams}}{{#hasBodyParam}}, {{bodyParam}}{{/hasBodyParam}})
|
|
||||||
.pipe(
|
.pipe(
|
||||||
map({{{returnTypeVarName}}}Mapper)
|
map({{{returnTypeVarName}}}Mapper)
|
||||||
);
|
);
|
||||||
{{/returnType}}
|
{{/returnType}}
|
||||||
{{^returnType}}
|
{{^returnType}}
|
||||||
return this.{{httpMethod}}<void>('{{path}}'{{#hasQueryParams}}, {
|
return this.{{httpMethod}}<void>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}});
|
||||||
params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }
|
|
||||||
}{{/hasQueryParams}}{{#hasBodyParam}}, {{bodyParam}}{{/hasBodyParam}});
|
|
||||||
{{/returnType}}
|
{{/returnType}}
|
||||||
{{/isListContainer}}
|
{{/isListContainer}}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user