@usearazzo/runner 1.0.1-alpha.22 → 1.0.1-alpha.24
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 +20 -0
- package/README.md +1 -1
- package/dist/arazzo-runner.browser.js +473 -293
- package/dist/arazzo-runner.browser.min.js +1 -1
- package/package.json +4 -4
- package/src/action/ActionResolver.cjs +4 -4
- package/src/action/ActionResolver.mjs +4 -4
- package/src/criterion/CriterionEvaluator.cjs +8 -9
- package/src/criterion/CriterionEvaluator.mjs +8 -9
- package/src/criterion/JSONPathCriterionEvaluator.cjs +1 -2
- package/src/criterion/JSONPathCriterionEvaluator.mjs +1 -2
- package/src/criterion/RegexCriterionEvaluator.cjs +1 -2
- package/src/criterion/RegexCriterionEvaluator.mjs +1 -2
- package/src/criterion/SimpleCriterionEvaluator.cjs +1 -2
- package/src/criterion/SimpleCriterionEvaluator.mjs +1 -2
- package/src/criterion/XPathCriterionEvaluator.cjs +4 -5
- package/src/criterion/XPathCriterionEvaluator.mjs +4 -5
- package/src/errors/CriterionError.cjs +52 -2
- package/src/errors/CriterionError.mjs +53 -2
- package/src/errors/ExecutionError.cjs +73 -2
- package/src/errors/ExecutionError.mjs +74 -2
- package/src/errors/ResolverError.cjs +53 -2
- package/src/errors/ResolverError.mjs +54 -2
- package/src/errors/RuntimeExpressionError.cjs +32 -2
- package/src/errors/RuntimeExpressionError.mjs +33 -2
- package/src/executor/ArazzoWorkflowLocatorNormalizer.cjs +4 -4
- package/src/executor/ArazzoWorkflowLocatorNormalizer.mjs +4 -4
- package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +79 -47
- package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +80 -48
- package/src/executor/StepExecutor.cjs +42 -32
- package/src/executor/StepExecutor.mjs +42 -32
- package/src/executor/StepRetryRunner.cjs +1 -1
- package/src/executor/StepRetryRunner.mjs +1 -1
- package/src/executor/StepTransitionInterpreter.cjs +3 -3
- package/src/executor/StepTransitionInterpreter.mjs +3 -3
- package/src/executor/WorkflowCallStack.cjs +2 -2
- package/src/executor/WorkflowCallStack.mjs +2 -2
- package/src/executor/WorkflowExecutor.cjs +17 -16
- package/src/executor/WorkflowExecutor.mjs +17 -16
- package/src/executor/abort.cjs +3 -3
- package/src/executor/abort.mjs +3 -3
- package/src/expression/RuntimeExpressionEvaluator.cjs +5 -5
- package/src/expression/RuntimeExpressionEvaluator.mjs +5 -5
- package/src/index.cjs +0 -7
- package/src/index.mjs +0 -1
- package/src/resolver/OpenAPIOperationParameterResolver.cjs +6 -6
- package/src/resolver/OpenAPIOperationParameterResolver.mjs +6 -6
- package/src/resolver/OutputResolver.cjs +3 -3
- package/src/resolver/OutputResolver.mjs +3 -3
- package/src/resolver/RequestBodyResolver.cjs +6 -6
- package/src/resolver/RequestBodyResolver.mjs +6 -6
- package/src/resolver/WorkflowParameterResolver.cjs +1 -1
- package/src/resolver/WorkflowParameterResolver.mjs +1 -1
- package/types/runner.d.ts +236 -60
- package/src/criterion/CriterionErrorReason.cjs +0 -51
- package/src/criterion/CriterionErrorReason.mjs +0 -46
|
@@ -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: ExecutionError.SourceDescriptionNotFound
|
|
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: ExecutionError.InvalidOperationReference
|
|
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: ExecutionError.InvalidOperationReference
|
|
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: ExecutionError.SourceDescriptionNotOpenAPI
|
|
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: ExecutionError.OperationNotFound
|
|
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: ExecutionError.InvalidOperationReference
|
|
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: ExecutionError.OperationNotFound
|
|
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: ExecutionError.OperationNotFound
|
|
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: ExecutionError.OperationNotFound
|
|
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: ExecutionError.SourceDescriptionNotFound
|
|
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: ExecutionError.SourceDescriptionNotOpenAPI
|
|
184
216
|
});
|
|
185
217
|
}
|
|
186
218
|
return source;
|
|
@@ -130,42 +130,49 @@ class StepExecutor {
|
|
|
130
130
|
* take precedence over it.
|
|
131
131
|
*
|
|
132
132
|
* A `signal` in that bag is honored here and not only at the transport: an
|
|
133
|
-
* already-aborted signal throws
|
|
133
|
+
* already-aborted signal throws with reason `ExecutionError.Aborted` instead of issuing a
|
|
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
|
-
|
|
147
|
-
reason:
|
|
156
|
+
...scope,
|
|
157
|
+
reason: _ExecutionError.default.AmbiguousTarget
|
|
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
|
-
|
|
153
|
-
reason:
|
|
162
|
+
...scope,
|
|
163
|
+
reason: _ExecutionError.default.WorkflowStep
|
|
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,16 +245,16 @@ 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
|
-
|
|
254
|
-
reason:
|
|
255
|
+
throw new _ExecutionError.default(`Step "${scope.stepId}" has no operationId or operationPath`, {
|
|
256
|
+
...scope,
|
|
257
|
+
reason: _ExecutionError.default.MissingOperation
|
|
255
258
|
});
|
|
256
259
|
}
|
|
257
260
|
|
|
@@ -282,13 +285,13 @@ class StepExecutor {
|
|
|
282
285
|
if (!(0, _apidomDatamodel.isArrayElement)(criteria)) {
|
|
283
286
|
throw new _ResolverError.default("A step's `successCriteria` is present but is not a list", {
|
|
284
287
|
...scope,
|
|
285
|
-
reason:
|
|
288
|
+
reason: _ResolverError.default.MalformedCriteria
|
|
286
289
|
});
|
|
287
290
|
}
|
|
288
291
|
if (![...criteria].every(_apidomNsArazzo.isCriterionElement)) {
|
|
289
292
|
throw new _ResolverError.default("A step's `successCriteria` contains an entry that is not a Criterion Object", {
|
|
290
293
|
...scope,
|
|
291
|
-
reason:
|
|
294
|
+
reason: _ResolverError.default.MalformedCriterion
|
|
292
295
|
});
|
|
293
296
|
}
|
|
294
297
|
}
|
|
@@ -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);
|
|
@@ -384,7 +394,7 @@ class StepExecutor {
|
|
|
384
394
|
// a step that calls a workflow made no HTTP call, so a condition on one
|
|
385
395
|
// fails like any unevaluable condition; the diagnostic keeps the
|
|
386
396
|
// authoring mistake visible.
|
|
387
|
-
if (error instanceof _RuntimeExpressionError.default && error.reason ===
|
|
397
|
+
if (error instanceof _RuntimeExpressionError.default && error.reason === _RuntimeExpressionError.default.UnavailableExpression) {
|
|
388
398
|
return {
|
|
389
399
|
condition,
|
|
390
400
|
met: false,
|
|
@@ -123,42 +123,49 @@ class StepExecutor {
|
|
|
123
123
|
* take precedence over it.
|
|
124
124
|
*
|
|
125
125
|
* A `signal` in that bag is honored here and not only at the transport: an
|
|
126
|
-
* already-aborted signal throws
|
|
126
|
+
* already-aborted signal throws with reason `ExecutionError.Aborted` instead of issuing a
|
|
127
127
|
* request nobody is waiting for, whose wire-level cancellation would surface
|
|
128
128
|
* as a transport {@link ClientError} — a request that failed on its own terms
|
|
129
129
|
* — rather than as the withdrawal it is.
|
|
130
|
+
*
|
|
131
|
+
* `workflowId` names the workflow the step belongs to; every error raised
|
|
132
|
+
* here then carries it alongside the `stepId`, since step ids are only
|
|
133
|
+
* unique within a workflow. The workflow executor always passes it.
|
|
130
134
|
*/
|
|
131
|
-
async execute(step, state, executeOptions = {}) {
|
|
135
|
+
async execute(step, state, executeOptions = {}, workflowId) {
|
|
132
136
|
const stepId = toValue(step.stepId);
|
|
137
|
+
const scope = {
|
|
138
|
+
...(workflowId === undefined ? {} : {
|
|
139
|
+
workflowId
|
|
140
|
+
}),
|
|
141
|
+
stepId
|
|
142
|
+
};
|
|
133
143
|
|
|
134
144
|
// a step's target fields are mutually exclusive; declaring more than one is
|
|
135
145
|
// malformed and has no defined resolution.
|
|
136
146
|
const declaredTargets = STEP_TARGET_FIELDS.filter(field => isStringElement(step[field]));
|
|
137
147
|
if (declaredTargets.length > 1) {
|
|
138
148
|
throw new ExecutionError(`Step "${stepId}" declares more than one of ${STEP_TARGET_FIELDS.join(', ')} (mutually exclusive)`, {
|
|
139
|
-
|
|
140
|
-
reason:
|
|
149
|
+
...scope,
|
|
150
|
+
reason: ExecutionError.AmbiguousTarget
|
|
141
151
|
});
|
|
142
152
|
}
|
|
143
153
|
if (isStringElement(step.workflowId)) {
|
|
144
154
|
throw new ExecutionError(`Step "${stepId}" targets a workflowId; use the workflow executor`, {
|
|
145
|
-
|
|
146
|
-
reason:
|
|
155
|
+
...scope,
|
|
156
|
+
reason: ExecutionError.WorkflowStep
|
|
147
157
|
});
|
|
148
158
|
}
|
|
149
|
-
this.assertSuccessCriteria(step);
|
|
159
|
+
this.assertSuccessCriteria(step, workflowId);
|
|
150
160
|
|
|
151
161
|
// locate which operation the step invokes — reduced to a (document, JSON
|
|
152
162
|
// Pointer) locator that the operation executor extracts, normalizes,
|
|
153
163
|
// assembles into a standalone doc, and runs.
|
|
154
|
-
const locator = await this.#locateOperation(step,
|
|
164
|
+
const locator = await this.#locateOperation(step, scope);
|
|
155
165
|
|
|
156
166
|
// resolve parameters and request body against the pre-request context.
|
|
157
167
|
// keyed by '{in}.{name}' rather than bare name, so two parameters that
|
|
158
168
|
// legally differ only in their location each reach their own place.
|
|
159
|
-
const scope = {
|
|
160
|
-
stepId
|
|
161
|
-
};
|
|
162
169
|
const preContext = state.toContext();
|
|
163
170
|
const parameters = this.#parameterResolver.resolve(step.parameters, expression => this.#evaluate(preContext, expression), scope);
|
|
164
171
|
const requestBody = this.#requestBodyResolver.resolve(step.requestBody, expression => this.#evaluate(preContext, expression), scope);
|
|
@@ -169,9 +176,7 @@ class StepExecutor {
|
|
|
169
176
|
// live side effect. The signal stays in the bag, so a request already in
|
|
170
177
|
// flight is aborted by the transport as before.
|
|
171
178
|
const signal = readAbortSignal(executeOptions);
|
|
172
|
-
throwIfAborted(signal,
|
|
173
|
-
stepId
|
|
174
|
-
});
|
|
179
|
+
throwIfAborted(signal, scope);
|
|
175
180
|
|
|
176
181
|
// the Arazzo-derived parameters and request body are spread after the opaque
|
|
177
182
|
// executeOptions bag so they take precedence over it. the operation executor
|
|
@@ -190,22 +195,20 @@ class StepExecutor {
|
|
|
190
195
|
// cancelled, it did not: reporting it as the abort keeps one error shape
|
|
191
196
|
// whether the run was withdrawn between two steps or mid-flight, and keeps
|
|
192
197
|
// the whole feature from reading differently per transport.
|
|
193
|
-
throwIfAborted(signal,
|
|
194
|
-
stepId
|
|
195
|
-
});
|
|
198
|
+
throwIfAborted(signal, scope);
|
|
196
199
|
throw error;
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
// evaluate criteria, outputs, and the next action against the post-request
|
|
200
203
|
// context (with $request / $url / $method and $response / $statusCode).
|
|
201
204
|
const postContext = state.toContext(this.#requestContext(response), this.#responseContext(response));
|
|
202
|
-
const successCriteria = this.evaluateCriteria(step, postContext);
|
|
205
|
+
const successCriteria = this.evaluateCriteria(step, postContext, workflowId);
|
|
203
206
|
const successful = successCriteria.every(criterion => criterion.met);
|
|
204
207
|
const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(postContext, expression), scope);
|
|
205
208
|
const {
|
|
206
209
|
matchedActions,
|
|
207
210
|
actionCriteria
|
|
208
|
-
} = this.selectActions(step, successful, postContext);
|
|
211
|
+
} = this.selectActions(step, successful, postContext, workflowId);
|
|
209
212
|
return {
|
|
210
213
|
stepId,
|
|
211
214
|
response,
|
|
@@ -235,16 +238,16 @@ class StepExecutor {
|
|
|
235
238
|
requestContentType: requestBody.contentType
|
|
236
239
|
};
|
|
237
240
|
}
|
|
238
|
-
async #locateOperation(step,
|
|
241
|
+
async #locateOperation(step, scope) {
|
|
239
242
|
if (isStringElement(step.operationId)) {
|
|
240
|
-
return this.#locatorNormalizer.normalizeOperationId(toValue(step.operationId), this.#document);
|
|
243
|
+
return this.#locatorNormalizer.normalizeOperationId(toValue(step.operationId), this.#document, scope);
|
|
241
244
|
}
|
|
242
245
|
if (isStringElement(step.operationPath)) {
|
|
243
|
-
return this.#locatorNormalizer.normalizeOperationPath(toValue(step.operationPath), this.#document);
|
|
246
|
+
return this.#locatorNormalizer.normalizeOperationPath(toValue(step.operationPath), this.#document, scope);
|
|
244
247
|
}
|
|
245
|
-
throw new ExecutionError(`Step "${stepId}" has no operationId or operationPath`, {
|
|
246
|
-
|
|
247
|
-
reason:
|
|
248
|
+
throw new ExecutionError(`Step "${scope.stepId}" has no operationId or operationPath`, {
|
|
249
|
+
...scope,
|
|
250
|
+
reason: ExecutionError.MissingOperation
|
|
248
251
|
});
|
|
249
252
|
}
|
|
250
253
|
|
|
@@ -275,13 +278,13 @@ class StepExecutor {
|
|
|
275
278
|
if (!isArrayElement(criteria)) {
|
|
276
279
|
throw new ResolverError("A step's `successCriteria` is present but is not a list", {
|
|
277
280
|
...scope,
|
|
278
|
-
reason:
|
|
281
|
+
reason: ResolverError.MalformedCriteria
|
|
279
282
|
});
|
|
280
283
|
}
|
|
281
284
|
if (![...criteria].every(isCriterionElement)) {
|
|
282
285
|
throw new ResolverError("A step's `successCriteria` contains an entry that is not a Criterion Object", {
|
|
283
286
|
...scope,
|
|
284
|
-
reason:
|
|
287
|
+
reason: ResolverError.MalformedCriterion
|
|
285
288
|
});
|
|
286
289
|
}
|
|
287
290
|
}
|
|
@@ -297,10 +300,12 @@ class StepExecutor {
|
|
|
297
300
|
* Public for the same reason as {@link StepExecutor.selectActions}: a step
|
|
298
301
|
* targeting a `workflowId` is run by the workflow executor and never reaches
|
|
299
302
|
* {@link StepExecutor.execute}, but its criteria are still the author's
|
|
300
|
-
* assertion about the step and must be evaluated somewhere.
|
|
303
|
+
* assertion about the step and must be evaluated somewhere. `workflowId`
|
|
304
|
+
* names the step's workflow on a thrown error, as for
|
|
305
|
+
* {@link StepExecutor.execute}.
|
|
301
306
|
*/
|
|
302
|
-
evaluateCriteria(step, context) {
|
|
303
|
-
this.assertSuccessCriteria(step);
|
|
307
|
+
evaluateCriteria(step, context, workflowId) {
|
|
308
|
+
this.assertSuccessCriteria(step, workflowId);
|
|
304
309
|
const criteria = step.successCriteria;
|
|
305
310
|
if (criteria === undefined) return [];
|
|
306
311
|
|
|
@@ -328,8 +333,10 @@ class StepExecutor {
|
|
|
328
333
|
* it runs itself and which therefore never reaches
|
|
329
334
|
* {@link StepExecutor.execute}. Both callers reach the criteria evaluator and
|
|
330
335
|
* the expression context through this one method rather than wiring their own.
|
|
336
|
+
* `workflowId` names the step's workflow on a thrown error, as for
|
|
337
|
+
* {@link StepExecutor.execute}.
|
|
331
338
|
*/
|
|
332
|
-
selectActions(step, successful, context) {
|
|
339
|
+
selectActions(step, successful, context, workflowId) {
|
|
333
340
|
const actionCriteria = [];
|
|
334
341
|
const isCriterionMet = (criterion, action) => {
|
|
335
342
|
const record = {
|
|
@@ -340,6 +347,9 @@ class StepExecutor {
|
|
|
340
347
|
return record.met;
|
|
341
348
|
};
|
|
342
349
|
const scope = {
|
|
350
|
+
...(workflowId === undefined ? {} : {
|
|
351
|
+
workflowId
|
|
352
|
+
}),
|
|
343
353
|
stepId: toValue(step.stepId)
|
|
344
354
|
};
|
|
345
355
|
const matchedActions = successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
|
|
@@ -377,7 +387,7 @@ class StepExecutor {
|
|
|
377
387
|
// a step that calls a workflow made no HTTP call, so a condition on one
|
|
378
388
|
// fails like any unevaluable condition; the diagnostic keeps the
|
|
379
389
|
// authoring mistake visible.
|
|
380
|
-
if (error instanceof RuntimeExpressionError && error.reason ===
|
|
390
|
+
if (error instanceof RuntimeExpressionError && error.reason === RuntimeExpressionError.UnavailableExpression) {
|
|
381
391
|
return {
|
|
382
392
|
condition,
|
|
383
393
|
met: false,
|
|
@@ -209,7 +209,7 @@ class StepRetryRunner {
|
|
|
209
209
|
throw new _ExecutionError.default(`retry action on step "${context.stepId}" in workflow "${context.workflowId}" carries a stepId/workflowId reference, but no runReference was given to run it`, {
|
|
210
210
|
stepId: context.stepId,
|
|
211
211
|
workflowId: context.workflowId,
|
|
212
|
-
reason:
|
|
212
|
+
reason: _ExecutionError.default.RetryReferenceUnsupported
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
215
|
}
|
|
@@ -199,7 +199,7 @@ class StepRetryRunner {
|
|
|
199
199
|
throw new ExecutionError(`retry action on step "${context.stepId}" in workflow "${context.workflowId}" carries a stepId/workflowId reference, but no runReference was given to run it`, {
|
|
200
200
|
stepId: context.stepId,
|
|
201
201
|
workflowId: context.workflowId,
|
|
202
|
-
reason:
|
|
202
|
+
reason: ExecutionError.RetryReferenceUnsupported
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
205
|
}
|
|
@@ -104,7 +104,7 @@ class StepTransitionInterpreter {
|
|
|
104
104
|
throw new _ExecutionError.default(`goto action on step "${stepId}" in workflow "${workflowId}" declares both a stepId and a workflowId (mutually exclusive)`, {
|
|
105
105
|
stepId,
|
|
106
106
|
workflowId,
|
|
107
|
-
reason:
|
|
107
|
+
reason: _ExecutionError.default.AmbiguousTarget
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
if (hasStepId) {
|
|
@@ -122,13 +122,13 @@ class StepTransitionInterpreter {
|
|
|
122
122
|
throw new _ExecutionError.default(`goto action on step "${stepId}" in workflow "${workflowId}" has neither stepId nor workflowId`, {
|
|
123
123
|
stepId,
|
|
124
124
|
workflowId,
|
|
125
|
-
reason:
|
|
125
|
+
reason: _ExecutionError.default.GotoTargetMissing
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
throw new _ExecutionError.default(`action on step "${stepId}" in workflow "${workflowId}" has unsupported type "${type}"`, {
|
|
129
129
|
stepId,
|
|
130
130
|
workflowId,
|
|
131
|
-
reason:
|
|
131
|
+
reason: _ExecutionError.default.UnknownActionType
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
|