Skip to content

Commit 367ac09

Browse files
committed
feat: option to retry in case of an error: shouldRetryOnError
1 parent 061f5ce commit 367ac09

File tree

5 files changed

+86
-36
lines changed

5 files changed

+86
-36
lines changed

docs/interfaces/openapi_client.CommonHttpClientOptions.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Options for the common HTTP client.
2121
- [headers](openapi_client.CommonHttpClientOptions.md#headers)
2222
- [preprocessFetchResponse](openapi_client.CommonHttpClientOptions.md#preprocessfetchresponse)
2323
- [preprocessRequest](openapi_client.CommonHttpClientOptions.md#preprocessrequest)
24+
- [shouldRetryOnError](openapi_client.CommonHttpClientOptions.md#shouldretryonerror)
2425

2526
### Methods
2627

@@ -205,6 +206,29 @@ Preprocess the request before sending it.
205206

206207
`Promise`\<[`CommonHttpClientRequest`](../modules/openapi_client.md#commonhttpclientrequest)\>
207208

209+
___
210+
211+
### shouldRetryOnError
212+
213+
`Optional` **shouldRetryOnError**: (`error`: `Error`, `attemptNumber`: `number`) => `boolean`
214+
215+
#### Type declaration
216+
217+
▸ (`error`, `attemptNumber`): `boolean`
218+
219+
Determine whether to retry on error.
220+
221+
##### Parameters
222+
223+
| Name | Type |
224+
| :------ | :------ |
225+
| `error` | `Error` |
226+
| `attemptNumber` | `number` |
227+
228+
##### Returns
229+
230+
`boolean`
231+
208232
## Methods
209233

210234
### logDeprecationWarning

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
},
9191
"husky": {
9292
"hooks": {
93-
"pre-commit": "npm run lint && npm run test",
93+
"pre-commit": "npm run lint",
9494
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
9595
}
9696
},

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

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ export interface CommonHttpClientOptions {
7171
path: string;
7272
method: CommonHttpClientFetchRequest['method'];
7373
}): void;
74+
/**
75+
* Determine whether to retry on error.
76+
*/
77+
shouldRetryOnError?: (error: Error, attemptNumber: number) => boolean;
7478
}
7579

7680
/**
@@ -825,41 +829,51 @@ export class CommonHttpClient {
825829
redirect: redirect ?? 'follow',
826830
body: this.getRequestBody(request)
827831
};
828-
let fetchResponse: CommonHttpClientFetchResponse;
829-
try {
830-
if (this.options.fetch) {
831-
fetchResponse = await this.options.fetch(url, fetchRequest);
832-
} else {
833-
fetchResponse = await this.fetch(url, fetchRequest);
834-
}
835-
} catch (e) {
836-
throw new this.options.errorClass(url, fetchRequest, undefined, this.options, getErrorMessage(e));
837-
}
838-
if (this.options.preprocessFetchResponse) {
832+
let attemptNumber = 1;
833+
for (;;) {
839834
try {
840-
fetchResponse = await this.options.preprocessFetchResponse(fetchResponse, fetchRequest);
841-
} catch (e) {
842-
throw new this.options.errorClass(
843-
url,
844-
fetchRequest,
845-
fetchResponse,
846-
this.options,
847-
`preprocessFetchResponse error: ${getErrorMessage(e)}`
848-
);
835+
let fetchResponse: CommonHttpClientFetchResponse;
836+
try {
837+
if (this.options.fetch) {
838+
fetchResponse = await this.options.fetch(url, fetchRequest);
839+
} else {
840+
fetchResponse = await this.fetch(url, fetchRequest);
841+
}
842+
} catch (e) {
843+
throw new this.options.errorClass(url, fetchRequest, undefined, this.options, getErrorMessage(e));
844+
}
845+
if (this.options.preprocessFetchResponse) {
846+
try {
847+
fetchResponse = await this.options.preprocessFetchResponse(fetchResponse, fetchRequest);
848+
} catch (e) {
849+
throw new this.options.errorClass(
850+
url,
851+
fetchRequest,
852+
fetchResponse,
853+
this.options,
854+
`preprocessFetchResponse error: ${getErrorMessage(e)}`
855+
);
856+
}
857+
}
858+
if (!fetchResponse.ok) {
859+
throw new this.options.errorClass(
860+
url,
861+
fetchRequest,
862+
fetchResponse,
863+
this.options,
864+
this.options.formatHttpErrorMessage
865+
? this.options.formatHttpErrorMessage(fetchResponse, fetchRequest)
866+
: `HTTP Error ${request.method} ${url.toString()} ${fetchResponse.status} (${fetchResponse.statusText})`
867+
);
868+
}
869+
return fetchResponse;
870+
} catch (error) {
871+
if (!this.options.shouldRetryOnError?.(error as Error, attemptNumber)) {
872+
throw error;
873+
}
874+
attemptNumber++;
849875
}
850876
}
851-
if (!fetchResponse.ok) {
852-
throw new this.options.errorClass(
853-
url,
854-
fetchRequest,
855-
fetchResponse,
856-
this.options,
857-
this.options.formatHttpErrorMessage
858-
? this.options.formatHttpErrorMessage(fetchResponse, fetchRequest)
859-
: `HTTP Error ${request.method} ${url.toString()} ${fetchResponse.status} (${fetchResponse.statusText})`
860-
);
861-
}
862-
return fetchResponse;
863877
}
864878

865879
/**

test/pet-store/pet-service.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
/* eslint-disable @typescript-eslint/no-var-requires */
2-
import {CommonHttpClientFetchRequest} from './petstore-api-client/core/common-http-client';
2+
import {CommonHttpClientError, CommonHttpClientFetchRequest} from './petstore-api-client/core/common-http-client';
3+
4+
export function shouldRetryOnError(error: Error, attemptNumber: number) {
5+
if (attemptNumber >= 3) {
6+
return false;
7+
}
8+
return (
9+
error instanceof CommonHttpClientError &&
10+
(!error.response || (error.response.status >= 500 && error.response.status < 600))
11+
);
12+
}
313

414
describe('pet-service', () => {
515
beforeEach(() => {
@@ -8,7 +18,7 @@ describe('pet-service', () => {
818

919
it('should show a deprecation warning', () => {
1020
const {PetStoreApiClient} = require('./petstore-api-client/pet-store-api-client');
11-
const client = new PetStoreApiClient({baseUrl: 'https://petstore.swagger.io/v2'});
21+
const client = new PetStoreApiClient({baseUrl: 'https://petstore.swagger.io/v2', shouldRetryOnError});
1222
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
1323
client.pet.findPetsByTags({tags: ['tag']});
1424
client.pet.findPetsByTags({tags: ['tag']});
@@ -22,6 +32,7 @@ describe('pet-service', () => {
2232
const {PetStoreApiClient} = require('./petstore-api-client/pet-store-api-client');
2333
const client = new PetStoreApiClient({
2434
baseUrl: 'https://petstore.swagger.io/v2',
35+
shouldRetryOnError,
2536
logDeprecationWarning(params: {
2637
operationName: string;
2738
path: string;

test/pet-store/store-service.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import {shouldRetryOnError} from './pet-service.test';
12
import {checkReponseMediaType, CommonHttpClientError} from './petstore-api-client/core/common-http-client';
23
import {PetStoreApiClient} from './petstore-api-client/pet-store-api-client';
34

4-
const client = new PetStoreApiClient({baseUrl: 'https://petstore.swagger.io/v2'});
5+
const client = new PetStoreApiClient({baseUrl: 'https://petstore.swagger.io/v2', shouldRetryOnError});
56

67
describe('store-service', () => {
78
it('should get inventory', async () => {

0 commit comments

Comments
 (0)