Skip to content

Commit 7b6bdc2

Browse files
committed
feat: add support aborting requests in tags interface
- support was added to HTTP Client for OAS operations - support was added to Tags Interface Older versions of swagger-client can use request interceptors to inject abort signal into requests. Refs #2349
1 parent da25c95 commit 7b6bdc2

File tree

7 files changed

+244
-0
lines changed

7 files changed

+244
-0
lines changed

docs/usage/http-client-for-oas-operations.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Property | Description
2626
`attachContentTypeForEmptyPayload` | `Boolean=false`. Attaches a `Content-Type` header to a `Request` even when no payload was provided for the `Request`.
2727
`http` | `Function=Http`. A function with an interface compatible with [HTTP Client](http-client.md).
2828
`userFetch` | `Function=cross-fetch`. Custom **asynchronous** fetch function that accepts two arguments: the `url` and the `Request` object and must return a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object. More info in [HTTP Client](http-client.md) documentation.
29+
`signal` | `AbortSignal=null`. AbortSignal object instance, which can be used to abort a request as desired.
2930

3031
For all later references, we will always use following OpenAPI 3.0.0 definition when referring
3132
to a `spec`.
@@ -153,6 +154,88 @@ SwaggerClient.execute({
153154
}); // => Promise.<Response>
154155
```
155156

157+
#### Request cancellation with AbortSignal
158+
159+
You may cancel requests with [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
160+
The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
161+
Using AbortController, you can easily implement request timeouts.
162+
163+
###### Node.js
164+
165+
AbortController needs to be introduced in Node.js environment via [abort-controller](https://www.npmjs.com/package/abort-controller) npm package.
166+
167+
```js
168+
const SwaggerClient = require('swagger-client');
169+
const AbortController = require('abort-controller');
170+
171+
const controller = new AbortController();
172+
const { signal } = controller;
173+
const timeout = setTimeout(() => {
174+
controller.abort();
175+
}, 1);
176+
177+
(async () => {
178+
try {
179+
await SwaggerClient.execute({
180+
spec,
181+
pathName: '/users',
182+
method: 'get',
183+
parameters: { q: 'search string' },
184+
securities: { authorized: { BearerAuth: "3492342948239482398" } },
185+
signal,
186+
});
187+
} catch (error) {
188+
if (error.name === 'AbortError') {
189+
console.error('request was aborted');
190+
}
191+
} finally {
192+
clearTimeout(timeout);
193+
}
194+
})();
195+
```
196+
197+
###### Browser
198+
199+
AbortController is part of modern [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
200+
No need to install it explicitly.
201+
202+
```html
203+
<html>
204+
<head>
205+
<script src="//unpkg.com/swagger-client"></script>
206+
<script>
207+
const controller = new AbortController();
208+
const { signal } = controller;
209+
const timeout = setTimeout(() => {
210+
controller.abort();
211+
}, 1);
212+
213+
(async () => {
214+
try {
215+
await SwaggerClient.execute({
216+
spec,
217+
pathName: '/users',
218+
method: 'get',
219+
parameters: { q: 'search string' },
220+
securities: { authorized: { BearerAuth: "3492342948239482398" } },
221+
signal,
222+
});
223+
} catch (error) {
224+
if (error.name === 'AbortError') {
225+
console.error('request was aborted');
226+
}
227+
} finally {
228+
clearTimeout(timeout);
229+
}
230+
})();
231+
</script>
232+
</head>
233+
<body>
234+
check console in browser's dev. tools
235+
</body>
236+
</html>
237+
```
238+
156239
#### Alternate API
157240

158241
It's also possible to call `execute` method from `SwaggerClient` instance.

docs/usage/tags-interface.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,73 @@ SwaggerClient({ url: 'http://petstore.swagger.io/v2/swagger.json' })
267267
</body>
268268
</html>
269269
```
270+
271+
#### Request cancellation with AbortSignal
272+
273+
You may cancel requests with [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
274+
The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
275+
Using AbortController, you can easily implement request timeouts.
276+
277+
###### Node.js
278+
279+
AbortController needs to be introduced in Node.js environment via [abort-controller](https://www.npmjs.com/package/abort-controller) npm package.
280+
281+
```js
282+
const SwaggerClient = require('swagger-client');
283+
const AbortController = require('abort-controller');
284+
285+
const controller = new AbortController();
286+
const { signal } = controller;
287+
const timeout = setTimeout(() => {
288+
controller.abort();
289+
}, 1);
290+
291+
(async () => {
292+
try {
293+
await new SwaggerClient({ spec })
294+
.then(client => client.apis.default.getUserList({}, { signal }))
295+
} catch (error) {
296+
if (error.name === 'AbortError') {
297+
console.error('request was aborted');
298+
}
299+
} finally {
300+
clearTimeout(timeout);
301+
}
302+
})();
303+
```
304+
305+
###### Browser
306+
307+
AbortController is part of modern [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
308+
No need to install it explicitly.
309+
310+
```html
311+
<html>
312+
<head>
313+
<script src="//unpkg.com/swagger-client"></script>
314+
<script>
315+
const controller = new AbortController();
316+
const { signal } = controller;
317+
const timeout = setTimeout(() => {
318+
controller.abort();
319+
}, 1);
320+
321+
(async () => {
322+
try {
323+
await new SwaggerClient({ spec })
324+
.then(client => client.apis.default.getUserList({}, { signal }))
325+
} catch (error) {
326+
if (error.name === 'AbortError') {
327+
console.error('request was aborted');
328+
}
329+
} finally {
330+
clearTimeout(timeout);
331+
}
332+
})();
333+
</script>
334+
</head>
335+
<body>
336+
check console in browser's dev. tools
337+
</body>
338+
</html>
339+
```

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"@babel/register": "=7.16.5",
7373
"@commitlint/cli": "^15.0.0",
7474
"@commitlint/config-conventional": "^15.0.0",
75+
"abort-controller": "^3.0.0",
7576
"babel-loader": "=8.2.3",
7677
"babel-plugin-lodash": "=3.3.4",
7778
"cross-env": "=7.0.3",

src/execute/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export function buildRequest(options) {
9898
server,
9999
serverVariables,
100100
http,
101+
signal,
101102
} = options;
102103

103104
let { parameters, parameterBuilders } = options;
@@ -123,6 +124,10 @@ export function buildRequest(options) {
123124
cookies: {},
124125
};
125126

127+
if (signal) {
128+
req.signal = signal;
129+
}
130+
126131
if (requestInterceptor) {
127132
req.requestInterceptor = requestInterceptor;
128133
}

test/execute/main.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Readable } from 'stream';
2+
import AbortController from 'abort-controller';
23

34
import { execute, buildRequest, self as stubs } from '../../src/execute/index.js';
45
import { normalizeSwagger } from '../../src/helpers.js';
@@ -157,6 +158,48 @@ describe('execute', () => {
157158
});
158159
});
159160

