Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support aborting requests in tags interface #2380

Merged
merged 1 commit into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/usage/http-client-for-oas-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Property | Description
`attachContentTypeForEmptyPayload` | `Boolean=false`. Attaches a `Content-Type` header to a `Request` even when no payload was provided for the `Request`.
`http` | `Function=Http`. A function with an interface compatible with [HTTP Client](http-client.md).
`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.
`signal` | `AbortSignal=null`. AbortSignal object instance, which can be used to abort a request as desired.

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

#### Request cancellation with AbortSignal

You may cancel requests with [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
Using AbortController, you can easily implement request timeouts.

###### Node.js

AbortController needs to be introduced in Node.js environment via [abort-controller](https://www.npmjs.com/package/abort-controller) npm package.

```js
const SwaggerClient = require('swagger-client');
const AbortController = require('abort-controller');

const controller = new AbortController();
const { signal } = controller;
const timeout = setTimeout(() => {
controller.abort();
}, 1);

(async () => {
try {
await SwaggerClient.execute({
spec,
pathName: '/users',
method: 'get',
parameters: { q: 'search string' },
securities: { authorized: { BearerAuth: "3492342948239482398" } },
signal,
});
} catch (error) {
if (error.name === 'AbortError') {
console.error('request was aborted');
}
} finally {
clearTimeout(timeout);
}
})();
```

###### Browser

AbortController is part of modern [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
No need to install it explicitly.

```html
<html>
<head>
<script src="//unpkg.com/swagger-client"></script>
<script>
const controller = new AbortController();
const { signal } = controller;
const timeout = setTimeout(() => {
controller.abort();
}, 1);

(async () => {
try {
await SwaggerClient.execute({
spec,
pathName: '/users',
method: 'get',
parameters: { q: 'search string' },
securities: { authorized: { BearerAuth: "3492342948239482398" } },
signal,
});
} catch (error) {
if (error.name === 'AbortError') {
console.error('request was aborted');
}
} finally {
clearTimeout(timeout);
}
})();
</script>
</head>
<body>
check console in browser's dev. tools
</body>
</html>
```

#### Alternate API

It's also possible to call `execute` method from `SwaggerClient` instance.
Expand Down
70 changes: 70 additions & 0 deletions docs/usage/tags-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,73 @@ SwaggerClient({ url: 'http://petstore.swagger.io/v2/swagger.json' })
</body>
</html>
```

#### Request cancellation with AbortSignal

You may cancel requests with [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
Using AbortController, you can easily implement request timeouts.

###### Node.js

AbortController needs to be introduced in Node.js environment via [abort-controller](https://www.npmjs.com/package/abort-controller) npm package.

```js
const SwaggerClient = require('swagger-client');
const AbortController = require('abort-controller');

const controller = new AbortController();
const { signal } = controller;
const timeout = setTimeout(() => {
controller.abort();
}, 1);

(async () => {
try {
await new SwaggerClient({ spec })
.then(client => client.apis.default.getUserList({}, { signal }))
} catch (error) {
if (error.name === 'AbortError') {
console.error('request was aborted');
}
} finally {
clearTimeout(timeout);
}
})();
```

###### Browser

AbortController is part of modern [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
No need to install it explicitly.

```html
<html>
<head>
<script src="//unpkg.com/swagger-client"></script>
<script>
const controller = new AbortController();
const { signal } = controller;
const timeout = setTimeout(() => {
controller.abort();
}, 1);

(async () => {
try {
await new SwaggerClient({ spec })
.then(client => client.apis.default.getUserList({}, { signal }))
} catch (error) {
if (error.name === 'AbortError') {
console.error('request was aborted');
}
} finally {
clearTimeout(timeout);
}
})();
</script>
</head>
<body>
check console in browser's dev. tools
</body>
</html>
```
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@babel/register": "=7.16.5",
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0",
"abort-controller": "^3.0.0",
"babel-loader": "=8.2.3",
"babel-plugin-lodash": "=3.3.4",
"cross-env": "=7.0.3",
Expand Down
5 changes: 5 additions & 0 deletions src/execute/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export function buildRequest(options) {
server,
serverVariables,
http,
signal,
} = options;

let { parameters, parameterBuilders } = options;
Expand All @@ -123,6 +124,10 @@ export function buildRequest(options) {
cookies: {},
};

if (signal) {
req.signal = signal;
}

if (requestInterceptor) {
req.requestInterceptor = requestInterceptor;
}
Expand Down
43 changes: 43 additions & 0 deletions test/execute/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Readable } from 'stream';
import AbortController from 'abort-controller';

import { execute, buildRequest, self as stubs } from '../../src/execute/index.js';
import { normalizeSwagger } from '../../src/helpers.js';
Expand Down Expand Up @@ -157,6 +158,48 @@ describe('execute', () => {
});
});

test('should allow aborting request during execution', async () => {
// cross-fetch exposes FetchAPI methods onto global
require('cross-fetch/polyfill');

// Given
const spec = {
host: 'swagger.io',
schemes: ['https'],
paths: {
'/one': {
get: {
operationId: 'getMe',
},
},
},
};

const spy = jest.fn().mockImplementation(() => Promise.resolve(new Response('data')));
const controller = new AbortController();
const { signal } = controller;

const response = execute({
userFetch: spy,
spec,
operationId: 'getMe',
signal,
});

controller.abort();
await response;

expect(spy.mock.calls.length).toEqual(1);
expect(spy.mock.calls[0][1]).toEqual({
method: 'GET',
url: 'https://swagger.io/one',
credentials: 'same-origin',
headers: {},
userFetch: spy,
signal,
});
});

test('should include values for query parameters', () => {
// Given
const spec = {
Expand Down
27 changes: 27 additions & 0 deletions test/interfaces.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import AbortController from 'abort-controller';

import {
mapTagOperations,
makeApisTagOperationsOperationExecute,
Expand Down Expand Up @@ -108,6 +110,31 @@ describe('intefaces', () => {
});
});

test('should pass signal option to execute', () => {
// Given
const spyMapTagOperations = jest.spyOn(stubs, 'mapTagOperations');
const spyExecute = jest.fn();
makeApisTagOperationsOperationExecute({ execute: spyExecute });
const { cb } = spyMapTagOperations.mock.calls[0][0];

// When
const controller = new AbortController();
const { signal } = controller;
const executer = cb({ pathName: '/one', method: 'GET' });
executer(['param'], { signal });

// Then
expect(spyExecute.mock.calls.length).toEqual(1);
expect(spyExecute.mock.calls[0][0]).toEqual({
spec: undefined,
operationId: undefined,
method: 'GET',
parameters: ['param'],
pathName: '/one',
signal,
});
});

test('should map tagOperations to execute', () => {
const interfaceValue = makeApisTagOperationsOperationExecute({
spec: {
Expand Down