Skip to content

Commit 26b6b4a

Browse files
committed
feat(api-client): parameters of operation prioritized over path default params
if same param name occurs, only the definition from operation parameter is used
1 parent 5fc0bb7 commit 26b6b4a

File tree

7 files changed

+450
-124
lines changed

7 files changed

+450
-124
lines changed

src/parser.ts

Lines changed: 99 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -108,98 +108,111 @@ function parseMethods(
108108
([pathName, pathDef]) =>
109109
Object.entries(pathDef)
110110
.filter(
111-
([methodType, operation]) =>
111+
([methodType, operation]: [string, OpenAPIV2.OperationObject]) =>
112112
supportedMethods.indexOf(methodType?.toUpperCase()) !== -1 && // skip unsupported methods
113113
(!swaggerTag ||
114114
(operation &&
115115
'tags' in operation &&
116-
operation.tags.includes(swaggerTag))), // if tag exists take only paths including this tag
116+
operation.tags?.includes(swaggerTag))), // if tag exists take only paths including this tag
117117
)
118-
.map(([methodType, operation]) => {
119-
// select the lowest success (code 20x) response
120-
const successResponseCode =
121-
('responses' in operation &&
122-
Object.keys(operation.responses)
123-
.slice()
124-
.sort()
125-
.filter(code => code.startsWith('2'))[0]) ||
126-
'default';
127-
128-
const okResponse = operation.responses[successResponseCode];
129-
130-
const responseTypeSchema = determineResponseType(
131-
okResponse && '$ref' in okResponse
132-
? responses[dereferenceType(okResponse.$ref)]
133-
: okResponse,
134-
);
135-
136-
const transformedParams = transformParameters(
137-
[...(pathDef.parameters || []), ...(operation.parameters || [])],
138-
parameters || {},
139-
);
140-
141-
const methodTypeLowered = methodType.toLowerCase() as MethodType;
142-
const formData = transformedParams
143-
.filter(({ name, isFormParameter }) => name && isFormParameter)
144-
.map(({ name, camelCaseName }) => ({
145-
name,
146-
camelCaseName: camelCaseName || name,
147-
}));
148-
const stringifyBody = (param?: Parameter) =>
149-
param ? `JSON.stringify(args.${param.camelCaseName})` : 'null';
150-
const body = /^(?:post|put|patch)\b/.test(methodTypeLowered)
151-
? formData.length
152-
? 'formData'
153-
: stringifyBody(
154-
transformedParams.find(
155-
({ isBodyParameter }) => isBodyParameter,
156-
),
157-
)
158-
: undefined;
159-
160-
return {
161-
hasJsonResponse: true,
162-
methodName: toCamelCase(
163-
operation.operationId
164-
? !swaggerTag
165-
? operation.operationId
166-
: operation.operationId.replace(`${swaggerTag}_`, '')
167-
: `${methodTypeLowered}_${pathName.replace(/[{}]/g, '')}`,
168-
),
169-
methodType: methodTypeLowered,
170-
body,
171-
parameters: transformedParams,
172-
paramsOptional: transformedParams.every(
173-
({ isRequired }) => !isRequired,
174-
),
175-
formData,
176-
// turn path interpolation `{this}` into string template `${args.this}
177-
path: pathName.replace(
178-
/{(.*?)}/g,
179-
(_: string, ...args: string[]): string =>
180-
`\${args.${toCamelCase(args[0])}}`,
181-
),
182-
responseGuard: responseTypeSchema.guard?.('response'),
183-
description: createDocsComment(
118+
.map(
119+
([methodType, operation]: [string, OpenAPIV2.OperationObject]) => {
120+
// select the lowest success (code 20x) response
121+
const successResponseCode =
122+
('responses' in operation &&
123+
Object.keys(operation.responses)
124+
.slice()
125+
.sort()
126+
.filter(code => code.startsWith('2'))[0]) ||
127+
'default';
128+
129+
const okResponse = operation.responses[successResponseCode];
130+
131+
const responseTypeSchema = determineResponseType(
132+
okResponse && '$ref' in okResponse
133+
? responses[dereferenceType(okResponse.$ref)]
134+
: okResponse,
135+
);
136+
137+
const transformedParams = transformParameters(
184138
[
185-
operation.summary,
186-
operation.description,
187-
operation.deprecated
188-
? `@deprecated this method has been deprecated and may be removed in future.`
189-
: null,
190-
`Response generated for [ ${successResponseCode} ] HTTP response code.`,
191-
]
192-
.filter(str => !!str)
193-
.join('\n'),
194-
2,
195-
true,
196-
),
197-
responseTypeSchema,
198-
...(responseTypeSchema.type === 'File' && {
199-
requestResponseType: 'blob' as 'blob',
200-
}),
201-
};
202-
}),
139+
...(pathDef.parameters?.length && operation.parameters?.length
140+
? pathDef.parameters.filter(parameter =>
141+
operation.parameters?.some(
142+
param =>
143+
(param as OpenAPIV2.ParameterObject).name !==
144+
(parameter as OpenAPIV2.ParameterObject).name,
145+
),
146+
)
147+
: pathDef.parameters || []),
148+
...(operation.parameters || []),
149+
] as ExtendedParameter[],
150+
parameters || {},
151+
);
152+
153+
const methodTypeLowered = methodType.toLowerCase() as MethodType;
154+
const formData = transformedParams
155+
.filter(({ name, isFormParameter }) => name && isFormParameter)
156+
.map(({ name, camelCaseName }) => ({
157+
name,
158+
camelCaseName: camelCaseName || name,
159+
}));
160+
const stringifyBody = (param?: Parameter) =>
161+
param ? `JSON.stringify(args.${param.camelCaseName})` : 'null';
162+
const body = /^(?:post|put|patch)\b/.test(methodTypeLowered)
163+
? formData.length
164+
? 'formData'
165+
: stringifyBody(
166+
transformedParams.find(
167+
({ isBodyParameter }) => isBodyParameter,
168+
),
169+
)
170+
: undefined;
171+
172+
return {
173+
hasJsonResponse: true,
174+
methodName: toCamelCase(
175+
operation.operationId
176+
? !swaggerTag
177+
? operation.operationId
178+
: operation.operationId.replace(`${swaggerTag}_`, '')
179+
: `${methodTypeLowered}_${pathName.replace(/[{}]/g, '')}`,
180+
),
181+
methodType: methodTypeLowered,
182+
body,
183+
parameters: transformedParams,
184+
paramsOptional: transformedParams.every(
185+
({ isRequired }) => !isRequired,
186+
),
187+
formData,
188+
// turn path interpolation `{this}` into string template `${args.this}
189+
path: pathName.replace(
190+
/{(.*?)}/g,
191+
(_: string, ...args: string[]): string =>
192+
`\${args.${toCamelCase(args[0])}}`,
193+
),
194+
responseGuard: responseTypeSchema.guard?.('response'),
195+
description: createDocsComment(
196+
[
197+
operation.summary,
198+
operation.description,
199+
operation.deprecated
200+
? `@deprecated this method has been deprecated and may be removed in future.`
201+
: null,
202+
`Response generated for [ ${successResponseCode} ] HTTP response code.`,
203+
]
204+
.filter(str => !!str)
205+
.join('\n'),
206+
2,
207+
true,
208+
),
209+
responseTypeSchema,
210+
...(responseTypeSchema.type === 'File' && {
211+
requestResponseType: 'blob' as 'blob',
212+
}),
213+
};
214+
},
215+
),
203216
),
204217
);
205218
}

