@usearazzo/runner 1.0.1-alpha.22 → 1.0.1-alpha.23
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 +6 -0
- package/dist/arazzo-runner.browser.js +122 -78
- package/dist/arazzo-runner.browser.min.js +1 -1
- package/package.json +4 -4
- package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +79 -47
- package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +80 -48
- package/src/executor/StepExecutor.cjs +35 -25
- package/src/executor/StepExecutor.mjs +35 -25
- package/src/executor/WorkflowExecutor.cjs +4 -3
- package/src/executor/WorkflowExecutor.mjs +4 -3
- package/types/runner.d.ts +40 -15
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.23",
|
|
4
4
|
"description": "Runner for Arazzo Workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arazzo",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"@swaggerexpert/json-pointer": "^3.0.2",
|
|
74
74
|
"@swaggerexpert/jsonpath": "^4.0.5",
|
|
75
75
|
"@swaggerexpert/media-types": "^1.0.2",
|
|
76
|
-
"@usearazzo/parser": "1.0.1-alpha.
|
|
77
|
-
"@usearazzo/resolver": "1.0.1-alpha.
|
|
76
|
+
"@usearazzo/parser": "1.0.1-alpha.23",
|
|
77
|
+
"@usearazzo/resolver": "1.0.1-alpha.23",
|
|
78
78
|
"@xmldom/xmldom": "^0.9.12",
|
|
79
79
|
"fontoxpath": "^3.34.0",
|
|
80
80
|
"ramda": "^0.32.0",
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"swagger-client": "^3.38.4"
|
|
96
96
|
},
|
|
97
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "49de409b1fb851453c8c8c19cfdc12ec88545ca1"
|
|
98
98
|
}
|
|
@@ -10,7 +10,8 @@ var _jsonPointer = require("@swaggerexpert/json-pointer");
|
|
|
10
10
|
var _empty = require("@speclynx/apidom-reference/configuration/empty");
|
|
11
11
|
var _OpenAPIDocument = _interopRequireDefault(require("../document/OpenAPIDocument.cjs"));
|
|
12
12
|
var _RuntimeExpressionEvaluator = _interopRequireDefault(require("../expression/RuntimeExpressionEvaluator.cjs"));
|
|
13
|
-
var
|
|
13
|
+
var _OpenAPIOperationExtractor = _interopRequireDefault(require("../extractor/OpenAPIOperationExtractor.cjs"));
|
|
14
|
+
var _ExecutionError = _interopRequireDefault(require("../errors/ExecutionError.cjs"));
|
|
14
15
|
var _SourceDescriptionsLocatorNormalizer = _interopRequireDefault(require("./SourceDescriptionsLocatorNormalizer.cjs"));
|
|
15
16
|
/**
|
|
16
17
|
* A canonical OpenAPI operation locator: the OpenAPI document that owns the
|
|
@@ -34,7 +35,7 @@ var _SourceDescriptionsLocatorNormalizer = _interopRequireDefault(require("./Sou
|
|
|
34
35
|
* `$sourceDescriptions.{name}.{operationId}`. Per the Arazzo Specification, a
|
|
35
36
|
* plain name is only valid when a single (non-arazzo) source description is
|
|
36
37
|
* defined; multiple sources require the expression form. A plain name is
|
|
37
|
-
* therefore
|
|
38
|
+
* therefore looked up in the first OpenAPI source description only.
|
|
38
39
|
* - `operationPath` embeds a `{$sourceDescriptions.{name}.url}` runtime
|
|
39
40
|
* expression followed by a JSON Pointer to the operation.
|
|
40
41
|
*
|
|
@@ -42,21 +43,31 @@ var _SourceDescriptionsLocatorNormalizer = _interopRequireDefault(require("./Sou
|
|
|
42
43
|
* or wrong); a source is classified by what it actually parses to. The
|
|
43
44
|
* normalizer acquires the referenced source documents from the registry on
|
|
44
45
|
* demand.
|
|
46
|
+
*
|
|
47
|
+
* A reference that cannot be resolved throws {@link ExecutionError}, attributed
|
|
48
|
+
* to the referencing step when a {@link StepScope} is given:
|
|
49
|
+
* `operation-not-found` when no operation answers it,
|
|
50
|
+
* `source-description-not-found` for a source name the document does not
|
|
51
|
+
* define, `source-description-not-openapi` for a source that is not an OpenAPI
|
|
52
|
+
* document, and `invalid-operation-reference` for a reference malformed as
|
|
53
|
+
* written. Every returned locator names an existing operation.
|
|
45
54
|
* @public
|
|
46
55
|
*/
|
|
47
56
|
class OpenAPIOperationLocatorNormalizer extends _SourceDescriptionsLocatorNormalizer.default {
|
|
57
|
+
#extractor = new _OpenAPIOperationExtractor.default();
|
|
58
|
+
|
|
48
59
|
/**
|
|
49
60
|
* Normalizes a plain or expression `operationId` to a canonical locator.
|
|
50
61
|
*
|
|
51
62
|
* A whole runtime expression (`$sourceDescriptions.{name}.{operationId}`) is
|
|
52
|
-
* resolved against its named source; a plain name is
|
|
53
|
-
*
|
|
63
|
+
* resolved against its named source; a plain name is looked up in the first
|
|
64
|
+
* OpenAPI source description only.
|
|
54
65
|
*/
|
|
55
|
-
async normalizeOperationId(operationId, document) {
|
|
66
|
+
async normalizeOperationId(operationId, document, scope) {
|
|
56
67
|
if ((0, _arazzoRuntimeExpression.test)(operationId)) {
|
|
57
|
-
return this.#resolveSourced(operationId, document);
|
|
68
|
+
return this.#resolveSourced(operationId, document, scope);
|
|
58
69
|
}
|
|
59
|
-
return this.#resolvePlainOperationId(operationId, document);
|
|
70
|
+
return this.#resolvePlainOperationId(operationId, document, scope);
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
/**
|
|
@@ -72,10 +83,21 @@ class OpenAPIOperationLocatorNormalizer extends _SourceDescriptionsLocatorNormal
|
|
|
72
83
|
* The runner only needs a URL and a JSON Pointer, so it does not enforce that
|
|
73
84
|
* the source description is identified by a runtime expression rather than a
|
|
74
85
|
* literal URL (a literal URL interpolates to itself and still resolves) — that
|
|
75
|
-
* is a conformance concern for the validator.
|
|
76
|
-
* not
|
|
86
|
+
* is a conformance concern for the validator. A source name the document does
|
|
87
|
+
* not define is detected before interpolation, so it is reported as such
|
|
88
|
+
* rather than as the empty URL it would interpolate to; the JSON Pointer must
|
|
89
|
+
* locate an operation in the acquired OpenAPI document.
|
|
77
90
|
*/
|
|
78
|
-
async normalizeOperationPath(operationPath, document) {
|
|
91
|
+
async normalizeOperationPath(operationPath, document, scope) {
|
|
92
|
+
for (const expression of (0, _arazzoRuntimeExpression.extract)(operationPath) ?? []) {
|
|
93
|
+
const parsed = this.parseSourceDescriptionsExpression(expression);
|
|
94
|
+
if (parsed !== undefined && document.resolveSourceDescriptionURI(parsed.sourceName) === undefined) {
|
|
95
|
+
throw new _ExecutionError.default(`operationPath "${operationPath}" names source description "${parsed.sourceName}", which the Arazzo document at "${document.uri}" does not define`, {
|
|
96
|
+
...scope,
|
|
97
|
+
reason: 'source-description-not-found'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
79
101
|
const evaluator = new _RuntimeExpressionEvaluator.default({}, {
|
|
80
102
|
strict: false,
|
|
81
103
|
document,
|
|
@@ -84,23 +106,35 @@ class OpenAPIOperationLocatorNormalizer extends _SourceDescriptionsLocatorNormal
|
|
|
84
106
|
const uriReference = evaluator.interpolate(operationPath);
|
|
85
107
|
const documentURI = _empty.url.sanitize(_empty.url.stripHash(uriReference));
|
|
86
108
|
if (documentURI === '') {
|
|
87
|
-
throw new
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
throw new _ExecutionError.default(`operationPath "${operationPath}" does not resolve to a source description URL`, {
|
|
110
|
+
...scope,
|
|
111
|
+
reason: 'invalid-operation-reference'
|
|
90
112
|
});
|
|
91
113
|
}
|
|
92
114
|
const jsonPointer = _jsonPointer.URIFragmentIdentifier.fromURIReference(uriReference);
|
|
93
115
|
if (!(0, _jsonPointer.testJSONPointer)(jsonPointer)) {
|
|
94
|
-
throw new
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
throw new _ExecutionError.default(`operationPath "${operationPath}" has an invalid JSON Pointer`, {
|
|
117
|
+
...scope,
|
|
118
|
+
reason: 'invalid-operation-reference'
|
|
97
119
|
});
|
|
98
120
|
}
|
|
99
121
|
const source = await this.registry.acquire(documentURI);
|
|
100
122
|
if (!_OpenAPIDocument.default.is(source)) {
|
|
101
|
-
throw new
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
throw new _ExecutionError.default(`operationPath "${operationPath}" references "${documentURI}", which is not an OpenAPI document`, {
|
|
124
|
+
...scope,
|
|
125
|
+
reason: 'source-description-not-openapi'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// the extractor owns locating an operation by pointer; its failure here
|
|
130
|
+
// means the pointer names no operation in this document.
|
|
131
|
+
try {
|
|
132
|
+
this.#extractor.extractByPointer(source, jsonPointer);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new _ExecutionError.default(`operationPath "${operationPath}" does not locate an operation in the OpenAPI document at "${source.uri}"`, {
|
|
135
|
+
cause: error,
|
|
136
|
+
...scope,
|
|
137
|
+
reason: 'operation-not-found'
|
|
104
138
|
});
|
|
105
139
|
}
|
|
106
140
|
return {
|
|
@@ -108,31 +142,24 @@ class OpenAPIOperationLocatorNormalizer extends _SourceDescriptionsLocatorNormal
|
|
|
108
142
|
jsonPointer
|
|
109
143
|
};
|
|
110
144
|
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Parses a `$sourceDescriptions.{name}.{reference}` expression, asserting
|
|
114
|
-
* it is a source-descriptions expression.
|
|
115
|
-
*/
|
|
116
|
-
#parseSourceDescriptionsExpression(expression) {
|
|
145
|
+
async #resolveSourced(expression, document, scope) {
|
|
117
146
|
const parsed = this.parseSourceDescriptionsExpression(expression);
|
|
118
147
|
if (parsed === undefined) {
|
|
119
|
-
throw new
|
|
120
|
-
|
|
148
|
+
throw new _ExecutionError.default(`operationId "${expression}" is not a $sourceDescriptions.{name}.{operationId} expression`, {
|
|
149
|
+
...scope,
|
|
150
|
+
reason: 'invalid-operation-reference'
|
|
121
151
|
});
|
|
122
152
|
}
|
|
123
|
-
return parsed;
|
|
124
|
-
}
|
|
125
|
-
async #resolveSourced(expression, document) {
|
|
126
153
|
const {
|
|
127
154
|
sourceName,
|
|
128
155
|
reference: operationId
|
|
129
|
-
} =
|
|
130
|
-
const openapiDocument = await this.#sourceDocument(sourceName, document);
|
|
156
|
+
} = parsed;
|
|
157
|
+
const openapiDocument = await this.#sourceDocument(sourceName, document, scope);
|
|
131
158
|
const jsonPointer = openapiDocument.operationIndex.get(operationId);
|
|
132
159
|
if (jsonPointer === undefined) {
|
|
133
|
-
throw new
|
|
134
|
-
|
|
135
|
-
|
|
160
|
+
throw new _ExecutionError.default(`Operation "${operationId}" not found in source description "${sourceName}" at "${openapiDocument.uri}"`, {
|
|
161
|
+
...scope,
|
|
162
|
+
reason: 'operation-not-found'
|
|
136
163
|
});
|
|
137
164
|
}
|
|
138
165
|
return {
|
|
@@ -153,15 +180,15 @@ class OpenAPIOperationLocatorNormalizer extends _SourceDescriptionsLocatorNormal
|
|
|
153
180
|
* Non-OpenAPI (arazzo) sources are skipped. Sources are acquired one at a time
|
|
154
181
|
* until the first OpenAPI source is reached.
|
|
155
182
|
*/
|
|
156
|
-
async #resolvePlainOperationId(operationId, document) {
|
|
183
|
+
async #resolvePlainOperationId(operationId, document, scope) {
|
|
157
184
|
for (const uri of document.sourceDescriptionURIs()) {
|
|
158
185
|
const source = await this.registry.acquire(uri);
|
|
159
186
|
if (!_OpenAPIDocument.default.is(source)) continue;
|
|
160
187
|
const jsonPointer = source.operationIndex.get(operationId);
|
|
161
188
|
if (jsonPointer === undefined) {
|
|
162
|
-
throw new
|
|
163
|
-
|
|
164
|
-
|
|
189
|
+
throw new _ExecutionError.default(`Operation "${operationId}" not found in source description at "${source.uri}"`, {
|
|
190
|
+
...scope,
|
|
191
|
+
reason: 'operation-not-found'
|
|
165
192
|
});
|
|
166
193
|
}
|
|
167
194
|
return {
|
|
@@ -169,26 +196,31 @@ class OpenAPIOperationLocatorNormalizer extends _SourceDescriptionsLocatorNormal
|
|
|
169
196
|
jsonPointer
|
|
170
197
|
};
|
|
171
198
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
199
|
+
|
|
200
|
+
// no source was named, so this is not a missing source description: there
|
|
201
|
+
// is simply no OpenAPI document for the operation to be in.
|
|
202
|
+
throw new _ExecutionError.default(`Operation "${operationId}" not found: the Arazzo document at "${document.uri}" has no OpenAPI source description`, {
|
|
203
|
+
...scope,
|
|
204
|
+
reason: 'operation-not-found'
|
|
175
205
|
});
|
|
176
206
|
}
|
|
177
207
|
|
|
178
208
|
/**
|
|
179
209
|
* Acquires a source description by name and asserts it is an OpenAPI document.
|
|
180
210
|
*/
|
|
181
|
-
async #sourceDocument(sourceName, document) {
|
|
211
|
+
async #sourceDocument(sourceName, document, scope) {
|
|
182
212
|
const uri = document.resolveSourceDescriptionURI(sourceName);
|
|
183
213
|
if (uri === undefined) {
|
|
184
|
-
throw new
|
|
185
|
-
|
|
214
|
+
throw new _ExecutionError.default(`Source description "${sourceName}" not found in the Arazzo document at "${document.uri}"`, {
|
|
215
|
+
...scope,
|
|
216
|
+
reason: 'source-description-not-found'
|
|
186
217
|
});
|
|
187
218
|
}
|
|
188
219
|
const source = await this.registry.acquire(uri);
|
|
189
220
|
if (!_OpenAPIDocument.default.is(source)) {
|
|
190
|
-
throw new
|
|
191
|
-
|
|
221
|
+
throw new _ExecutionError.default(`Source description "${sourceName}" at "${uri}" is not an OpenAPI document`, {
|
|
222
|
+
...scope,
|
|
223
|
+
reason: 'source-description-not-openapi'
|
|
192
224
|
});
|
|
193
225
|
}
|
|
194
226
|
return source;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { test as isRuntimeExpression } from '@swaggerexpert/arazzo-runtime-expression';
|
|
1
|
+
import { extract, test as isRuntimeExpression } from '@swaggerexpert/arazzo-runtime-expression';
|
|
2
2
|
import { URIFragmentIdentifier, testJSONPointer } from '@swaggerexpert/json-pointer';
|
|
3
3
|
import { url } from '@speclynx/apidom-reference/configuration/empty';
|
|
4
4
|
import OpenAPIDocument from "../document/OpenAPIDocument.mjs";
|
|
5
5
|
import RuntimeExpressionEvaluator from "../expression/RuntimeExpressionEvaluator.mjs";
|
|
6
|
-
import
|
|
6
|
+
import OpenAPIOperationExtractor from "../extractor/OpenAPIOperationExtractor.mjs";
|
|
7
|
+
import ExecutionError from "../errors/ExecutionError.mjs";
|
|
7
8
|
import SourceDescriptionsLocatorNormalizer from "./SourceDescriptionsLocatorNormalizer.mjs";
|
|
8
9
|
/**
|
|
9
10
|
* A canonical OpenAPI operation locator: the OpenAPI document that owns the
|
|
@@ -26,7 +27,7 @@ import SourceDescriptionsLocatorNormalizer from "./SourceDescriptionsLocatorNorm
|
|
|
26
27
|
* `$sourceDescriptions.{name}.{operationId}`. Per the Arazzo Specification, a
|
|
27
28
|
* plain name is only valid when a single (non-arazzo) source description is
|
|
28
29
|
* defined; multiple sources require the expression form. A plain name is
|
|
29
|
-
* therefore
|
|
30
|
+
* therefore looked up in the first OpenAPI source description only.
|
|
30
31
|
* - `operationPath` embeds a `{$sourceDescriptions.{name}.url}` runtime
|
|
31
32
|
* expression followed by a JSON Pointer to the operation.
|
|
32
33
|
*
|
|
@@ -34,21 +35,31 @@ import SourceDescriptionsLocatorNormalizer from "./SourceDescriptionsLocatorNorm
|
|
|
34
35
|
* or wrong); a source is classified by what it actually parses to. The
|
|
35
36
|
* normalizer acquires the referenced source documents from the registry on
|
|
36
37
|
* demand.
|
|
38
|
+
*
|
|
39
|
+
* A reference that cannot be resolved throws {@link ExecutionError}, attributed
|
|
40
|
+
* to the referencing step when a {@link StepScope} is given:
|
|
41
|
+
* `operation-not-found` when no operation answers it,
|
|
42
|
+
* `source-description-not-found` for a source name the document does not
|
|
43
|
+
* define, `source-description-not-openapi` for a source that is not an OpenAPI
|
|
44
|
+
* document, and `invalid-operation-reference` for a reference malformed as
|
|
45
|
+
* written. Every returned locator names an existing operation.
|
|
37
46
|
* @public
|
|
38
47
|
*/
|
|
39
48
|
class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormalizer {
|
|
49
|
+
#extractor = new OpenAPIOperationExtractor();
|
|
50
|
+
|
|
40
51
|
/**
|
|
41
52
|
* Normalizes a plain or expression `operationId` to a canonical locator.
|
|
42
53
|
*
|
|
43
54
|
* A whole runtime expression (`$sourceDescriptions.{name}.{operationId}`) is
|
|
44
|
-
* resolved against its named source; a plain name is
|
|
45
|
-
*
|
|
55
|
+
* resolved against its named source; a plain name is looked up in the first
|
|
56
|
+
* OpenAPI source description only.
|
|
46
57
|
*/
|
|
47
|
-
async normalizeOperationId(operationId, document) {
|
|
58
|
+
async normalizeOperationId(operationId, document, scope) {
|
|
48
59
|
if (isRuntimeExpression(operationId)) {
|
|
49
|
-
return this.#resolveSourced(operationId, document);
|
|
60
|
+
return this.#resolveSourced(operationId, document, scope);
|
|
50
61
|
}
|
|
51
|
-
return this.#resolvePlainOperationId(operationId, document);
|
|
62
|
+
return this.#resolvePlainOperationId(operationId, document, scope);
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
/**
|
|
@@ -64,10 +75,21 @@ class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormali
|
|
|
64
75
|
* The runner only needs a URL and a JSON Pointer, so it does not enforce that
|
|
65
76
|
* the source description is identified by a runtime expression rather than a
|
|
66
77
|
* literal URL (a literal URL interpolates to itself and still resolves) — that
|
|
67
|
-
* is a conformance concern for the validator.
|
|
68
|
-
* not
|
|
78
|
+
* is a conformance concern for the validator. A source name the document does
|
|
79
|
+
* not define is detected before interpolation, so it is reported as such
|
|
80
|
+
* rather than as the empty URL it would interpolate to; the JSON Pointer must
|
|
81
|
+
* locate an operation in the acquired OpenAPI document.
|
|
69
82
|
*/
|
|
70
|
-
async normalizeOperationPath(operationPath, document) {
|
|
83
|
+
async normalizeOperationPath(operationPath, document, scope) {
|
|
84
|
+
for (const expression of extract(operationPath) ?? []) {
|
|
85
|
+
const parsed = this.parseSourceDescriptionsExpression(expression);
|
|
86
|
+
if (parsed !== undefined && document.resolveSourceDescriptionURI(parsed.sourceName) === undefined) {
|
|
87
|
+
throw new ExecutionError(`operationPath "${operationPath}" names source description "${parsed.sourceName}", which the Arazzo document at "${document.uri}" does not define`, {
|
|
88
|
+
...scope,
|
|
89
|
+
reason: 'source-description-not-found'
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
71
93
|
const evaluator = new RuntimeExpressionEvaluator({}, {
|
|
72
94
|
strict: false,
|
|
73
95
|
document,
|
|
@@ -76,23 +98,35 @@ class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormali
|
|
|
76
98
|
const uriReference = evaluator.interpolate(operationPath);
|
|
77
99
|
const documentURI = url.sanitize(url.stripHash(uriReference));
|
|
78
100
|
if (documentURI === '') {
|
|
79
|
-
throw new
|
|
80
|
-
|
|
81
|
-
|
|
101
|
+
throw new ExecutionError(`operationPath "${operationPath}" does not resolve to a source description URL`, {
|
|
102
|
+
...scope,
|
|
103
|
+
reason: 'invalid-operation-reference'
|
|
82
104
|
});
|
|
83
105
|
}
|
|
84
106
|
const jsonPointer = URIFragmentIdentifier.fromURIReference(uriReference);
|
|
85
107
|
if (!testJSONPointer(jsonPointer)) {
|
|
86
|
-
throw new
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
throw new ExecutionError(`operationPath "${operationPath}" has an invalid JSON Pointer`, {
|
|
109
|
+
...scope,
|
|
110
|
+
reason: 'invalid-operation-reference'
|
|
89
111
|
});
|
|
90
112
|
}
|
|
91
113
|
const source = await this.registry.acquire(documentURI);
|
|
92
114
|
if (!OpenAPIDocument.is(source)) {
|
|
93
|
-
throw new
|
|
94
|
-
|
|
95
|
-
|
|
115
|
+
throw new ExecutionError(`operationPath "${operationPath}" references "${documentURI}", which is not an OpenAPI document`, {
|
|
116
|
+
...scope,
|
|
117
|
+
reason: 'source-description-not-openapi'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// the extractor owns locating an operation by pointer; its failure here
|
|
122
|
+
// means the pointer names no operation in this document.
|
|
123
|
+
try {
|
|
124
|
+
this.#extractor.extractByPointer(source, jsonPointer);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
throw new ExecutionError(`operationPath "${operationPath}" does not locate an operation in the OpenAPI document at "${source.uri}"`, {
|
|
127
|
+
cause: error,
|
|
128
|
+
...scope,
|
|
129
|
+
reason: 'operation-not-found'
|
|
96
130
|
});
|
|
97
131
|
}
|
|
98
132
|
return {
|
|
@@ -100,31 +134,24 @@ class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormali
|
|
|
100
134
|
jsonPointer
|
|
101
135
|
};
|
|
102
136
|
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Parses a `$sourceDescriptions.{name}.{reference}` expression, asserting
|
|
106
|
-
* it is a source-descriptions expression.
|
|
107
|
-
*/
|
|
108
|
-
#parseSourceDescriptionsExpression(expression) {
|
|
137
|
+
async #resolveSourced(expression, document, scope) {
|
|
109
138
|
const parsed = this.parseSourceDescriptionsExpression(expression);
|
|
110
139
|
if (parsed === undefined) {
|
|
111
|
-
throw new
|
|
112
|
-
|
|
140
|
+
throw new ExecutionError(`operationId "${expression}" is not a $sourceDescriptions.{name}.{operationId} expression`, {
|
|
141
|
+
...scope,
|
|
142
|
+
reason: 'invalid-operation-reference'
|
|
113
143
|
});
|
|
114
144
|
}
|
|
115
|
-
return parsed;
|
|
116
|
-
}
|
|
117
|
-
async #resolveSourced(expression, document) {
|
|
118
145
|
const {
|
|
119
146
|
sourceName,
|
|
120
147
|
reference: operationId
|
|
121
|
-
} =
|
|
122
|
-
const openapiDocument = await this.#sourceDocument(sourceName, document);
|
|
148
|
+
} = parsed;
|
|
149
|
+
const openapiDocument = await this.#sourceDocument(sourceName, document, scope);
|
|
123
150
|
const jsonPointer = openapiDocument.operationIndex.get(operationId);
|
|
124
151
|
if (jsonPointer === undefined) {
|
|
125
|
-
throw new
|
|
126
|
-
|
|
127
|
-
|
|
152
|
+
throw new ExecutionError(`Operation "${operationId}" not found in source description "${sourceName}" at "${openapiDocument.uri}"`, {
|
|
153
|
+
...scope,
|
|
154
|
+
reason: 'operation-not-found'
|
|
128
155
|
});
|
|
129
156
|
}
|
|
130
157
|
return {
|
|
@@ -145,15 +172,15 @@ class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormali
|
|
|
145
172
|
* Non-OpenAPI (arazzo) sources are skipped. Sources are acquired one at a time
|
|
146
173
|
* until the first OpenAPI source is reached.
|
|
147
174
|
*/
|
|
148
|
-
async #resolvePlainOperationId(operationId, document) {
|
|
175
|
+
async #resolvePlainOperationId(operationId, document, scope) {
|
|
149
176
|
for (const uri of document.sourceDescriptionURIs()) {
|
|
150
177
|
const source = await this.registry.acquire(uri);
|
|
151
178
|
if (!OpenAPIDocument.is(source)) continue;
|
|
152
179
|
const jsonPointer = source.operationIndex.get(operationId);
|
|
153
180
|
if (jsonPointer === undefined) {
|
|
154
|
-
throw new
|
|
155
|
-
|
|
156
|
-
|
|
181
|
+
throw new ExecutionError(`Operation "${operationId}" not found in source description at "${source.uri}"`, {
|
|
182
|
+
...scope,
|
|
183
|
+
reason: 'operation-not-found'
|
|
157
184
|
});
|
|
158
185
|
}
|
|
159
186
|
return {
|
|
@@ -161,26 +188,31 @@ class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormali
|
|
|
161
188
|
jsonPointer
|
|
162
189
|
};
|
|
163
190
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
|
|
192
|
+
// no source was named, so this is not a missing source description: there
|
|
193
|
+
// is simply no OpenAPI document for the operation to be in.
|
|
194
|
+
throw new ExecutionError(`Operation "${operationId}" not found: the Arazzo document at "${document.uri}" has no OpenAPI source description`, {
|
|
195
|
+
...scope,
|
|
196
|
+
reason: 'operation-not-found'
|
|
167
197
|
});
|
|
168
198
|
}
|
|
169
199
|
|
|
170
200
|
/**
|
|
171
201
|
* Acquires a source description by name and asserts it is an OpenAPI document.
|
|
172
202
|
*/
|
|
173
|
-
async #sourceDocument(sourceName, document) {
|
|
203
|
+
async #sourceDocument(sourceName, document, scope) {
|
|
174
204
|
const uri = document.resolveSourceDescriptionURI(sourceName);
|
|
175
205
|
if (uri === undefined) {
|
|
176
|
-
throw new
|
|
177
|
-
|
|
206
|
+
throw new ExecutionError(`Source description "${sourceName}" not found in the Arazzo document at "${document.uri}"`, {
|
|
207
|
+
...scope,
|
|
208
|
+
reason: 'source-description-not-found'
|
|
178
209
|
});
|
|
179
210
|
}
|
|
180
211
|
const source = await this.registry.acquire(uri);
|
|
181
212
|
if (!OpenAPIDocument.is(source)) {
|
|
182
|
-
throw new
|
|
183
|
-
|
|
213
|
+
throw new ExecutionError(`Source description "${sourceName}" at "${uri}" is not an OpenAPI document`, {
|
|
214
|
+
...scope,
|
|
215
|
+
reason: 'source-description-not-openapi'
|
|
184
216
|
});
|
|
185
217
|
}
|
|
186
218
|
return source;
|
|
@@ -134,38 +134,45 @@ class StepExecutor {
|
|
|
134
134
|
* request nobody is waiting for, whose wire-level cancellation would surface
|
|
135
135
|
* as a transport {@link ClientError} — a request that failed on its own terms
|
|
136
136
|
* — rather than as the withdrawal it is.
|
|
137
|
+
*
|
|
138
|
+
* `workflowId` names the workflow the step belongs to; every error raised
|
|
139
|
+
* here then carries it alongside the `stepId`, since step ids are only
|
|
140
|
+
* unique within a workflow. The workflow executor always passes it.
|
|
137
141
|
*/
|
|
138
|
-
async execute(step, state, executeOptions = {}) {
|
|
142
|
+
async execute(step, state, executeOptions = {}, workflowId) {
|
|
139
143
|
const stepId = (0, _apidomCore.toValue)(step.stepId);
|
|
144
|
+
const scope = {
|
|
145
|
+
...(workflowId === undefined ? {} : {
|
|
146
|
+
workflowId
|
|
147
|
+
}),
|
|
148
|
+
stepId
|
|
149
|
+
};
|
|
140
150
|
|
|
141
151
|
// a step's target fields are mutually exclusive; declaring more than one is
|
|
142
152
|
// malformed and has no defined resolution.
|
|
143
153
|
const declaredTargets = STEP_TARGET_FIELDS.filter(field => (0, _apidomDatamodel.isStringElement)(step[field]));
|
|
144
154
|
if (declaredTargets.length > 1) {
|
|
145
155
|
throw new _ExecutionError.default(`Step "${stepId}" declares more than one of ${STEP_TARGET_FIELDS.join(', ')} (mutually exclusive)`, {
|
|
146
|
-
|
|
156
|
+
...scope,
|
|
147
157
|
reason: 'ambiguous-target'
|
|
148
158
|
});
|
|
149
159
|
}
|
|
150
160
|
if ((0, _apidomDatamodel.isStringElement)(step.workflowId)) {
|
|
151
161
|
throw new _ExecutionError.default(`Step "${stepId}" targets a workflowId; use the workflow executor`, {
|
|
152
|
-
|
|
162
|
+
...scope,
|
|
153
163
|
reason: 'workflow-step'
|
|
154
164
|
});
|
|
155
165
|
}
|
|
156
|
-
this.assertSuccessCriteria(step);
|
|
166
|
+
this.assertSuccessCriteria(step, workflowId);
|
|
157
167
|
|
|
158
168
|
// locate which operation the step invokes — reduced to a (document, JSON
|
|
159
169
|
// Pointer) locator that the operation executor extracts, normalizes,
|
|
160
170
|
// assembles into a standalone doc, and runs.
|
|
161
|
-
const locator = await this.#locateOperation(step,
|
|
171
|
+
const locator = await this.#locateOperation(step, scope);
|
|
162
172
|
|
|
163
173
|
// resolve parameters and request body against the pre-request context.
|
|
164
174
|
// keyed by '{in}.{name}' rather than bare name, so two parameters that
|
|
165
175
|
// legally differ only in their location each reach their own place.
|
|
166
|
-
const scope = {
|
|
167
|
-
stepId
|
|
168
|
-
};
|
|
169
176
|
const preContext = state.toContext();
|
|
170
177
|
const parameters = this.#parameterResolver.resolve(step.parameters, expression => this.#evaluate(preContext, expression), scope);
|
|
171
178
|
const requestBody = this.#requestBodyResolver.resolve(step.requestBody, expression => this.#evaluate(preContext, expression), scope);
|
|
@@ -176,9 +183,7 @@ class StepExecutor {
|
|
|
176
183
|
// live side effect. The signal stays in the bag, so a request already in
|
|
177
184
|
// flight is aborted by the transport as before.
|
|
178
185
|
const signal = (0, _abort.readAbortSignal)(executeOptions);
|
|
179
|
-
(0, _abort.throwIfAborted)(signal,
|
|
180
|
-
stepId
|
|
181
|
-
});
|
|
186
|
+
(0, _abort.throwIfAborted)(signal, scope);
|
|
182
187
|
|
|
183
188
|
// the Arazzo-derived parameters and request body are spread after the opaque
|
|
184
189
|
// executeOptions bag so they take precedence over it. the operation executor
|
|
@@ -197,22 +202,20 @@ class StepExecutor {
|
|
|
197
202
|
// cancelled, it did not: reporting it as the abort keeps one error shape
|
|
198
203
|
// whether the run was withdrawn between two steps or mid-flight, and keeps
|
|
199
204
|
// the whole feature from reading differently per transport.
|
|
200
|
-
(0, _abort.throwIfAborted)(signal,
|
|
201
|
-
stepId
|
|
202
|
-
});
|
|
205
|
+
(0, _abort.throwIfAborted)(signal, scope);
|
|
203
206
|
throw error;
|
|
204
207
|
}
|
|
205
208
|
|
|
206
209
|
// evaluate criteria, outputs, and the next action against the post-request
|
|
207
210
|
// context (with $request / $url / $method and $response / $statusCode).
|
|
208
211
|
const postContext = state.toContext(this.#requestContext(response), this.#responseContext(response));
|
|
209
|
-
const successCriteria = this.evaluateCriteria(step, postContext);
|
|
212
|
+
const successCriteria = this.evaluateCriteria(step, postContext, workflowId);
|
|
210
213
|
const successful = successCriteria.every(criterion => criterion.met);
|
|
211
214
|
const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(postContext, expression), scope);
|
|
212
215
|
const {
|
|
213
216
|
matchedActions,
|
|
214
217
|
actionCriteria
|
|
215
|
-
} = this.selectActions(step, successful, postContext);
|
|
218
|
+
} = this.selectActions(step, successful, postContext, workflowId);
|
|
216
219
|
return {
|
|
217
220
|
stepId,
|
|
218
221
|
response,
|
|
@@ -242,15 +245,15 @@ class StepExecutor {
|
|
|
242
245
|
requestContentType: requestBody.contentType
|
|
243
246
|
};
|
|
244
247
|
}
|
|
245
|
-
async #locateOperation(step,
|
|
248
|
+
async #locateOperation(step, scope) {
|
|
246
249
|
if ((0, _apidomDatamodel.isStringElement)(step.operationId)) {
|
|
247
|
-
return this.#locatorNormalizer.normalizeOperationId((0, _apidomCore.toValue)(step.operationId), this.#document);
|
|
250
|
+
return this.#locatorNormalizer.normalizeOperationId((0, _apidomCore.toValue)(step.operationId), this.#document, scope);
|
|
248
251
|
}
|
|
249
252
|
if ((0, _apidomDatamodel.isStringElement)(step.operationPath)) {
|
|
250
|
-
return this.#locatorNormalizer.normalizeOperationPath((0, _apidomCore.toValue)(step.operationPath), this.#document);
|
|
253
|
+
return this.#locatorNormalizer.normalizeOperationPath((0, _apidomCore.toValue)(step.operationPath), this.#document, scope);
|
|
251
254
|
}
|
|
252
|
-
throw new _ExecutionError.default(`Step "${stepId}" has no operationId or operationPath`, {
|
|
253
|
-
|
|
255
|
+
throw new _ExecutionError.default(`Step "${scope.stepId}" has no operationId or operationPath`, {
|
|
256
|
+
...scope,
|
|
254
257
|
reason: 'missing-operation'
|
|
255
258
|
});
|
|
256
259
|
}
|
|
@@ -304,10 +307,12 @@ class StepExecutor {
|
|
|
304
307
|
* Public for the same reason as {@link StepExecutor.selectActions}: a step
|
|
305
308
|
* targeting a `workflowId` is run by the workflow executor and never reaches
|
|
306
309
|
* {@link StepExecutor.execute}, but its criteria are still the author's
|
|
307
|
-
* assertion about the step and must be evaluated somewhere.
|
|
310
|
+
* assertion about the step and must be evaluated somewhere. `workflowId`
|
|
311
|
+
* names the step's workflow on a thrown error, as for
|
|
312
|
+
* {@link StepExecutor.execute}.
|
|
308
313
|
*/
|
|
309
|
-
evaluateCriteria(step, context) {
|
|
310
|
-
this.assertSuccessCriteria(step);
|
|
314
|
+
evaluateCriteria(step, context, workflowId) {
|
|
315
|
+
this.assertSuccessCriteria(step, workflowId);
|
|
311
316
|
const criteria = step.successCriteria;
|
|
312
317
|
if (criteria === undefined) return [];
|
|
313
318
|
|
|
@@ -335,8 +340,10 @@ class StepExecutor {
|
|
|
335
340
|
* it runs itself and which therefore never reaches
|
|
336
341
|
* {@link StepExecutor.execute}. Both callers reach the criteria evaluator and
|
|
337
342
|
* the expression context through this one method rather than wiring their own.
|
|
343
|
+
* `workflowId` names the step's workflow on a thrown error, as for
|
|
344
|
+
* {@link StepExecutor.execute}.
|
|
338
345
|
*/
|
|
339
|
-
selectActions(step, successful, context) {
|
|
346
|
+
selectActions(step, successful, context, workflowId) {
|
|
340
347
|
const actionCriteria = [];
|
|
341
348
|
const isCriterionMet = (criterion, action) => {
|
|
342
349
|
const record = {
|
|
@@ -347,6 +354,9 @@ class StepExecutor {
|
|
|
347
354
|
return record.met;
|
|
348
355
|
};
|
|
349
356
|
const scope = {
|
|
357
|
+
...(workflowId === undefined ? {} : {
|
|
358
|
+
workflowId
|
|
359
|
+
}),
|
|
350
360
|
stepId: (0, _apidomCore.toValue)(step.stepId)
|
|
351
361
|
};
|
|
352
362
|
const matchedActions = successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
|