From d47afb6ff118f4999e300a7dd6e679dd9497677f Mon Sep 17 00:00:00 2001 From: didavila Date: Thu, 26 Mar 2026 11:17:37 +0100 Subject: [PATCH] fix: body param and 2xx response codes in repository generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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> --- src/generators/clean-arch.generator.ts | 15 ++++++++++++--- src/types/openapi.types.ts | 2 ++ templates/api.repository.impl.mustache | 12 +++--------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/generators/clean-arch.generator.ts b/src/generators/clean-arch.generator.ts index aa71354..78b8dc9 100644 --- a/src/generators/clean-arch.generator.ts +++ b/src/generators/clean-arch.generator.ts @@ -271,7 +271,11 @@ export function generateCleanArchitecture( let returnType = 'void'; let returnBaseType = 'void'; 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.$ref) { 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({ nickname: op.operationId || `${method}${pathKey.replace(/\//g, '_')}`, summary: op.summary || '', @@ -294,15 +301,17 @@ export function generateCleanArchitecture( ...p, '-last': i === allParams.length - 1 })), - hasQueryParams: (op.parameters || []).some((p) => p.in === 'query'), + hasQueryParams, queryParams: (op.parameters || []) .filter((p) => p.in === 'query') .map((p, i: number, arr: unknown[]) => ({ paramName: p.name, '-last': i === arr.length - 1 })), - hasBodyParam: !!op.requestBody, + hasBodyParam, bodyParam: 'body', + hasOptions: hasQueryParams || hasBodyParam, + hasBothParamsAndBody: hasQueryParams && hasBodyParam, returnType: returnType !== 'void' ? returnType : false, returnBaseType: returnBaseType !== 'void' ? returnBaseType : false, returnTypeVarName: returnType !== 'void' ? toCamelCase(returnType) : false, diff --git a/src/types/openapi.types.ts b/src/types/openapi.types.ts index 1791429..0aa3a8f 100644 --- a/src/types/openapi.types.ts +++ b/src/types/openapi.types.ts @@ -115,6 +115,8 @@ export interface TagOperation { queryParams: unknown[]; hasBodyParam: boolean; bodyParam: string; + hasOptions: boolean; + hasBothParamsAndBody: boolean; returnType: string | boolean; returnBaseType: string | boolean; returnTypeVarName: string | boolean; diff --git a/templates/api.repository.impl.mustache b/templates/api.repository.impl.mustache index e40cb8a..a466595 100644 --- a/templates/api.repository.impl.mustache +++ b/templates/api.repository.impl.mustache @@ -32,26 +32,20 @@ export class {{classname}}RepositoryImpl extends MRepository implements {{classn {{#operation}} {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}}): Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> { {{#isListContainer}} - return this.{{httpMethod}}<{{{returnBaseType}}}Dto>('{{path}}'{{#hasQueryParams}}, { - params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} } - }{{/hasQueryParams}}{{#hasBodyParam}}, {{bodyParam}}{{/hasBodyParam}}) + return this.{{httpMethod}}<{{{returnBaseType}}}Dto>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}}) .pipe( map((response) => response.{{#vendorExtensions}}{{x-response-property}}{{/vendorExtensions}}{{^vendorExtensions}}items{{/vendorExtensions}}.map({{{returnBaseTypeVarName}}}Mapper)) ); {{/isListContainer}} {{^isListContainer}} {{#returnType}} - return this.{{httpMethod}}<{{{returnType}}}Dto>('{{path}}'{{#hasQueryParams}}, { - params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} } - }{{/hasQueryParams}}{{#hasBodyParam}}, {{bodyParam}}{{/hasBodyParam}}) + return this.{{httpMethod}}<{{{returnType}}}Dto>('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}}) .pipe( map({{{returnTypeVarName}}}Mapper) ); {{/returnType}} {{^returnType}} - return this.{{httpMethod}}('{{path}}'{{#hasQueryParams}}, { - params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} } - }{{/hasQueryParams}}{{#hasBodyParam}}, {{bodyParam}}{{/hasBodyParam}}); + return this.{{httpMethod}}('{{path}}'{{#hasOptions}}, { {{#hasQueryParams}}params: { {{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}} }{{/hasQueryParams}}{{#hasBothParamsAndBody}}, {{/hasBothParamsAndBody}}{{#hasBodyParam}}body{{/hasBodyParam}} }{{/hasOptions}}); {{/returnType}} {{/isListContainer}} }