tests/custom-without-guards/api/api-client.interface.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,64 @@ export interface APIClientInterface {
124124
observe?: 'events',
125125
): Observable<HttpEvent<void>>;
126126

127+
/**
128+
* Arguments object for method `getPetsWithDefaultIdParamId`.
129+
*/
130+
getPetsWithDefaultIdParamIdParams?: {
131+
/** An ID or a slug identifying this Pet. */
132+
id: string,
133+
};
134+
135+
/**
136+
* Get details of the game.
137+
* Default id param should be overriden to string
138+
* Response generated for [ 200 ] HTTP response code.
139+
*/
140+
getPetsWithDefaultIdParamId(
141+
args: Exclude<APIClientInterface['getPetsWithDefaultIdParamIdParams'], undefined>,
142+
requestHttpOptions?: HttpOptions,
143+
observe?: 'body',
144+
): Observable<models.Pet>;
145+
getPetsWithDefaultIdParamId(
146+
args: Exclude<APIClientInterface['getPetsWithDefaultIdParamIdParams'], undefined>,
147+
requestHttpOptions?: HttpOptions,
148+
observe?: 'response',
149+
): Observable<HttpResponse<models.Pet>>;
150+
getPetsWithDefaultIdParamId(
151+
args: Exclude<APIClientInterface['getPetsWithDefaultIdParamIdParams'], undefined>,
152+
requestHttpOptions?: HttpOptions,
153+
observe?: 'events',
154+
): Observable<HttpEvent<models.Pet>>;
155+
156+
/**
157+
* Arguments object for method `patchPetsWithDefaultIdParamId`.
158+
*/
159+
patchPetsWithDefaultIdParamIdParams?: {
160+
/** A unique integer value identifying this Pet. */
161+
id: number,
162+
body?: any,
163+
};
164+
165+
/**
166+
* Default id param should be number and not string
167+
* Response generated for [ 200 ] HTTP response code.
168+
*/
169+
patchPetsWithDefaultIdParamId(
170+
args: Exclude<APIClientInterface['patchPetsWithDefaultIdParamIdParams'], undefined>,
171+
requestHttpOptions?: HttpOptions,
172+
observe?: 'body',
173+
): Observable<void>;
174+
patchPetsWithDefaultIdParamId(
175+
args: Exclude<APIClientInterface['patchPetsWithDefaultIdParamIdParams'], undefined>,
176+
requestHttpOptions?: HttpOptions,
177+
observe?: 'response',
178+
): Observable<HttpResponse<void>>;
179+
patchPetsWithDefaultIdParamId(
180+
args: Exclude<APIClientInterface['patchPetsWithDefaultIdParamIdParams'], undefined>,
181+
requestHttpOptions?: HttpOptions,
182+
observe?: 'events',
183+
): Observable<HttpEvent<void>>;
184+
127185
/**
128186
* Response generated for [ 200 ] HTTP response code.
129187
*/