161+
test('should allow aborting request during execution', async () => {
162+
// cross-fetch exposes FetchAPI methods onto global
163+
require('cross-fetch/polyfill');
164+
165+
// Given
166+
const spec = {
167+
host: 'swagger.io',
168+
schemes: ['https'],
169+
paths: {
170+
'/one': {
171+
get: {
172+
operationId: 'getMe',
173+
},
174+
},
175+
},
176+
};
177+
178+
const spy = jest.fn().mockImplementation(() => Promise.resolve(new Response('data')));
179+
const controller = new AbortController();
180+
const { signal } = controller;
181+
182+
const response = execute({
183+
userFetch: spy,
184+
spec,
185+
operationId: 'getMe',
186+
signal,
187+
});
188+
189+
controller.abort();
190+
await response;
191+
192+
expect(spy.mock.calls.length).toEqual(1);
193+
expect(spy.mock.calls[0][1]).toEqual({
194+
method: 'GET',
195+
url: 'https://swagger.io/one',
196+
credentials: 'same-origin',
197+
headers: {},
198+
userFetch: spy,
199+
signal,
200+
});
201+
});
202+
160203
test('should include values for query parameters', () => {
161204
// Given
162205
const spec = {

test/interfaces.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import AbortController from 'abort-controller';
2+
13
import {
24
mapTagOperations,
35
makeApisTagOperationsOperationExecute,
@@ -108,6 +110,31 @@ describe('intefaces', () => {
108110
});
109111
});
110112

113+
test('should pass signal option to execute', () => {
114+
// Given
115+
const spyMapTagOperations = jest.spyOn(stubs, 'mapTagOperations');
116+
const spyExecute = jest.fn();
117+
makeApisTagOperationsOperationExecute({ execute: spyExecute });
118+
const { cb } = spyMapTagOperations.mock.calls[0][0];
119+
120+
// When
121+
const controller = new AbortController();
122+
const { signal } = controller;
123+
const executer = cb({ pathName: '/one', method: 'GET' });
124+
executer(['param'], { signal });
125+
126+
// Then
127+
expect(spyExecute.mock.calls.length).toEqual(1);
128+
expect(spyExecute.mock.calls[0][0]).toEqual({
129+
spec: undefined,
130+
operationId: undefined,
131+
method: 'GET',
132+
parameters: ['param'],
133+
pathName: '/one',
134+
signal,
135+
});
136+
});
137+
111138
test('should map tagOperations to execute', () => {
112139
const interfaceValue = makeApisTagOperationsOperationExecute({
113140
spec: {

0 commit comments

Comments
 (0)