@usearazzo/runner 1.0.1-alpha.11 → 1.0.1-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +1 -3
- package/dist/arazzo-runner.browser.js +2053 -146
- package/dist/arazzo-runner.browser.min.js +1 -1
- package/package.json +5 -4
- package/src/client/OpenAPIOperationResponse.cjs +37 -2
- package/src/client/OpenAPIOperationResponse.mjs +37 -2
- package/src/client/OpenAPIOperationResponseNormalizer.cjs +47 -3
- package/src/client/OpenAPIOperationResponseNormalizer.mjs +48 -3
- package/src/executor/OpenAPIOperationExecutor.cjs +144 -1
- package/src/executor/OpenAPIOperationExecutor.mjs +145 -1
- package/types/runner.d.ts +30 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usearazzo/runner",
|
|
3
|
-
"version": "1.0.1-alpha.
|
|
3
|
+
"version": "1.0.1-alpha.13",
|
|
4
4
|
"description": "Runner for Arazzo Workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arazzo",
|
|
@@ -69,11 +69,12 @@
|
|
|
69
69
|
"@speclynx/apidom-traverse": "^6.1.0",
|
|
70
70
|
"@swaggerexpert/arazzo-criterion": "^1.0.1",
|
|
71
71
|
"@swaggerexpert/arazzo-runtime-expression": "^3.3.0",
|
|
72
|
+
"@swaggerexpert/cookie": "^2.0.2",
|
|
72
73
|
"@swaggerexpert/json-pointer": "^3.0.2",
|
|
73
74
|
"@swaggerexpert/jsonpath": "^4.0.5",
|
|
74
75
|
"@swaggerexpert/media-types": "^1.0.1",
|
|
75
|
-
"@usearazzo/parser": "1.0.1-alpha.
|
|
76
|
-
"@usearazzo/resolver": "1.0.1-alpha.
|
|
76
|
+
"@usearazzo/parser": "1.0.1-alpha.13",
|
|
77
|
+
"@usearazzo/resolver": "1.0.1-alpha.13",
|
|
77
78
|
"@xmldom/xmldom": "^0.9.12",
|
|
78
79
|
"fontoxpath": "^3.34.0",
|
|
79
80
|
"ramda": "^0.32.0",
|
|
@@ -93,5 +94,5 @@
|
|
|
93
94
|
"devDependencies": {
|
|
94
95
|
"swagger-client": "^3.38.4"
|
|
95
96
|
},
|
|
96
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "41c018418a867e1469de0b2393929f7ec129c447"
|
|
97
98
|
}
|
|
@@ -17,6 +17,14 @@ exports.default = void 0;
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Response from an OpenAPI operation execution.
|
|
20
|
+
*
|
|
21
|
+
* `body` is the payload parsed by its content type, what `$response.body`
|
|
22
|
+
* reads. The body as received is read through `text()`, `blob()` and
|
|
23
|
+
* `bytes()`, named after the WHATWG `Response` methods but synchronous, and
|
|
24
|
+
* empty when the response carried no body (e.g. `204`, `304`). `bytes()` and
|
|
25
|
+
* `blob()` always return the bytes received. A body that is valid UTF-8 is held
|
|
26
|
+
* as its decoded string, which a plain-text or XML `body` shares rather than
|
|
27
|
+
* duplicates, and is encoded back on read; any other body is held as bytes.
|
|
20
28
|
* @public
|
|
21
29
|
*/
|
|
22
30
|
class OpenAPIOperationResponse {
|
|
@@ -25,18 +33,45 @@ class OpenAPIOperationResponse {
|
|
|
25
33
|
status;
|
|
26
34
|
statusText;
|
|
27
35
|
headers;
|
|
28
|
-
text;
|
|
29
36
|
body;
|
|
30
37
|
request;
|
|
38
|
+
#content;
|
|
31
39
|
constructor(raw) {
|
|
32
40
|
this.ok = raw.ok;
|
|
33
41
|
this.url = raw.url;
|
|
34
42
|
this.status = raw.status;
|
|
35
43
|
this.statusText = raw.statusText;
|
|
36
44
|
this.headers = raw.headers;
|
|
37
|
-
this.text = raw.text;
|
|
38
45
|
this.body = raw.body;
|
|
46
|
+
this.#content = raw.content;
|
|
39
47
|
this.request = raw.request;
|
|
40
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The body as text, like `Response.text()`; `''` when there is none.
|
|
52
|
+
*/
|
|
53
|
+
text() {
|
|
54
|
+
return typeof this.#content === 'string' ? this.#content : new TextDecoder().decode(this.#content);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The body as a `Blob` typed by the response's `Content-Type`, like
|
|
59
|
+
* `Response.blob()`.
|
|
60
|
+
*/
|
|
61
|
+
blob() {
|
|
62
|
+
// the lib types only take ArrayBuffer-backed views; the normalizer never
|
|
63
|
+
// produces a shared one.
|
|
64
|
+
return new Blob([this.#content], {
|
|
65
|
+
type: this.headers['content-type'] ?? ''
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The body as bytes, like `Response.bytes()`; a fresh copy on every call, so
|
|
71
|
+
* the response stays immutable.
|
|
72
|
+
*/
|
|
73
|
+
bytes() {
|
|
74
|
+
return typeof this.#content === 'string' ? new TextEncoder().encode(this.#content) : this.#content.slice();
|
|
75
|
+
}
|
|
41
76
|
}
|
|
42
77
|
var _default = exports.default = OpenAPIOperationResponse;
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Response from an OpenAPI operation execution.
|
|
14
|
+
*
|
|
15
|
+
* `body` is the payload parsed by its content type, what `$response.body`
|
|
16
|
+
* reads. The body as received is read through `text()`, `blob()` and
|
|
17
|
+
* `bytes()`, named after the WHATWG `Response` methods but synchronous, and
|
|
18
|
+
* empty when the response carried no body (e.g. `204`, `304`). `bytes()` and
|
|
19
|
+
* `blob()` always return the bytes received. A body that is valid UTF-8 is held
|
|
20
|
+
* as its decoded string, which a plain-text or XML `body` shares rather than
|
|
21
|
+
* duplicates, and is encoded back on read; any other body is held as bytes.
|
|
14
22
|
* @public
|
|
15
23
|
*/
|
|
16
24
|
class OpenAPIOperationResponse {
|
|
@@ -19,18 +27,45 @@ class OpenAPIOperationResponse {
|
|
|
19
27
|
status;
|
|
20
28
|
statusText;
|
|
21
29
|
headers;
|
|
22
|
-
text;
|
|
23
30
|
body;
|
|
24
31
|
request;
|
|
32
|
+
#content;
|
|
25
33
|
constructor(raw) {
|
|
26
34
|
this.ok = raw.ok;
|
|
27
35
|
this.url = raw.url;
|
|
28
36
|
this.status = raw.status;
|
|
29
37
|
this.statusText = raw.statusText;
|
|
30
38
|
this.headers = raw.headers;
|
|
31
|
-
this.text = raw.text;
|
|
32
39
|
this.body = raw.body;
|
|
40
|
+
this.#content = raw.content;
|
|
33
41
|
this.request = raw.request;
|
|
34
42
|
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The body as text, like `Response.text()`; `''` when there is none.
|
|
46
|
+
*/
|
|
47
|
+
text() {
|
|
48
|
+
return typeof this.#content === 'string' ? this.#content : new TextDecoder().decode(this.#content);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The body as a `Blob` typed by the response's `Content-Type`, like
|
|
53
|
+
* `Response.blob()`.
|
|
54
|
+
*/
|
|
55
|
+
blob() {
|
|
56
|
+
// the lib types only take ArrayBuffer-backed views; the normalizer never
|
|
57
|
+
// produces a shared one.
|
|
58
|
+
return new Blob([this.#content], {
|
|
59
|
+
type: this.headers['content-type'] ?? ''
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The body as bytes, like `Response.bytes()`; a fresh copy on every call, so
|
|
65
|
+
* the response stays immutable.
|
|
66
|
+
*/
|
|
67
|
+
bytes() {
|
|
68
|
+
return typeof this.#content === 'string' ? new TextEncoder().encode(this.#content) : this.#content.slice();
|
|
69
|
+
}
|
|
35
70
|
}
|
|
36
71
|
export default OpenAPIOperationResponse;
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.default = void 0;
|
|
8
|
+
var _mediaTypes = require("@swaggerexpert/media-types");
|
|
8
9
|
var _swaggerClient = require("../vendor/swagger-client.cjs");
|
|
9
10
|
var _OpenAPIOperationResponse = _interopRequireDefault(require("./OpenAPIOperationResponse.cjs"));
|
|
10
11
|
// @ts-expect-error vendored swagger-client bundle has no type declarations
|
|
@@ -20,7 +21,11 @@ var _OpenAPIOperationResponse = _interopRequireDefault(require("./OpenAPIOperati
|
|
|
20
21
|
* Headers are taken from the `Response` directly rather than from the
|
|
21
22
|
* deserializer, which splits every value containing `", "` into an array:
|
|
22
23
|
* values stay strings as sent, with repeated fields (`Set-Cookie` included)
|
|
23
|
-
* combined by `Headers.get`.
|
|
24
|
+
* combined by `Headers.get`. The body is read once, as bytes, and handed to the
|
|
25
|
+
* deserializer from memory. A body that decodes as UTF-8 without loss is kept
|
|
26
|
+
* as that one string, the same one the deserializer parses, so a plain-text or
|
|
27
|
+
* XML `body` shares it; any other body (binary, another charset, a leading
|
|
28
|
+
* byte-order mark) is kept as the bytes, so they are never re-encoded lossily.
|
|
24
29
|
* @public
|
|
25
30
|
*/
|
|
26
31
|
class OpenAPIOperationResponseNormalizer {
|
|
@@ -31,7 +36,30 @@ class OpenAPIOperationResponseNormalizer {
|
|
|
31
36
|
* carries none (e.g. a stubbed transport).
|
|
32
37
|
*/
|
|
33
38
|
async normalize(response, request) {
|
|
34
|
-
const
|
|
39
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
40
|
+
const text = this.#decodeLosslessly(bytes);
|
|
41
|
+
// the deserializer matches the raw Content-Type case-sensitively and against
|
|
42
|
+
// its parameters too, so it is handed only the parsed essence; the response's
|
|
43
|
+
// own `headers` below stay as sent.
|
|
44
|
+
const deserializerHeaders = new Headers(response.headers);
|
|
45
|
+
const contentType = response.headers.get('content-type');
|
|
46
|
+
const mediaType = contentType === null ? undefined : (0, _mediaTypes.parse)(contentType, {
|
|
47
|
+
type: 'media-type'
|
|
48
|
+
}).tree;
|
|
49
|
+
if (mediaType !== undefined) {
|
|
50
|
+
deserializerHeaders.set('content-type', `${mediaType.type}/${mediaType.subtype}`);
|
|
51
|
+
}
|
|
52
|
+
// the deserializer only reads the body through text() or blob(), by content
|
|
53
|
+
// type; answering from memory spares a second copy of the stream.
|
|
54
|
+
const deserialized = await (0, _swaggerClient.deserializeResponse)({
|
|
55
|
+
ok: response.ok,
|
|
56
|
+
url: response.url,
|
|
57
|
+
status: response.status,
|
|
58
|
+
statusText: response.statusText,
|
|
59
|
+
headers: deserializerHeaders,
|
|
60
|
+
text: async () => text ?? new TextDecoder().decode(bytes),
|
|
61
|
+
blob: async () => new Blob([bytes])
|
|
62
|
+
}, request?.url);
|
|
35
63
|
const headers = Object.fromEntries(Array.from(response.headers.keys(), name => [name, response.headers.get(name)]));
|
|
36
64
|
return new _OpenAPIOperationResponse.default({
|
|
37
65
|
ok: deserialized.ok,
|
|
@@ -39,10 +67,26 @@ class OpenAPIOperationResponseNormalizer {
|
|
|
39
67
|
status: deserialized.status,
|
|
40
68
|
statusText: deserialized.statusText,
|
|
41
69
|
headers,
|
|
42
|
-
text: deserialized.text,
|
|
43
70
|
body: deserialized.body,
|
|
71
|
+
content: text ?? bytes,
|
|
44
72
|
request
|
|
45
73
|
});
|
|
46
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The bytes as a string that encodes back to exactly them, or `undefined`
|
|
78
|
+
* when there is none: invalid UTF-8, or a leading byte-order mark, which
|
|
79
|
+
* decoding strips.
|
|
80
|
+
*/
|
|
81
|
+
#decodeLosslessly(bytes) {
|
|
82
|
+
if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) return undefined;
|
|
83
|
+
try {
|
|
84
|
+
return new TextDecoder('utf-8', {
|
|
85
|
+
fatal: true
|
|
86
|
+
}).decode(bytes);
|
|
87
|
+
} catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
47
91
|
}
|
|
48
92
|
var _default = exports.default = OpenAPIOperationResponseNormalizer;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { parse } from '@swaggerexpert/media-types';
|
|
2
|
+
|
|
1
3
|
// @ts-expect-error vendored swagger-client bundle has no type declarations
|
|
2
4
|
import { deserializeResponse } from '../vendor/swagger-client.mjs';
|
|
3
5
|
import OpenAPIOperationResponse from "./OpenAPIOperationResponse.mjs";
|
|
@@ -12,7 +14,11 @@ import OpenAPIOperationResponse from "./OpenAPIOperationResponse.mjs";
|
|
|
12
14
|
* Headers are taken from the `Response` directly rather than from the
|
|
13
15
|
* deserializer, which splits every value containing `", "` into an array:
|
|
14
16
|
* values stay strings as sent, with repeated fields (`Set-Cookie` included)
|
|
15
|
-
* combined by `Headers.get`.
|
|
17
|
+
* combined by `Headers.get`. The body is read once, as bytes, and handed to the
|
|
18
|
+
* deserializer from memory. A body that decodes as UTF-8 without loss is kept
|
|
19
|
+
* as that one string, the same one the deserializer parses, so a plain-text or
|
|
20
|
+
* XML `body` shares it; any other body (binary, another charset, a leading
|
|
21
|
+
* byte-order mark) is kept as the bytes, so they are never re-encoded lossily.
|
|
16
22
|
* @public
|
|
17
23
|
*/
|
|
18
24
|
class OpenAPIOperationResponseNormalizer {
|
|
@@ -23,7 +29,30 @@ class OpenAPIOperationResponseNormalizer {
|
|
|
23
29
|
* carries none (e.g. a stubbed transport).
|
|
24
30
|
*/
|
|
25
31
|
async normalize(response, request) {
|
|
26
|
-
const
|
|
32
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
33
|
+
const text = this.#decodeLosslessly(bytes);
|
|
34
|
+
// the deserializer matches the raw Content-Type case-sensitively and against
|
|
35
|
+
// its parameters too, so it is handed only the parsed essence; the response's
|
|
36
|
+
// own `headers` below stay as sent.
|
|
37
|
+
const deserializerHeaders = new Headers(response.headers);
|
|
38
|
+
const contentType = response.headers.get('content-type');
|
|
39
|
+
const mediaType = contentType === null ? undefined : parse(contentType, {
|
|
40
|
+
type: 'media-type'
|
|
41
|
+
}).tree;
|
|
42
|
+
if (mediaType !== undefined) {
|
|
43
|
+
deserializerHeaders.set('content-type', `${mediaType.type}/${mediaType.subtype}`);
|
|
44
|
+
}
|
|
45
|
+
// the deserializer only reads the body through text() or blob(), by content
|
|
46
|
+
// type; answering from memory spares a second copy of the stream.
|
|
47
|
+
const deserialized = await deserializeResponse({
|
|
48
|
+
ok: response.ok,
|
|
49
|
+
url: response.url,
|
|
50
|
+
status: response.status,
|
|
51
|
+
statusText: response.statusText,
|
|
52
|
+
headers: deserializerHeaders,
|
|
53
|
+
text: async () => text ?? new TextDecoder().decode(bytes),
|
|
54
|
+
blob: async () => new Blob([bytes])
|
|
55
|
+
}, request?.url);
|
|
27
56
|
const headers = Object.fromEntries(Array.from(response.headers.keys(), name => [name, response.headers.get(name)]));
|
|
28
57
|
return new OpenAPIOperationResponse({
|
|
29
58
|
ok: deserialized.ok,
|
|
@@ -31,10 +60,26 @@ class OpenAPIOperationResponseNormalizer {
|
|
|
31
60
|
status: deserialized.status,
|
|
32
61
|
statusText: deserialized.statusText,
|
|
33
62
|
headers,
|
|
34
|
-
text: deserialized.text,
|
|
35
63
|
body: deserialized.body,
|
|
64
|
+
content: text ?? bytes,
|
|
36
65
|
request
|
|
37
66
|
});
|
|
38
67
|
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The bytes as a string that encodes back to exactly them, or `undefined`
|
|
71
|
+
* when there is none: invalid UTF-8, or a leading byte-order mark, which
|
|
72
|
+
* decoding strips.
|
|
73
|
+
*/
|
|
74
|
+
#decodeLosslessly(bytes) {
|
|
75
|
+
if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) return undefined;
|
|
76
|
+
try {
|
|
77
|
+
return new TextDecoder('utf-8', {
|
|
78
|
+
fatal: true
|
|
79
|
+
}).decode(bytes);
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
39
84
|
}
|
|
40
85
|
export default OpenAPIOperationResponseNormalizer;
|
|
@@ -9,6 +9,7 @@ var _apidomCore = require("@speclynx/apidom-core");
|
|
|
9
9
|
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
10
10
|
var _empty = require("@speclynx/apidom-reference/configuration/empty");
|
|
11
11
|
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
12
|
+
var _cookie = require("@swaggerexpert/cookie");
|
|
12
13
|
var _mediaTypes = require("@swaggerexpert/media-types");
|
|
13
14
|
var _ramdaAdjunct = require("ramda-adjunct");
|
|
14
15
|
var _swaggerClient = require("../vendor/swagger-client.cjs");
|
|
@@ -30,6 +31,21 @@ var _ClientError = _interopRequireDefault(require("../errors/ClientError.cjs"));
|
|
|
30
31
|
* @public
|
|
31
32
|
*/
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The plain-JSON shape of the assembled document, as far as declaring a
|
|
36
|
+
* parameter on its one operation needs it.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A caller-supplied parameter the operation does not declare, in a location
|
|
41
|
+
* it can still be delivered to.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const UNDECLARED_LOCATIONS = new Set(['header', 'query', 'cookie', 'path']);
|
|
45
|
+
|
|
46
|
+
// the header parameters OpenAPI 3 says SHALL be ignored, which buildRequest drops
|
|
47
|
+
const IGNORED_HEADERS = new Set(['accept', 'authorization', 'content-type']);
|
|
48
|
+
|
|
33
49
|
/**
|
|
34
50
|
* Options for executing an OpenAPI operation.
|
|
35
51
|
*
|
|
@@ -148,9 +164,11 @@ class OpenAPIOperationExecutor {
|
|
|
148
164
|
const withServer = this.#applyServerOptions(rest, normalizedOperation, locator.document);
|
|
149
165
|
const withBody = this.#adaptRequestBody(withServer, normalizedOperation, locator.document, locator.jsonPointer);
|
|
150
166
|
const buildOptions = this.#adaptRequestContentType(withBody, normalizedOperation, locator.document, locator.jsonPointer);
|
|
167
|
+
const undeclared = this.#undeclaredParameters(buildOptions.parameters, normalizedOperation);
|
|
168
|
+
const spec = assembled.toJSON();
|
|
169
|
+
this.#declareQueryAndPathParameters(spec, undeclared, (0, _apidomCore.toValue)(operation.meta.get('path')), (0, _apidomCore.toValue)(operation.meta.get('http-method')), locator);
|
|
151
170
|
let request;
|
|
152
171
|
try {
|
|
153
|
-
const spec = assembled.toJSON();
|
|
154
172
|
request = (0, _swaggerClient.buildRequest)({
|
|
155
173
|
...buildOptions,
|
|
156
174
|
spec,
|
|
@@ -170,6 +188,7 @@ class OpenAPIOperationExecutor {
|
|
|
170
188
|
if (typeof authoredContentType === 'string' && buildOptions.requestContentType !== authoredContentType) {
|
|
171
189
|
request.headers['Content-Type'] = authoredContentType;
|
|
172
190
|
}
|
|
191
|
+
this.#deliverHeadersAndCookies(request, undeclared);
|
|
173
192
|
request = await this.#finalizeRequest(request, requestInterceptor);
|
|
174
193
|
|
|
175
194
|
// capture the request as sent, so `$url` / `$method` / `$request.*` can be
|
|
@@ -195,6 +214,130 @@ class OpenAPIOperationExecutor {
|
|
|
195
214
|
return this.#responseNormalizer.normalize(rawResponse, requestInfo);
|
|
196
215
|
}
|
|
197
216
|
|
|
217
|
+
/**
|
|
218
|
+
* The `{in}.{name}`-keyed parameters (see {@link ParameterDelivery}) in a
|
|
219
|
+
* header, query, cookie or path location that the operation does not
|
|
220
|
+
* declare. `buildRequest` delivers only what the operation declares, yet a
|
|
221
|
+
* step's parameters are what its author asked to send — an `Authorization`
|
|
222
|
+
* header carrying a token from an earlier step most of all, which OpenAPI 3
|
|
223
|
+
* cannot even declare. A declared `Accept`, `Authorization` or
|
|
224
|
+
* `Content-Type` header parameter counts as undeclared too: `buildRequest`
|
|
225
|
+
* drops it for OpenAPI 3, as that specification requires, and for 2.0
|
|
226
|
+
* delivering it here sends the same value. Bare keys are left to
|
|
227
|
+
* `buildRequest`: they name no location to deliver to.
|
|
228
|
+
*/
|
|
229
|
+
#undeclaredParameters(parameters, operation) {
|
|
230
|
+
if (!(0, _ramdaAdjunct.isPlainObject)(parameters)) return [];
|
|
231
|
+
const declared = new Set(((0, _apidomDatamodel.isArrayElement)(operation.parameters) ? [...operation.parameters] : []).filter(_apidomDatamodel.isObjectElement).map(parameter => `${(0, _apidomCore.toValue)(parameter.get('in'))}.${(0, _apidomCore.toValue)(parameter.get('name'))}`));
|
|
232
|
+
return Object.entries(parameters).flatMap(([key, value]) => {
|
|
233
|
+
const separator = key.indexOf('.');
|
|
234
|
+
const location = key.slice(0, separator);
|
|
235
|
+
const name = key.slice(separator + 1);
|
|
236
|
+
// an unresolved expression or a null delivers nothing, as buildRequest
|
|
237
|
+
// does for a declared parameter
|
|
238
|
+
if (separator === -1 || !UNDECLARED_LOCATIONS.has(location) || value === undefined || value === null) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const ignored = location === 'header' && IGNORED_HEADERS.has(name.toLowerCase());
|
|
242
|
+
return declared.has(key) && !ignored ? [] : [{
|
|
243
|
+
location,
|
|
244
|
+
name,
|
|
245
|
+
value
|
|
246
|
+
}];
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Declares the undeclared query and path parameters on the assembled
|
|
252
|
+
* operation, so `buildRequest` serializes and encodes them as it does
|
|
253
|
+
* declared ones. A path parameter is declared only when the path has its
|
|
254
|
+
* `{name}` template expression; otherwise there is nowhere to put it, and
|
|
255
|
+
* that authoring error throws {@link ClientError} rather than sending a
|
|
256
|
+
* request without it.
|
|
257
|
+
*/
|
|
258
|
+
#declareQueryAndPathParameters(spec, undeclared, path, method, locator) {
|
|
259
|
+
const operation = spec.paths[path][method];
|
|
260
|
+
const isSwagger = (0, _apidomNsOpenapi.isSwaggerElement)(locator.document.parseResult.api);
|
|
261
|
+
for (const {
|
|
262
|
+
location,
|
|
263
|
+
name
|
|
264
|
+
} of undeclared) {
|
|
265
|
+
if (location !== 'query' && location !== 'path') continue;
|
|
266
|
+
if (location === 'path' && !path.includes(`{${name}}`)) {
|
|
267
|
+
throw new _ClientError.default(`Path parameter "${name}" is not declared by the operation and its path "${path}" ` + `has no "{${name}}" to substitute it into`, {
|
|
268
|
+
operationPath: locator.jsonPointer
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
const required = location === 'path';
|
|
272
|
+
(operation.parameters ??= []).push(isSwagger ? {
|
|
273
|
+
name,
|
|
274
|
+
in: location,
|
|
275
|
+
required,
|
|
276
|
+
type: 'string'
|
|
277
|
+
} : {
|
|
278
|
+
name,
|
|
279
|
+
in: location,
|
|
280
|
+
required,
|
|
281
|
+
schema: {}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Sets the undeclared header parameters on the built request and appends the
|
|
288
|
+
* undeclared cookie parameters to its `Cookie` header. A header replaces any
|
|
289
|
+
* value the build gave it under any spelling — the default `Accept`, say —
|
|
290
|
+
* since the step naming it is the more specific intent. OpenAPI 2.0 has no
|
|
291
|
+
* cookie location, so its cookies always go this way.
|
|
292
|
+
*/
|
|
293
|
+
#deliverHeadersAndCookies(request, undeclared) {
|
|
294
|
+
const present = name => Object.keys(request.headers).find(header => header.toLowerCase() === name.toLowerCase());
|
|
295
|
+
for (const {
|
|
296
|
+
location,
|
|
297
|
+
name,
|
|
298
|
+
value
|
|
299
|
+
} of undeclared) {
|
|
300
|
+
if (location !== 'header') continue;
|
|
301
|
+
const built = present(name);
|
|
302
|
+
if (built !== undefined) delete request.headers[built];
|
|
303
|
+
request.headers[name] = this.#stringify(value);
|
|
304
|
+
}
|
|
305
|
+
const cookies = undeclared.filter(({
|
|
306
|
+
location
|
|
307
|
+
}) => location === 'cookie').map(({
|
|
308
|
+
name,
|
|
309
|
+
value
|
|
310
|
+
}) => [name, this.#stringify(value)]);
|
|
311
|
+
if (cookies.length === 0) return;
|
|
312
|
+
// serialized as buildRequest serializes a declared cookie, so a `;` in the
|
|
313
|
+
// value is percent-encoded rather than splitting it into further cookies
|
|
314
|
+
const serialized = (0, _cookie.serializeCookie)(cookies, {
|
|
315
|
+
encoders: {
|
|
316
|
+
value: _cookie.cookieValueStrictPercentEncoder
|
|
317
|
+
},
|
|
318
|
+
validators: {
|
|
319
|
+
name: _cookie.cookieNameLenientValidator
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
const cookieHeader = present('Cookie') ?? 'Cookie';
|
|
323
|
+
const existing = request.headers[cookieHeader];
|
|
324
|
+
request.headers[cookieHeader] = existing ? `${existing}; ${serialized}` : serialized;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* A header or cookie value with no schema to serialize it by, the way
|
|
329
|
+
* OpenAPI's default for those locations (`simple` and `form`, not exploded)
|
|
330
|
+
* serializes a declared one: an array as `a,b`, an object as `k,v,k2,v2`,
|
|
331
|
+
* anything else via `String()`.
|
|
332
|
+
*/
|
|
333
|
+
#stringify(value) {
|
|
334
|
+
if (Array.isArray(value)) return value.map(String).join(',');
|
|
335
|
+
if (typeof value === 'object' && value !== null) {
|
|
336
|
+
return Object.entries(value).flat().map(String).join(',');
|
|
337
|
+
}
|
|
338
|
+
return String(value);
|
|
339
|
+
}
|
|
340
|
+
|
|
198
341
|
/**
|
|
199
342
|
* Derives where the request is sent, returning the options to build with.
|
|
200
343
|
*
|