Skip to content

Commit b43904f

Browse files
committed
feat: ability to show warnings in case of deprecated operation calls
1 parent 5c89ec9 commit b43904f

File tree

9 files changed

+208
-27
lines changed

9 files changed

+208
-27
lines changed

docs/interfaces/openapi_client.CommonHttpClientOptions.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Options for the common HTTP client.
1212

1313
- [baseUrl](openapi_client.CommonHttpClientOptions.md#baseurl)
1414
- [binaryResponseType](openapi_client.CommonHttpClientOptions.md#binaryresponsetype)
15+
- [deprecatedOperations](openapi_client.CommonHttpClientOptions.md#deprecatedoperations)
1516
- [errorClass](openapi_client.CommonHttpClientOptions.md#errorclass)
1617
- [fetch](openapi_client.CommonHttpClientOptions.md#fetch)
1718
- [formatHttpErrorMessage](openapi_client.CommonHttpClientOptions.md#formathttperrormessage)
@@ -20,6 +21,10 @@ Options for the common HTTP client.
2021
- [preprocessFetchResponse](openapi_client.CommonHttpClientOptions.md#preprocessfetchresponse)
2122
- [preprocessRequest](openapi_client.CommonHttpClientOptions.md#preprocessrequest)
2223

24+
### Methods
25+
26+
- [logDeprecationWarning](openapi_client.CommonHttpClientOptions.md#logdeprecationwarning)
27+
2328
## Properties
2429

2530
### baseUrl
@@ -38,6 +43,18 @@ Type of the response body for binary responses.
3843

3944
___
4045

46+
### deprecatedOperations
47+
48+
`Optional` **deprecatedOperations**: `Object`
49+
50+
Deprecated operations. Used to warn about deprecated operations.
51+
52+
#### Index signature
53+
54+
[methodAndPath: `string`]: `string`
55+
56+
___
57+
4158
### errorClass
4259

4360
**errorClass**: (`url`: `URL`, `request`: `undefined` \| [`CommonHttpClientFetchRequest`](openapi_client.CommonHttpClientFetchRequest.md), `response`: `undefined` \| [`CommonHttpClientFetchResponse`](openapi_client.CommonHttpClientFetchResponse.md), `options`: `undefined` \| [`CommonHttpClientOptions`](openapi_client.CommonHttpClientOptions.md), `message`: `string`) => `Error`
@@ -178,3 +195,24 @@ Preprocess the request before sending it.
178195
##### Returns
179196

180197
`Promise`\<[`CommonHttpClientRequest`](../modules/openapi_client.md#commonhttpclientrequest)\>
198+
199+
## Methods
200+
201+
### logDeprecationWarning
202+
203+
`Optional` **logDeprecationWarning**(`params`): `void`
204+
205+
Log a deprecation warning.
206+
207+
#### Parameters
208+
209+
| Name | Type | Description |
210+
| :------ | :------ | :------ |
211+
| `params` | `Object` | - |
212+
| `params.method` | ``"GET"`` \| ``"HEAD"`` \| ``"POST"`` \| ``"PUT"`` \| ``"DELETE"`` \| ``"CONNECT"`` \| ``"OPTIONS"`` \| ``"PATCH"`` | - |
213+
| `params.operationName` | `string` | Either operation method name in case if it's not part of the service, or service name and operation method name separated by a dot. Examples: `users.getUserById`, `getSystemConfig` |
214+
| `params.path` | `string` | - |
215+
216+
#### Returns
217+
218+
`void`

docs/interfaces/openapi_client.OpenApiClientGeneratorConfigOperations.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Configuration for generating operation calls.
2020
- [makeResponseValidationSchemasExtensible](openapi_client.OpenApiClientGeneratorConfigOperations.md#makeresponsevalidationschemasextensible)
2121
- [mediaTypeArgumentName](openapi_client.OpenApiClientGeneratorConfigOperations.md#mediatypeargumentname)
2222
- [responseBinaryType](openapi_client.OpenApiClientGeneratorConfigOperations.md#responsebinarytype)
23+
- [showDeprecatedWarnings](openapi_client.OpenApiClientGeneratorConfigOperations.md#showdeprecatedwarnings)
2324
- [validateResponse](openapi_client.OpenApiClientGeneratorConfigOperations.md#validateresponse)
2425

2526
## Properties
@@ -123,6 +124,20 @@ Binary response/request type. Used when the response is not a JSON value.
123124

124125
___
125126

127+
### showDeprecatedWarnings
128+
129+
`Optional` **showDeprecatedWarnings**: `boolean`
130+
131+
Shows a warning when a deprecated operation is used.
132+
133+
**`Default`**
134+
135+
```ts
136+
false
137+
```
138+
139+
___
140+
126141
### validateResponse
127142

128143
`Optional` **validateResponse**: `boolean`

src/schema-to-typescript/common/client.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ export function generateClient({
111111
responseBinaryType,
112112
binaryTypes,
113113
jsDocRenderConfig,
114-
commentsConfig
114+
commentsConfig,
115+
deprecatedOperations
115116
}: {
116117
commonHttpClientClassName: string;
117118
commonHttpClientClassOptionsName: string;
@@ -132,6 +133,7 @@ export function generateClient({
132133
binaryTypes: OpenApiClientCustomizableBinaryType[];
133134
jsDocRenderConfig: JsDocRenderConfig;
134135
commentsConfig: CommentsRenderConfig;
136+
deprecatedOperations: {[methodAndPath: string]: string};
135137
}): ClientGenerationResultFile {
136138
const clientPropertyName = 'client';
137139
const commonHttpClientImportName = 'commonHttpClient';
@@ -156,22 +158,48 @@ export function generateClient({
156158
)
157159
);
158160

161+
const generatedMethods = generateOperationMethods({
162+
paths,
163+
commonHttpClientImportName,
164+
operationImportPath: clientImportPath,
165+
operationsConfig,
166+
getModelData,
167+
validationContext,
168+
binaryTypes,
169+
jsDocRenderConfig
170+
});
171+
172+
const clientConstructorOptionsObject = objectExpression([
173+
objectProperty(identifier('baseUrl'), stringLiteral(baseUrl ?? servers[0]?.url ?? defaultServerUrl)),
174+
objectProperty(identifier('binaryResponseType'), stringLiteral(responseBinaryType)),
175+
objectProperty(identifier('errorClass'), identifier(errorTypeName))
176+
]);
177+
178+
const deprecatedOperationsTotal = {
179+
...deprecatedOperations,
180+
...generatedMethods.deprecatedOperations
181+
};
182+
183+
if (operationsConfig?.showDeprecatedWarnings && Object.keys(deprecatedOperationsTotal).length > 0) {
184+
clientConstructorOptionsObject.properties.push(
185+
objectProperty(
186+
identifier('deprecatedOperations'),
187+
objectExpression(
188+
Object.entries(deprecatedOperationsTotal).map(([methodAndPath, operationName]) =>
189+
objectProperty(stringLiteral(methodAndPath), stringLiteral(operationName))
190+
)
191+
)
192+
)
193+
);
194+
}
195+
159196
const clientClassBody = classBody([
160197
makeProtected(
161198
classProperty(
162199
identifier(clientPropertyName),
163200
newExpression(
164201
memberExpression(identifier(commonHttpClientImportName), identifier(commonHttpClientClassName)),
165-
[
166-
objectExpression([
167-
objectProperty(
168-
identifier('baseUrl'),
169-
stringLiteral(baseUrl ?? servers[0]?.url ?? defaultServerUrl)
170-
),
171-
objectProperty(identifier('binaryResponseType'), stringLiteral(responseBinaryType)),
172-
objectProperty(identifier('errorClass'), identifier(errorTypeName))
173-
])
174-
]
202+
[clientConstructorOptionsObject]
175203
)
176204
)
177205
),
@@ -242,16 +270,6 @@ export function generateClient({
242270
const optionsTypeStatement =
243271
exportOptionsType === false ? optionsTypeDeclaration : exportNamedDeclaration(optionsTypeDeclaration);
244272

245-
const generatedMethods = generateOperationMethods({
246-
paths,
247-
commonHttpClientImportName,
248-
operationImportPath: clientImportPath,
249-
operationsConfig,
250-
getModelData,
251-
validationContext,
252-
binaryTypes,
253-
jsDocRenderConfig
254-
});
255273
clientClassBody.body.push(...generatedMethods.methods);
256274
extendDependencyImports(dependencyImports, generatedMethods.dependencyImports);
257275
clientClassBody.body.push(

src/schema-to-typescript/common/core/common-http-client.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@ export interface CommonHttpClientOptions {
4949
* Custom validation error handling. Can be used to log errors.
5050
*/
5151
handleValidationError?: (error: Error) => void;
52+
/**
53+
* Deprecated operations. Used to warn about deprecated operations.
54+
*/
55+
deprecatedOperations?: {[methodAndPath: string]: string /* Operation name */};
56+
/**
57+
* Log a deprecation warning.
58+
*/
59+
logDeprecationWarning?(params: {
60+
/**
61+
* Either operation method name in case if it's not part of the service, or service name and operation method
62+
* name separated by a dot.
63+
*
64+
* Examples: `users.getUserById`, `getSystemConfig`
65+
*/
66+
operationName: string;
67+
path: string;
68+
method: CommonHttpClientFetchRequest['method'];
69+
}): void;
5270
}
5371

5472
/**
@@ -603,6 +621,12 @@ const formatParameter: Record<CommonHttpClientRequestParameterSerializeStyle, Pa
603621
}
604622
};
605623

624+
/**
625+
* Stores the deprecation warning state. For every shown deprecation warning, the method and path are stored in order to
626+
* avoid showing the same warning appearing multiple times.
627+
*/
628+
const deprecationWarningShown: {[methodAndPath: string]: boolean} = {};
629+
606630
/**
607631
* Common HTTP client. Configurable for different environments.
608632
*/
@@ -627,6 +651,27 @@ export class CommonHttpClient {
627651
return this.options;
628652
}
629653

654+
/**
655+
* Logs a deprecation warning if the operation is deprecated.
656+
*/
657+
protected logDeprecationWarningIfNecessary(params: {path: string; method: CommonHttpClientFetchRequest['method']}) {
658+
const methodAndPath = `${params.method} ${params.path}`;
659+
const operationName = this.options.deprecatedOperations?.[methodAndPath];
660+
if (!operationName) {
661+
return;
662+
}
663+
if (!deprecationWarningShown[methodAndPath]) {
664+
deprecationWarningShown[methodAndPath] = true;
665+
if (this.options.logDeprecationWarning) {
666+
this.options.logDeprecationWarning({method: params.method, path: params.path, operationName});
667+
} else {
668+
console.warn(
669+
`Deprecated API call ${this.constructor.name ?? 'ApiClient'}.${operationName}: ${methodAndPath}`
670+
);
671+
}
672+
}
673+
}
674+
630675
/**
631676
* Turns an object with query params into a URLSearchParams object.
632677
*/
@@ -720,6 +765,7 @@ export class CommonHttpClient {
720765
* Perform a request.
721766
*/
722767
public async request(request: CommonHttpClientRequest): Promise<CommonHttpClientFetchResponse> {
768+
this.logDeprecationWarningIfNecessary(request);
723769
try {
724770
request = await this.preprocessRequest(request);
725771
} catch (e) {

src/schema-to-typescript/common/operation-methods.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ export function generateOperationMethods({
170170
const modelRegisterValidationSchemaImports: Record<string, true> = {};
171171
const methodProperties: ClassProperty[] = [];
172172
const validationStatements: Statement[] = [];
173+
const deprecatedOperations: {[methodAndPath: string]: string} = {};
173174

174175
if (validateResponse && !validationContext) {
175176
throw new Error('Validation should be configured for response validation.');
@@ -211,6 +212,9 @@ export function generateOperationMethods({
211212
httpMethod
212213
})
213214
: suggestedOperationMethodName;
215+
if (operation.deprecated) {
216+
deprecatedOperations[`${httpMethod.toUpperCase()} ${path}`] = operationName;
217+
}
214218
const operationReturn = getOperationReturnType({
215219
operation,
216220
getModelData,
@@ -679,5 +683,5 @@ export function generateOperationMethods({
679683

680684
methodProperties.sort((a, b) => (a.key as Identifier).name.localeCompare((b.key as Identifier).name));
681685

682-
return {methods: methodProperties, dependencyImports, validationStatements};
686+
return {methods: methodProperties, dependencyImports, validationStatements, deprecatedOperations};
683687
}

src/schema-to-typescript/common/services.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export interface GeneratedServicesImportInfo {
4646
export interface GeneratedServices {
4747
files: ClientGenerationResultFile[];
4848
services: GeneratedServicesImportInfo[];
49+
deprecatedOperations: {[methodAndPath: string]: string};
4950
}
5051

5152
export const defaultServicesRelativeDirPath = 'services';
@@ -87,6 +88,7 @@ export function generateServices({
8788
const commonHttpClientImportName = 'commonHttpClient';
8889
const files: ClientGenerationResultFile[] = [];
8990
const services: GeneratedServicesImportInfo[] = [];
91+
const deprecatedOperations: {[methodAndPath: string]: string} = {};
9092
for (const [tag, paths] of Object.entries(taggedPaths)) {
9193
const importPath = path.join(
9294
relativeDirPath,
@@ -123,7 +125,7 @@ export function generateServices({
123125

124126
services.push({
125127
name: serviceName,
126-
tag: tag,
128+
tag,
127129
importPath,
128130
jsdoc
129131
});
@@ -141,6 +143,16 @@ export function generateServices({
141143
});
142144
const serviceClassBody = classBody([...serviceMethods.methods]);
143145

146+
Object.assign(
147+
deprecatedOperations,
148+
Object.fromEntries(
149+
Object.entries(serviceMethods.deprecatedOperations).map(([methodAndPath, operationName]) => [
150+
methodAndPath,
151+
`${applyEntityNameCase(tag, 'camelCase')}.${operationName}`
152+
])
153+
)
154+
);
155+
144156
if (serviceMethods.validationStatements.length > 0) {
145157
serviceClassBody.body.push(
146158
makeProtected(
@@ -186,5 +198,5 @@ export function generateServices({
186198
)
187199
});
188200
}
189-
return {files, services};
201+
return {files, services, deprecatedOperations};
190202
}

src/schema-to-typescript/openapi-to-typescript-client.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,12 @@ export interface OpenApiClientGeneratorConfigOperations {
685685
* @default 'blob'
686686
*/
687687
responseBinaryType?: OpenApiClientBuiltinBinaryType;
688+
/**
689+
* Shows a warning when a deprecated operation is used.
690+
*
691+
* @default false
692+
*/
693+
showDeprecatedWarnings?: boolean;
688694
}
689695

690696
/**
@@ -1125,7 +1131,7 @@ export async function openapiToTypescriptClient({
11251131
commonHttpServiceImportPath: commonHttpService.importPath,
11261132
commonHttpServiceClassName: commonHttpService.className,
11271133
clientConfig: generateConfig.client,
1128-
generatedServiceImports: generatedServices ? generatedServices.services : [],
1134+
generatedServiceImports: generatedServices?.services ?? [],
11291135
servers: document.servers ?? [],
11301136
info: document.info,
11311137
paths: extractedTags.rest,
@@ -1136,7 +1142,8 @@ export async function openapiToTypescriptClient({
11361142
responseBinaryType: generateConfig.operations?.responseBinaryType ?? 'blob',
11371143
binaryTypes,
11381144
jsDocRenderConfig: generateConfig.jsDoc,
1139-
commentsConfig: generateConfig.comments
1145+
commentsConfig: generateConfig.comments,
1146+
deprecatedOperations: generatedServices?.deprecatedOperations ?? {}
11401147
})
11411148
);
11421149
}

test/pet-store/api-typescript-generator-config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export default async function (): Promise<ApiTypescriptGeneratorConfig> {
1818
},
1919
operations: {
2020
validateResponse: true,
21-
makeResponseValidationSchemasExtensible: true
21+
makeResponseValidationSchemasExtensible: true,
22+
showDeprecatedWarnings: true
2223
},
2324
validation: {
2425
library: 'zod'

0 commit comments

Comments
 (0)