Skip to content

Commit 0a9e84d

Browse files
Merge pull request #42 from contentstack/next
DX | 05-07-2024 | Hotfix
2 parents 56567b2 + f1f6409 commit 0a9e84d

File tree

7 files changed

+125
-71
lines changed

7 files changed

+125
-71
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## Change log
22

3+
4+
### Version: 1.0.3
5+
#### Date: July-08-2024
6+
- Fixed retry response error handling
7+
38
### Version: 1.0.2
49
#### Date: April-02-2024
510
- Update dependency packages

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/core",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"type": "commonjs",
55
"main": "./dist/cjs/src/index.js",
66
"types": "./dist/cjs/src/index.d.ts",
@@ -21,7 +21,7 @@
2121
"axios": "^1.6.8",
2222
"axios-mock-adapter": "^1.22.0",
2323
"lodash": "^4.17.21",
24-
"qs": "^6.12.0",
24+
"qs": "^6.12.1",
2525
"tslib": "^2.6.2"
2626
},
2727
"files": [

src/lib/request.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { AxiosInstance } from './types';
1+
import { Axios } from 'axios';
22

3-
export async function getData(instance: AxiosInstance, url: string, data?: any) {
3+
export async function getData(instance: Axios, url: string, data?: any) {
44
try {
55
const response = await instance.get(url, { params: data });
6-
if (response.data) {
6+
if (response && response.data) {
77
return response.data;
88
} else {
99
throw Error(JSON.stringify(response));
1010
}
1111
} catch (err) {
12-
throw err;
12+
throw Error(JSON.stringify(err));
1313
}
1414
}
Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
1+
/* eslint-disable @typescript-eslint/no-throw-literal */
2+
import axios, { InternalAxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios';
23

34
declare module 'axios' {
45
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -14,60 +15,68 @@ const defaultConfig = {
1415
};
1516

1617
export const retryRequestHandler = (req: InternalAxiosRequestConfig<any>): InternalAxiosRequestConfig<any> => {
17-
req.retryCount = req.retryCount || 0;
18+
req.retryCount = req.retryCount || 1;
1819

1920
return req;
2021
};
2122

2223
export const retryResponseHandler = (response: AxiosResponse) => response;
2324

24-
export const retryResponseErrorHandler = async (error: any, config: any): Promise<any> => {
25-
let retryCount = error.config.retryCount;
26-
// let retryErrorType = null;
25+
export const retryResponseErrorHandler = (error: any, config: any, axiosInstance: AxiosInstance) => {
26+
try {
27+
let retryCount = error.config.retryCount;
28+
config = { ...defaultConfig, ...config };
2729

28-
config = { ...defaultConfig, ...config };
29-
30-
if (!error.config.retryOnError || retryCount > config.retryLimit) {
31-
return Promise.reject(error);
32-
}
33-
34-
const response = error.response;
35-
if (!response) {
36-
if (error.code === 'ECONNABORTED') {
37-
error.response = {
38-
...error.response,
39-
status: 408,
40-
statusText: `timeout of ${config.timeout}ms exceeded`,
41-
};
42-
43-
return Promise.resolve(error.response);
44-
} else {
45-
return Promise.reject(error);
30+
if (!error.config.retryOnError || retryCount > config.retryLimit) {
31+
throw error;
4632
}
47-
} else if (response.status == 429 || response.status == 401) {
48-
retryCount++;
49-
// retryErrorType = `Error with status: ${response.status}`;
5033

51-
if (retryCount > config.retryLimit) {
52-
return Promise.reject(error);
34+
const response = error.response;
35+
if (!response) {
36+
if (error.code === 'ECONNABORTED') {
37+
const customError = {
38+
error_message: `Timeout of ${config.timeout}ms exceeded`,
39+
error_code: 408,
40+
errors: null,
41+
};
42+
throw customError; // Throw customError object
43+
} else {
44+
throw error;
45+
}
46+
} else if (response.status == 429 || response.status == 401) {
47+
retryCount++;
48+
49+
if (retryCount >= config.retryLimit) {
50+
if (error.response && error.response.data) {
51+
return Promise.reject(error.response.data);
52+
}
53+
return Promise.reject(error);
54+
}
55+
error.config.retryCount = retryCount;
56+
57+
return axiosInstance(error.config);
5358
}
5459

55-
await new Promise((resolve) => setTimeout(resolve, 1000));
56-
57-
error.config.retryCount = retryCount;
60+
if (config.retryCondition && config.retryCondition(error)) {
61+
retryCount++;
5862

59-
return axios(error.request);
60-
}
61-
62-
if (config.retryCondition && config.retryCondition(error)) {
63-
// retryErrorType = error.response ? `Error with status: ${response.status}` : `Error Code:${error.code}`;
64-
retryCount++;
63+
return retry(error, config, retryCount, config.retryDelay, axiosInstance);
64+
}
6565

66-
return retry(error, config, retryCount, config.retryDelay);
66+
const customError = {
67+
status: response.status,
68+
statusText: response.statusText,
69+
error_message: response.data.error_message,
70+
error_code: response.data.error_code,
71+
errors: response.data.errors,
72+
};
73+
74+
throw customError;
75+
} catch (err) {
76+
throw err;
6777
}
6878
};
69-
70-
const retry = (error: any, config: any, retryCount: number, retryDelay: number) => {
79+
const retry = (error: any, config: any, retryCount: number, retryDelay: number, axiosInstance: AxiosInstance) => {
7180
let delayTime: number = retryDelay;
7281
if (retryCount > config.retryLimit) {
7382
return Promise.reject(error);
@@ -78,7 +87,7 @@ const retry = (error: any, config: any, retryCount: number, retryDelay: number)
7887

7988
return new Promise(function (resolve) {
8089
return setTimeout(function () {
81-
return resolve(axios(error.request));
90+
return resolve(axiosInstance(error.request));
8291
}, delayTime);
8392
});
8493
};

test/retryPolicy/delivery-sdk-handlers.spec.ts

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('retryRequestHandler', () => {
1212
const requestConfig: InternalAxiosRequestConfig = { headers: {} as AxiosHeaders };
1313
const updatedConfig = retryRequestHandler(requestConfig);
1414

15-
expect(updatedConfig.retryCount).toBe(0);
15+
expect(updatedConfig.retryCount).toBe(1);
1616
});
1717
});
1818

@@ -40,40 +40,65 @@ describe('retryResponseErrorHandler', () => {
4040
it('should reject the promise if retryOnError is false', async () => {
4141
const error = { config: { retryOnError: false }, code: 'ECONNABORTED' };
4242
const config = { retryLimit: 5 };
43-
44-
await expect(retryResponseErrorHandler(error, config)).rejects.toBe(error);
43+
const client = axios.create();
44+
45+
try {
46+
await retryResponseErrorHandler(error, config, client);
47+
fail('Expected retryResponseErrorHandler to throw an error');
48+
} catch (err) {
49+
expect(err).toEqual(expect.objectContaining({
50+
code: 'ECONNABORTED',
51+
config: expect.objectContaining({ retryOnError: false }),
52+
}));
53+
}
4554
});
4655
it('should reject the promise if retryOnError is true', async () => {
4756
const error = { config: { retryOnError: true } };
4857
const config = { retryLimit: 5 };
49-
50-
await expect(retryResponseErrorHandler(error, config)).rejects.toBe(error);
58+
const client = axios.create();
59+
60+
try {
61+
await retryResponseErrorHandler(error, config, client);
62+
fail('Expected retryResponseErrorHandler to throw an error');
63+
} catch (err: any) {
64+
expect(err.config).toEqual(expect.objectContaining({ retryOnError: true }));
65+
expect(err).toEqual(error);
66+
}
5167
});
5268
it('should resolve the promise to 408 error if retryOnError is true and error code is ECONNABORTED', async () => {
5369
const error = { config: { retryOnError: true, retryCount: 1 }, code: 'ECONNABORTED' };
5470
const config = { retryLimit: 5, timeout: 1000 };
55-
56-
const errorResponse = { status: 408, statusText: 'timeout of 1000ms exceeded' };
57-
58-
await expect(retryResponseErrorHandler(error, config)).resolves.toEqual(errorResponse);
71+
const client = axios.create();
72+
try {
73+
await retryResponseErrorHandler(error, config, client);
74+
fail('Expected retryResponseErrorHandler to throw an error');
75+
} catch (err) {
76+
expect(err).toEqual(expect.objectContaining({
77+
error_code: 408,
78+
error_message: `Timeout of ${config.timeout}ms exceeded`,
79+
errors: null
80+
}));
81+
}
5982
});
6083
it('should reject the promise if response status is 429 and retryCount exceeds retryLimit', async () => {
6184
const error = {
6285
config: { retryOnError: true, retryCount: 5 },
6386
response: { status: 429, statusText: 'timeout of 1000ms exceeded' },
6487
};
6588
const config = { retryLimit: 5, timeout: 1000 };
89+
const client = axios.create();
6690

67-
await expect(retryResponseErrorHandler(error, config)).rejects.toBe(error);
91+
await expect(retryResponseErrorHandler(error, config, client)).rejects.toBe(error);
6892
});
6993
it('should reject the promise if response status is 401 and retryCount exceeds retryLimit', async () => {
7094
const error = {
7195
config: { retryOnError: true, retryCount: 5 },
7296
response: { status: 401, statusText: 'timeout of 1000ms exceeded' },
7397
};
7498
const config = { retryLimit: 5, timeout: 1000 };
99+
const client = axios.create();
75100

76-
await expect(retryResponseErrorHandler(error, config)).rejects.toBe(error);
101+
await expect(retryResponseErrorHandler(error, config, client)).rejects.toBe(error);
77102
});
78103
it('should reject the promise if response status is 429 or 401 and retryCount is within limit', async () => {
79104
const error = {
@@ -87,17 +112,24 @@ describe('retryResponseErrorHandler', () => {
87112
},
88113
};
89114
const config = { retryLimit: 5, timeout: 1000 };
115+
const client = axios.create();
90116

91117
const finalResponseObj = {
92-
config: { retryOnError: true, retryCount: 5 },
118+
config: { retryOnError: true, retryCount: 4 },
93119
response: { status: 429, statusText: 'timeout of 1000ms exceeded' },
94120
};
95121

96122
mock.onPost('/retryURL').reply(200, finalResponseObj);
97123

98-
const finalResponse = await retryResponseErrorHandler(error, config);
124+
try {
125+
await retryResponseErrorHandler(error, config, client);
126+
throw new Error('Expected retryResponseErrorHandler to throw an error');
127+
} catch (err: any) {
128+
expect(err.response.status).toBe(429);
129+
expect(err.response.statusText).toBe(error.response.statusText);
130+
expect(err.config.retryCount).toBe(error.config.retryCount);
131+
}
99132

100-
expect(finalResponse.data).toEqual(finalResponseObj);
101133
});
102134
it('should call the retry function if retryCondition is passed', async () => {
103135
const error = {
@@ -113,6 +145,7 @@ describe('retryResponseErrorHandler', () => {
113145
// eslint-disable-next-line @typescript-eslint/no-shadow
114146
const retryCondition = (error: any) => true;
115147
const config = { retryLimit: 5, timeout: 1000, retryCondition: retryCondition };
148+
const client = axios.create();
116149

117150
const finalResponseObj = {
118151
config: { retryOnError: true, retryCount: 5 },
@@ -121,7 +154,7 @@ describe('retryResponseErrorHandler', () => {
121154

122155
mock.onPost('/retryURL').reply(200, finalResponseObj);
123156

124-
const finalResponse = await retryResponseErrorHandler(error, config);
157+
const finalResponse: any = await retryResponseErrorHandler(error, config, client);
125158

126159
expect(finalResponse.data).toEqual(finalResponseObj);
127160
});
@@ -139,6 +172,7 @@ describe('retryResponseErrorHandler', () => {
139172
// eslint-disable-next-line @typescript-eslint/no-shadow
140173
const retryCondition = (error: any) => true;
141174
const config = { retryLimit: 5, timeout: 1000, retryCondition: retryCondition };
175+
const client = axios.create();
142176

143177
const finalResponseObj = {
144178
config: { retryOnError: true, retryCount: 5 },
@@ -147,6 +181,6 @@ describe('retryResponseErrorHandler', () => {
147181

148182
mock.onPost('/retryURL').reply(200, finalResponseObj);
149183

150-
await expect(retryResponseErrorHandler(error, config)).rejects.toBe(error);
184+
await expect(retryResponseErrorHandler(error, config, client)).rejects.toBe(error);
151185
});
152186
});

tools/cleanup.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ const fs = require('fs');
33
const Path = require('path');
44
/* eslint-enable */
55

6-
const deleteFolderRecursive = (path) => {
6+
const sanitizePath = (inputPath) => {
7+
return Path.normalize(inputPath).replace(/^(\.\.(\/|\\|$))+/, '');
8+
};
9+
10+
const deleteFolderRecursive = (inputPath) => {
11+
const path = sanitizePath(inputPath);
12+
713
if (fs.existsSync(path)) {
814
fs.readdirSync(path).forEach((file) => {
9-
const curPath = Path.join(path, file);
15+
const curPath = Path.join(path, sanitizePath(file));
1016
if (fs.lstatSync(curPath).isDirectory()) {
1117
deleteFolderRecursive(curPath);
1218
} else {

0 commit comments

Comments
 (0)