tests/custom-without-guards/api/api-client.service.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,75 @@ export class APIClient implements APIClientInterface {
193193
return this.http.delete<void>(`${this.domain}${path}`, options);
194194
}
195195

196+
/**
197+
* Get details of the game.
198+
* Default id param should be overriden to string
199+
* Response generated for [ 200 ] HTTP response code.
200+
*/
201+
getPetsWithDefaultIdParamId(
202+
args: Exclude<APIClientInterface['getPetsWithDefaultIdParamIdParams'], undefined>,
203+
requestHttpOptions?: HttpOptions,
204+
observe?: 'body',
205+
): Observable<models.Pet>;
206+
getPetsWithDefaultIdParamId(
207+
args: Exclude<APIClientInterface['getPetsWithDefaultIdParamIdParams'], undefined>,
208+
requestHttpOptions?: HttpOptions,
209+
observe?: 'response',
210+
): Observable<HttpResponse<models.Pet>>;
211+
getPetsWithDefaultIdParamId(
212+
args: Exclude<APIClientInterface['getPetsWithDefaultIdParamIdParams'], undefined>,
213+
requestHttpOptions?: HttpOptions,
214+
observe?: 'events',
215+
): Observable<HttpEvent<models.Pet>>;
216+
getPetsWithDefaultIdParamId(
217+
args: Exclude<APIClientInterface['getPetsWithDefaultIdParamIdParams'], undefined>,
218+
requestHttpOptions?: HttpOptions,
219+
observe: any = 'body',
220+
): Observable<models.Pet | HttpResponse<models.Pet> | HttpEvent<models.Pet>> {
221+
const path = `/pets-with-default-id-param/${args.id}`;
222+
const options = {
223+
...this.options,
224+
...requestHttpOptions,
225+
observe,
226+
};
227+
228+
return this.http.get<models.Pet>(`${this.domain}${path}`, options);
229+
}
230+
231+
/**
232+
* Default id param should be number and not string
233+
* Response generated for [ 200 ] HTTP response code.
234+
*/
235+
patchPetsWithDefaultIdParamId(
236+
args: Exclude<APIClientInterface['patchPetsWithDefaultIdParamIdParams'], undefined>,
237+
requestHttpOptions?: HttpOptions,
238+
observe?: 'body',
239+
): Observable<void>;
240+
patchPetsWithDefaultIdParamId(
241+
args: Exclude<APIClientInterface['patchPetsWithDefaultIdParamIdParams'], undefined>,
242+
requestHttpOptions?: HttpOptions,
243+
observe?: 'response',
244+
): Observable<HttpResponse<void>>;
245+
patchPetsWithDefaultIdParamId(
246+
args: Exclude<APIClientInterface['patchPetsWithDefaultIdParamIdParams'], undefined>,
247+
requestHttpOptions?: HttpOptions,
248+
observe?: 'events',
249+
): Observable<HttpEvent<void>>;
250+
patchPetsWithDefaultIdParamId(
251+
args: Exclude<APIClientInterface['patchPetsWithDefaultIdParamIdParams'], undefined>,
252+
requestHttpOptions?: HttpOptions,
253+
observe: any = 'body',
254+
): Observable<void | HttpResponse<void> | HttpEvent<void>> {
255+
const path = `/pets-with-default-id-param/${args.id}`;
256+
const options = {
257+
...this.options,
258+
...requestHttpOptions,
259+
observe,
260+
};
261+
262+
return this.http.patch<void>(`${this.domain}${path}`, JSON.stringify(args.body), options);
263+
}
264+
196265
/**
197266
* Response generated for [ 200 ] HTTP response code.
198267
*/

0 commit comments

Comments
 (0)