@usearazzo/runner 1.0.1-alpha.12 → 1.0.1-alpha.14
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 +12 -0
- package/dist/arazzo-runner.browser.js +1942 -35
- package/dist/arazzo-runner.browser.min.js +1 -1
- package/package.json +5 -4
- package/src/document/ParameterIdentity.cjs +16 -0
- package/src/document/ParameterIdentity.mjs +16 -0
- package/src/executor/OpenAPIOperationExecutor.cjs +144 -1
- package/src/executor/OpenAPIOperationExecutor.mjs +145 -1
- package/src/resolver/OpenAPIOperationParameterResolver.cjs +12 -1
- package/src/resolver/OpenAPIOperationParameterResolver.mjs +12 -1
- package/types/runner.d.ts +5 -1
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.14",
|
|
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.14",
|
|
77
|
+
"@usearazzo/resolver": "1.0.1-alpha.14",
|
|
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": "40a494596c0eed2f984fb87f544f3cf69488a6a7"
|
|
97
98
|
}
|
|
@@ -75,6 +75,14 @@ class ParameterIdentity {
|
|
|
75
75
|
if (location === undefined || (0, _apidomDatamodel.isNullElement)(location)) return undefined;
|
|
76
76
|
return (0, _apidomDatamodel.isStringElement)(location) ? (0, _apidomCore.toValue)(location) : null;
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The locations the Parameter Object defines: Arazzo 1.0.1's closed set plus
|
|
81
|
+
* 1.1.0's `querystring`. 1.0.0's field description also listed `body`, which
|
|
82
|
+
* its own summary of locations did not; 1.0.1 removed it as an erratum, so it
|
|
83
|
+
* is not a location in any version (a payload is the step's `requestBody`).
|
|
84
|
+
*/
|
|
85
|
+
static LOCATIONS = ['path', 'query', 'header', 'cookie', 'querystring'];
|
|
78
86
|
name;
|
|
79
87
|
location;
|
|
80
88
|
constructor(name, location) {
|
|
@@ -82,6 +90,14 @@ class ParameterIdentity {
|
|
|
82
90
|
this.location = location;
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Whether the location is one the Parameter Object defines. An absent or
|
|
95
|
+
* malformed location is not.
|
|
96
|
+
*/
|
|
97
|
+
get hasDefinedLocation() {
|
|
98
|
+
return ParameterIdentity.LOCATIONS.includes(this.location);
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
/**
|
|
86
102
|
* Whether this and the other identity name the same parameter. A malformed
|
|
87
103
|
* location equals nothing, its own duplicate included.
|
|
@@ -70,6 +70,14 @@ class ParameterIdentity {
|
|
|
70
70
|
if (location === undefined || isNullElement(location)) return undefined;
|
|
71
71
|
return isStringElement(location) ? toValue(location) : null;
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The locations the Parameter Object defines: Arazzo 1.0.1's closed set plus
|
|
76
|
+
* 1.1.0's `querystring`. 1.0.0's field description also listed `body`, which
|
|
77
|
+
* its own summary of locations did not; 1.0.1 removed it as an erratum, so it
|
|
78
|
+
* is not a location in any version (a payload is the step's `requestBody`).
|
|
79
|
+
*/
|
|
80
|
+
static LOCATIONS = ['path', 'query', 'header', 'cookie', 'querystring'];
|
|
73
81
|
name;
|
|
74
82
|
location;
|
|
75
83
|
constructor(name, location) {
|
|
@@ -77,6 +85,14 @@ class ParameterIdentity {
|
|
|
77
85
|
this.location = location;
|
|
78
86
|
}
|
|
79
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Whether the location is one the Parameter Object defines. An absent or
|
|
90
|
+
* malformed location is not.
|
|
91
|
+
*/
|
|
92
|
+
get hasDefinedLocation() {
|
|
93
|
+
return ParameterIdentity.LOCATIONS.includes(this.location);
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
/**
|
|
81
97
|
* Whether this and the other identity name the same parameter. A malformed
|
|
82
98
|
* location equals nothing, its own duplicate included.
|
|
@@ -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
|
*
|
|
@@ -2,6 +2,7 @@ import { toValue } from '@speclynx/apidom-core';
|
|
|
2
2
|
import { isArrayElement, isObjectElement } from '@speclynx/apidom-datamodel';
|
|
3
3
|
import { url as urlUtils } from '@speclynx/apidom-reference/configuration/empty';
|
|
4
4
|
import { isSwaggerElement, isParameterElement } from '@speclynx/apidom-ns-openapi-2';
|
|
5
|
+
import { serializeCookie, cookieNameLenientValidator, cookieValueStrictPercentEncoder } from '@swaggerexpert/cookie';
|
|
5
6
|
import { identify, matches, selectBestMatch } from '@swaggerexpert/media-types';
|
|
6
7
|
import { isPlainObject } from 'ramda-adjunct';
|
|
7
8
|
|
|
@@ -22,6 +23,19 @@ import ClientError from "../errors/ClientError.mjs";
|
|
|
22
23
|
* May be synchronous or asynchronous.
|
|
23
24
|
* @public
|
|
24
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* The plain-JSON shape of the assembled document, as far as declaring a
|
|
28
|
+
* parameter on its one operation needs it.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* A caller-supplied parameter the operation does not declare, in a location
|
|
32
|
+
* it can still be delivered to.
|
|
33
|
+
*/
|
|
34
|
+
const UNDECLARED_LOCATIONS = new Set(['header', 'query', 'cookie', 'path']);
|
|
35
|
+
|
|
36
|
+
// the header parameters OpenAPI 3 says SHALL be ignored, which buildRequest drops
|
|
37
|
+
const IGNORED_HEADERS = new Set(['accept', 'authorization', 'content-type']);
|
|
38
|
+
|
|
25
39
|
/**
|
|
26
40
|
* Options for executing an OpenAPI operation.
|
|
27
41
|
*
|
|
@@ -39,6 +53,7 @@ import ClientError from "../errors/ClientError.mjs";
|
|
|
39
53
|
* {@link RequestInterceptor}.
|
|
40
54
|
* @public
|
|
41
55
|
*/
|
|
56
|
+
|
|
42
57
|
/**
|
|
43
58
|
* The execute options forwarded to {@link OpenAPIOperationExecutor.execute}.
|
|
44
59
|
*
|
|
@@ -61,10 +76,12 @@ import ClientError from "../errors/ClientError.mjs";
|
|
|
61
76
|
* `responseNormalizer` — reach `buildRequest` but nothing acts on them.
|
|
62
77
|
* @public
|
|
63
78
|
*/
|
|
79
|
+
|
|
64
80
|
/**
|
|
65
81
|
* Options for the OpenAPIOperationExecutor.
|
|
66
82
|
* @public
|
|
67
83
|
*/
|
|
84
|
+
|
|
68
85
|
/**
|
|
69
86
|
* Executes a single OpenAPI operation named by a canonical
|
|
70
87
|
* {@link OpenAPIOperationLocator}.
|
|
@@ -137,9 +154,11 @@ class OpenAPIOperationExecutor {
|
|
|
137
154
|
const withServer = this.#applyServerOptions(rest, normalizedOperation, locator.document);
|
|
138
155
|
const withBody = this.#adaptRequestBody(withServer, normalizedOperation, locator.document, locator.jsonPointer);
|
|
139
156
|
const buildOptions = this.#adaptRequestContentType(withBody, normalizedOperation, locator.document, locator.jsonPointer);
|
|
157
|
+
const undeclared = this.#undeclaredParameters(buildOptions.parameters, normalizedOperation);
|
|
158
|
+
const spec = assembled.toJSON();
|
|
159
|
+
this.#declareQueryAndPathParameters(spec, undeclared, toValue(operation.meta.get('path')), toValue(operation.meta.get('http-method')), locator);
|
|
140
160
|
let request;
|
|
141
161
|
try {
|
|
142
|
-
const spec = assembled.toJSON();
|
|
143
162
|
request = buildRequest({
|
|
144
163
|
...buildOptions,
|
|
145
164
|
spec,
|
|
@@ -159,6 +178,7 @@ class OpenAPIOperationExecutor {
|
|
|
159
178
|
if (typeof authoredContentType === 'string' && buildOptions.requestContentType !== authoredContentType) {
|
|
160
179
|
request.headers['Content-Type'] = authoredContentType;
|
|
161
180
|
}
|
|
181
|
+
this.#deliverHeadersAndCookies(request, undeclared);
|
|
162
182
|
request = await this.#finalizeRequest(request, requestInterceptor);
|
|
163
183
|
|
|
164
184
|
// capture the request as sent, so `$url` / `$method` / `$request.*` can be
|
|
@@ -184,6 +204,130 @@ class OpenAPIOperationExecutor {
|
|
|
184
204
|
return this.#responseNormalizer.normalize(rawResponse, requestInfo);
|
|
185
205
|
}
|
|
186
206
|
|
|
207
|
+
/**
|
|
208
|
+
* The `{in}.{name}`-keyed parameters (see {@link ParameterDelivery}) in a
|
|
209
|
+
* header, query, cookie or path location that the operation does not
|
|
210
|
+
* declare. `buildRequest` delivers only what the operation declares, yet a
|
|
211
|
+
* step's parameters are what its author asked to send — an `Authorization`
|
|
212
|
+
* header carrying a token from an earlier step most of all, which OpenAPI 3
|
|
213
|
+
* cannot even declare. A declared `Accept`, `Authorization` or
|
|
214
|
+
* `Content-Type` header parameter counts as undeclared too: `buildRequest`
|
|
215
|
+
* drops it for OpenAPI 3, as that specification requires, and for 2.0
|
|
216
|
+
* delivering it here sends the same value. Bare keys are left to
|
|
217
|
+
* `buildRequest`: they name no location to deliver to.
|
|
218
|
+
*/
|
|
219
|
+
#undeclaredParameters(parameters, operation) {
|
|
220
|
+
if (!isPlainObject(parameters)) return [];
|
|
221
|
+
const declared = new Set((isArrayElement(operation.parameters) ? [...operation.parameters] : []).filter(isObjectElement).map(parameter => `${toValue(parameter.get('in'))}.${toValue(parameter.get('name'))}`));
|
|
222
|
+
return Object.entries(parameters).flatMap(([key, value]) => {
|
|
223
|
+
const separator = key.indexOf('.');
|
|
224
|
+
const location = key.slice(0, separator);
|
|
225
|
+
const name = key.slice(separator + 1);
|
|
226
|
+
// an unresolved expression or a null delivers nothing, as buildRequest
|
|
227
|
+
// does for a declared parameter
|
|
228
|
+
if (separator === -1 || !UNDECLARED_LOCATIONS.has(location) || value === undefined || value === null) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const ignored = location === 'header' && IGNORED_HEADERS.has(name.toLowerCase());
|
|
232
|
+
return declared.has(key) && !ignored ? [] : [{
|
|
233
|
+
location,
|
|
234
|
+
name,
|
|
235
|
+
value
|
|
236
|
+
}];
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Declares the undeclared query and path parameters on the assembled
|
|
242
|
+
* operation, so `buildRequest` serializes and encodes them as it does
|
|
243
|
+
* declared ones. A path parameter is declared only when the path has its
|
|
244
|
+
* `{name}` template expression; otherwise there is nowhere to put it, and
|
|
245
|
+
* that authoring error throws {@link ClientError} rather than sending a
|
|
246
|
+
* request without it.
|
|
247
|
+
*/
|
|
248
|
+
#declareQueryAndPathParameters(spec, undeclared, path, method, locator) {
|
|
249
|
+
const operation = spec.paths[path][method];
|
|
250
|
+
const isSwagger = isSwaggerElement(locator.document.parseResult.api);
|
|
251
|
+
for (const {
|
|
252
|
+
location,
|
|
253
|
+
name
|
|
254
|
+
} of undeclared) {
|
|
255
|
+
if (location !== 'query' && location !== 'path') continue;
|
|
256
|
+
if (location === 'path' && !path.includes(`{${name}}`)) {
|
|
257
|
+
throw new ClientError(`Path parameter "${name}" is not declared by the operation and its path "${path}" ` + `has no "{${name}}" to substitute it into`, {
|
|
258
|
+
operationPath: locator.jsonPointer
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const required = location === 'path';
|
|
262
|
+
(operation.parameters ??= []).push(isSwagger ? {
|
|
263
|
+
name,
|
|
264
|
+
in: location,
|
|
265
|
+
required,
|
|
266
|
+
type: 'string'
|
|
267
|
+
} : {
|
|
268
|
+
name,
|
|
269
|
+
in: location,
|
|
270
|
+
required,
|
|
271
|
+
schema: {}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Sets the undeclared header parameters on the built request and appends the
|
|
278
|
+
* undeclared cookie parameters to its `Cookie` header. A header replaces any
|
|
279
|
+
* value the build gave it under any spelling — the default `Accept`, say —
|
|
280
|
+
* since the step naming it is the more specific intent. OpenAPI 2.0 has no
|
|
281
|
+
* cookie location, so its cookies always go this way.
|
|
282
|
+
*/
|
|
283
|
+
#deliverHeadersAndCookies(request, undeclared) {
|
|
284
|
+
const present = name => Object.keys(request.headers).find(header => header.toLowerCase() === name.toLowerCase());
|
|
285
|
+
for (const {
|
|
286
|
+
location,
|
|
287
|
+
name,
|
|
288
|
+
value
|
|
289
|
+
} of undeclared) {
|
|
290
|
+
if (location !== 'header') continue;
|
|
291
|
+
const built = present(name);
|
|
292
|
+
if (built !== undefined) delete request.headers[built];
|
|
293
|
+
request.headers[name] = this.#stringify(value);
|
|
294
|
+
}
|
|
295
|
+
const cookies = undeclared.filter(({
|
|
296
|
+
location
|
|
297
|
+
}) => location === 'cookie').map(({
|
|
298
|
+
name,
|
|
299
|
+
value
|
|
300
|
+
}) => [name, this.#stringify(value)]);
|
|
301
|
+
if (cookies.length === 0) return;
|
|
302
|
+
// serialized as buildRequest serializes a declared cookie, so a `;` in the
|
|
303
|
+
// value is percent-encoded rather than splitting it into further cookies
|
|
304
|
+
const serialized = serializeCookie(cookies, {
|
|
305
|
+
encoders: {
|
|
306
|
+
value: cookieValueStrictPercentEncoder
|
|
307
|
+
},
|
|
308
|
+
validators: {
|
|
309
|
+
name: cookieNameLenientValidator
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
const cookieHeader = present('Cookie') ?? 'Cookie';
|
|
313
|
+
const existing = request.headers[cookieHeader];
|
|
314
|
+
request.headers[cookieHeader] = existing ? `${existing}; ${serialized}` : serialized;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* A header or cookie value with no schema to serialize it by, the way
|
|
319
|
+
* OpenAPI's default for those locations (`simple` and `form`, not exploded)
|
|
320
|
+
* serializes a declared one: an array as `a,b`, an object as `k,v,k2,v2`,
|
|
321
|
+
* anything else via `String()`.
|
|
322
|
+
*/
|
|
323
|
+
#stringify(value) {
|
|
324
|
+
if (Array.isArray(value)) return value.map(String).join(',');
|
|
325
|
+
if (typeof value === 'object' && value !== null) {
|
|
326
|
+
return Object.entries(value).flat().map(String).join(',');
|
|
327
|
+
}
|
|
328
|
+
return String(value);
|
|
329
|
+
}
|
|
330
|
+
|
|
187
331
|
/**
|
|
188
332
|
* Derives where the request is sent, returning the options to build with.
|
|
189
333
|
*
|
|
@@ -35,7 +35,11 @@ var _StepParameterResolver = _interopRequireDefault(require("./StepParameterReso
|
|
|
35
35
|
* instead would let it capture *every* declared location of that name — see
|
|
36
36
|
* `ParameterDelivery` for the lookup order that makes it so. A
|
|
37
37
|
* non-string location throws for the same reason it deduplicates with nothing
|
|
38
|
-
* in the normalizer: it names no location.
|
|
38
|
+
* in the normalizer: it names no location. So does a location outside
|
|
39
|
+
* `path`, `query`, `header`, `cookie` and `querystring`: Arazzo 1.0.1 removed
|
|
40
|
+
* `in: body`, and forwarding one would let it still deliver a request body
|
|
41
|
+
* against an OpenAPI 2.0 operation, bypassing the step's `requestBody`, while
|
|
42
|
+
* an unknown location reaches nothing at all. And two *different* parameters the
|
|
39
43
|
* map reports as colliding cannot both be delivered, so a collision throws
|
|
40
44
|
* rather than silently dropping one — whether they collide on one key (the
|
|
41
45
|
* scheme is not injective: a `querystring` parameter named `header.token` and
|
|
@@ -96,6 +100,13 @@ class OpenAPIOperationParameterResolver extends _StepParameterResolver.default {
|
|
|
96
100
|
reason: 'malformed-location'
|
|
97
101
|
});
|
|
98
102
|
}
|
|
103
|
+
if (!identity.hasDefinedLocation) {
|
|
104
|
+
throw new _ResolverError.default(`Parameter "${name}" has location "${location}", which is not one of ` + `${_ParameterIdentity.default.LOCATIONS.join(', ')}`, {
|
|
105
|
+
...scope,
|
|
106
|
+
target: name,
|
|
107
|
+
reason: 'invalid-location'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
99
110
|
const {
|
|
100
111
|
key,
|
|
101
112
|
outcome
|
|
@@ -28,7 +28,11 @@ import StepParameterResolver from "./StepParameterResolver.mjs";
|
|
|
28
28
|
* instead would let it capture *every* declared location of that name — see
|
|
29
29
|
* `ParameterDelivery` for the lookup order that makes it so. A
|
|
30
30
|
* non-string location throws for the same reason it deduplicates with nothing
|
|
31
|
-
* in the normalizer: it names no location.
|
|
31
|
+
* in the normalizer: it names no location. So does a location outside
|
|
32
|
+
* `path`, `query`, `header`, `cookie` and `querystring`: Arazzo 1.0.1 removed
|
|
33
|
+
* `in: body`, and forwarding one would let it still deliver a request body
|
|
34
|
+
* against an OpenAPI 2.0 operation, bypassing the step's `requestBody`, while
|
|
35
|
+
* an unknown location reaches nothing at all. And two *different* parameters the
|
|
32
36
|
* map reports as colliding cannot both be delivered, so a collision throws
|
|
33
37
|
* rather than silently dropping one — whether they collide on one key (the
|
|
34
38
|
* scheme is not injective: a `querystring` parameter named `header.token` and
|
|
@@ -89,6 +93,13 @@ class OpenAPIOperationParameterResolver extends StepParameterResolver {
|
|
|
89
93
|
reason: 'malformed-location'
|
|
90
94
|
});
|
|
91
95
|
}
|
|
96
|
+
if (!identity.hasDefinedLocation) {
|
|
97
|
+
throw new ResolverError(`Parameter "${name}" has location "${location}", which is not one of ` + `${ParameterIdentity.LOCATIONS.join(', ')}`, {
|
|
98
|
+
...scope,
|
|
99
|
+
target: name,
|
|
100
|
+
reason: 'invalid-location'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
92
103
|
const {
|
|
93
104
|
key,
|
|
94
105
|
outcome
|
package/types/runner.d.ts
CHANGED
|
@@ -1196,7 +1196,11 @@ export declare interface OpenAPIOperationNormalizerOptions {
|
|
|
1196
1196
|
* instead would let it capture *every* declared location of that name — see
|
|
1197
1197
|
* `ParameterDelivery` for the lookup order that makes it so. A
|
|
1198
1198
|
* non-string location throws for the same reason it deduplicates with nothing
|
|
1199
|
-
* in the normalizer: it names no location.
|
|
1199
|
+
* in the normalizer: it names no location. So does a location outside
|
|
1200
|
+
* `path`, `query`, `header`, `cookie` and `querystring`: Arazzo 1.0.1 removed
|
|
1201
|
+
* `in: body`, and forwarding one would let it still deliver a request body
|
|
1202
|
+
* against an OpenAPI 2.0 operation, bypassing the step's `requestBody`, while
|
|
1203
|
+
* an unknown location reaches nothing at all. And two *different* parameters the
|
|
1200
1204
|
* map reports as colliding cannot both be delivered, so a collision throws
|
|
1201
1205
|
* rather than silently dropping one — whether they collide on one key (the
|
|
1202
1206
|
* scheme is not injective: a `querystring` parameter named `header.token` and
|