@usearazzo/runner 0.0.0-stage → 1.0.1-alpha.11
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 +37 -0
- package/LICENSE +202 -0
- package/NOTICE +20 -0
- package/README.md +163 -2
- package/dist/arazzo-runner.browser.js +113245 -0
- package/dist/arazzo-runner.browser.min.js +1 -0
- package/package.json +95 -4
- package/src/ArazzoRunner.cjs +122 -0
- package/src/ArazzoRunner.mjs +113 -0
- package/src/action/ActionResolver.cjs +160 -0
- package/src/action/ActionResolver.mjs +150 -0
- package/src/assembler/OpenAPI2DocumentAssembler.cjs +111 -0
- package/src/assembler/OpenAPI2DocumentAssembler.mjs +104 -0
- package/src/assembler/OpenAPI30DocumentAssembler.cjs +110 -0
- package/src/assembler/OpenAPI30DocumentAssembler.mjs +103 -0
- package/src/assembler/OpenAPI31DocumentAssembler.cjs +111 -0
- package/src/assembler/OpenAPI31DocumentAssembler.mjs +104 -0
- package/src/assembler/OpenAPIDocumentAssembler.cjs +58 -0
- package/src/assembler/OpenAPIDocumentAssembler.mjs +50 -0
- package/src/client/HTTPClient.cjs +5 -0
- package/src/client/HTTPClient.mjs +1 -0
- package/src/client/HTTPClientFetch.cjs +16 -0
- package/src/client/HTTPClientFetch.mjs +10 -0
- package/src/client/OpenAPIOperationRequest.cjs +5 -0
- package/src/client/OpenAPIOperationRequest.mjs +1 -0
- package/src/client/OpenAPIOperationResponse.cjs +42 -0
- package/src/client/OpenAPIOperationResponse.mjs +36 -0
- package/src/client/OpenAPIOperationResponseNormalizer.cjs +48 -0
- package/src/client/OpenAPIOperationResponseNormalizer.mjs +40 -0
- package/src/client/ParameterDelivery.cjs +119 -0
- package/src/client/ParameterDelivery.mjs +113 -0
- package/src/constants.cjs +9 -0
- package/src/constants.mjs +3 -0
- package/src/criterion/CriterionEvaluator.cjs +205 -0
- package/src/criterion/CriterionEvaluator.mjs +196 -0
- package/src/criterion/JSONPathCriterionEvaluator.cjs +47 -0
- package/src/criterion/JSONPathCriterionEvaluator.mjs +40 -0
- package/src/criterion/RegexCriterionEvaluator.cjs +67 -0
- package/src/criterion/RegexCriterionEvaluator.mjs +60 -0
- package/src/criterion/SimpleCriterionEvaluator.cjs +50 -0
- package/src/criterion/SimpleCriterionEvaluator.mjs +43 -0
- package/src/criterion/XPathCriterionEvaluator.cjs +117 -0
- package/src/criterion/XPathCriterionEvaluator.mjs +109 -0
- package/src/document/APIDocument.cjs +35 -0
- package/src/document/APIDocument.mjs +30 -0
- package/src/document/ArazzoDocument.cjs +81 -0
- package/src/document/ArazzoDocument.mjs +74 -0
- package/src/document/ArazzoSourceDescriptionIndex.cjs +26 -0
- package/src/document/ArazzoSourceDescriptionIndex.mjs +20 -0
- package/src/document/ArazzoStepIndex.cjs +25 -0
- package/src/document/ArazzoStepIndex.mjs +19 -0
- package/src/document/ArazzoWorkflowIndex.cjs +17 -0
- package/src/document/ArazzoWorkflowIndex.mjs +11 -0
- package/src/document/OpenAPIDocument.cjs +28 -0
- package/src/document/OpenAPIDocument.mjs +21 -0
- package/src/document/OpenAPIOperationIndex.cjs +24 -0
- package/src/document/OpenAPIOperationIndex.mjs +18 -0
- package/src/document/ParameterIdentity.cjs +93 -0
- package/src/document/ParameterIdentity.mjs +88 -0
- package/src/document/openapi-types.cjs +5 -0
- package/src/document/openapi-types.mjs +1 -0
- package/src/errors/ArazzoRunnerError.cjs +10 -0
- package/src/errors/ArazzoRunnerError.mjs +5 -0
- package/src/errors/AssemblerError.cjs +11 -0
- package/src/errors/AssemblerError.mjs +4 -0
- package/src/errors/ClientError.cjs +11 -0
- package/src/errors/ClientError.mjs +4 -0
- package/src/errors/CriterionError.cjs +11 -0
- package/src/errors/CriterionError.mjs +4 -0
- package/src/errors/ExecutionError.cjs +11 -0
- package/src/errors/ExecutionError.mjs +4 -0
- package/src/errors/ExtractionError.cjs +11 -0
- package/src/errors/ExtractionError.mjs +4 -0
- package/src/errors/InvalidEntryDocumentError.cjs +11 -0
- package/src/errors/InvalidEntryDocumentError.mjs +4 -0
- package/src/errors/NormalizationError.cjs +11 -0
- package/src/errors/NormalizationError.mjs +4 -0
- package/src/errors/ResolverError.cjs +11 -0
- package/src/errors/ResolverError.mjs +4 -0
- package/src/errors/RuntimeExpressionError.cjs +11 -0
- package/src/errors/RuntimeExpressionError.mjs +4 -0
- package/src/errors/UnmatchedProviderError.cjs +11 -0
- package/src/errors/UnmatchedProviderError.mjs +4 -0
- package/src/executor/ArazzoWorkflowLocatorNormalizer.cjs +147 -0
- package/src/executor/ArazzoWorkflowLocatorNormalizer.mjs +137 -0
- package/src/executor/OpenAPIOperationExecutor.cjs +458 -0
- package/src/executor/OpenAPIOperationExecutor.mjs +447 -0
- package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +197 -0
- package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +189 -0
- package/src/executor/SourceDescriptionsLocatorNormalizer.cjs +44 -0
- package/src/executor/SourceDescriptionsLocatorNormalizer.mjs +38 -0
- package/src/executor/StepExecutor.cjs +333 -0
- package/src/executor/StepExecutor.mjs +326 -0
- package/src/executor/StepRetryRunner.cjs +226 -0
- package/src/executor/StepRetryRunner.mjs +215 -0
- package/src/executor/StepTransitionInterpreter.cjs +159 -0
- package/src/executor/StepTransitionInterpreter.mjs +151 -0
- package/src/executor/WorkflowCallStack.cjs +116 -0
- package/src/executor/WorkflowCallStack.mjs +106 -0
- package/src/executor/WorkflowExecutor.cjs +1091 -0
- package/src/executor/WorkflowExecutor.mjs +1077 -0
- package/src/executor/abort.cjs +70 -0
- package/src/executor/abort.mjs +60 -0
- package/src/expression/RuntimeExpressionContext.cjs +5 -0
- package/src/expression/RuntimeExpressionContext.mjs +1 -0
- package/src/expression/RuntimeExpressionEvaluator.cjs +337 -0
- package/src/expression/RuntimeExpressionEvaluator.mjs +328 -0
- package/src/extractor/ArazzoStepExtractor.cjs +48 -0
- package/src/extractor/ArazzoStepExtractor.mjs +41 -0
- package/src/extractor/ArazzoWorkflowExtractor.cjs +50 -0
- package/src/extractor/ArazzoWorkflowExtractor.mjs +43 -0
- package/src/extractor/OpenAPIOperationExtractor.cjs +84 -0
- package/src/extractor/OpenAPIOperationExtractor.mjs +77 -0
- package/src/index.cjs +426 -0
- package/src/index.mjs +75 -0
- package/src/normalizer/ArazzoStepNormalizer.cjs +23 -0
- package/src/normalizer/ArazzoStepNormalizer.mjs +17 -0
- package/src/normalizer/ArazzoWorkflowNormalizer.cjs +214 -0
- package/src/normalizer/ArazzoWorkflowNormalizer.mjs +206 -0
- package/src/normalizer/OpenAPI2OperationNormalizer.cjs +128 -0
- package/src/normalizer/OpenAPI2OperationNormalizer.mjs +120 -0
- package/src/normalizer/OpenAPI30OperationNormalizer.cjs +129 -0
- package/src/normalizer/OpenAPI30OperationNormalizer.mjs +121 -0
- package/src/normalizer/OpenAPI31OperationNormalizer.cjs +131 -0
- package/src/normalizer/OpenAPI31OperationNormalizer.mjs +123 -0
- package/src/normalizer/OpenAPIOperationNormalizer.cjs +58 -0
- package/src/normalizer/OpenAPIOperationNormalizer.mjs +50 -0
- package/src/registry/DocumentRegistry.cjs +160 -0
- package/src/registry/DocumentRegistry.mjs +151 -0
- package/src/registry/providers/ArazzoDocumentRegistryProvider.cjs +154 -0
- package/src/registry/providers/ArazzoDocumentRegistryProvider.mjs +146 -0
- package/src/registry/providers/DocumentRegistryProvider.cjs +31 -0
- package/src/registry/providers/DocumentRegistryProvider.mjs +25 -0
- package/src/registry/providers/OpenAPIDocumentRegistryProvider.cjs +122 -0
- package/src/registry/providers/OpenAPIDocumentRegistryProvider.mjs +114 -0
- package/src/resolver/ArazzoValueResolver.cjs +42 -0
- package/src/resolver/ArazzoValueResolver.mjs +37 -0
- package/src/resolver/OpenAPIOperationParameterResolver.cjs +117 -0
- package/src/resolver/OpenAPIOperationParameterResolver.mjs +110 -0
- package/src/resolver/OutputResolver.cjs +71 -0
- package/src/resolver/OutputResolver.mjs +64 -0
- package/src/resolver/RequestBodyResolver.cjs +227 -0
- package/src/resolver/RequestBodyResolver.mjs +220 -0
- package/src/resolver/ResolverScope.cjs +5 -0
- package/src/resolver/ResolverScope.mjs +1 -0
- package/src/resolver/StepParameterResolver.cjs +22 -0
- package/src/resolver/StepParameterResolver.mjs +15 -0
- package/src/resolver/WorkflowParameterResolver.cjs +73 -0
- package/src/resolver/WorkflowParameterResolver.mjs +66 -0
- package/src/state/WorkflowExecutionState.cjs +99 -0
- package/src/state/WorkflowExecutionState.mjs +93 -0
- package/src/vendor/swagger-client.cjs +1 -0
- package/src/vendor/swagger-client.mjs +1 -0
- package/types/runner.d.ts +2410 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { test as isRuntimeExpression } from '@swaggerexpert/arazzo-runtime-expression';
|
|
2
|
+
import { URIFragmentIdentifier, testJSONPointer } from '@swaggerexpert/json-pointer';
|
|
3
|
+
import { url } from '@speclynx/apidom-reference/configuration/empty';
|
|
4
|
+
import OpenAPIDocument from "../document/OpenAPIDocument.mjs";
|
|
5
|
+
import RuntimeExpressionEvaluator from "../expression/RuntimeExpressionEvaluator.mjs";
|
|
6
|
+
import ExtractionError from "../errors/ExtractionError.mjs";
|
|
7
|
+
import SourceDescriptionsLocatorNormalizer from "./SourceDescriptionsLocatorNormalizer.mjs";
|
|
8
|
+
/**
|
|
9
|
+
* A canonical OpenAPI operation locator: the OpenAPI document that owns the
|
|
10
|
+
* operation and a JSON Pointer locating the operation within it
|
|
11
|
+
* (`/paths/{path}/{method}`).
|
|
12
|
+
*
|
|
13
|
+
* A step names its operation in one of several ways — a plain `operationId`, a
|
|
14
|
+
* `$sourceDescriptions` runtime expression, or an `operationPath` — which are
|
|
15
|
+
* all locators for the same thing. The normalizer reduces any of them to this
|
|
16
|
+
* single canonical form.
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes a step's OpenAPI operation locator to a canonical
|
|
21
|
+
* {@link OpenAPIOperationLocator} — the OpenAPI document and a JSON Pointer to
|
|
22
|
+
* the operation within it.
|
|
23
|
+
*
|
|
24
|
+
* A step names its operation with either `operationId` or `operationPath`:
|
|
25
|
+
* - `operationId` is a plain operation name, or a whole runtime expression
|
|
26
|
+
* `$sourceDescriptions.{name}.{operationId}`. Per the Arazzo Specification, a
|
|
27
|
+
* plain name is only valid when a single (non-arazzo) source description is
|
|
28
|
+
* defined; multiple sources require the expression form. A plain name is
|
|
29
|
+
* therefore resolved against the first source description that has it.
|
|
30
|
+
* - `operationPath` embeds a `{$sourceDescriptions.{name}.url}` runtime
|
|
31
|
+
* expression followed by a JSON Pointer to the operation.
|
|
32
|
+
*
|
|
33
|
+
* The `type` declared on a source description is not trusted (it may be absent
|
|
34
|
+
* or wrong); a source is classified by what it actually parses to. The
|
|
35
|
+
* normalizer acquires the referenced source documents from the registry on
|
|
36
|
+
* demand.
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
class OpenAPIOperationLocatorNormalizer extends SourceDescriptionsLocatorNormalizer {
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes a plain or expression `operationId` to a canonical locator.
|
|
42
|
+
*
|
|
43
|
+
* A whole runtime expression (`$sourceDescriptions.{name}.{operationId}`) is
|
|
44
|
+
* resolved against its named source; a plain name is resolved against the
|
|
45
|
+
* first source description that has it.
|
|
46
|
+
*/
|
|
47
|
+
async normalizeOperationId(operationId, document) {
|
|
48
|
+
if (isRuntimeExpression(operationId)) {
|
|
49
|
+
return this.#resolveSourced(operationId, document);
|
|
50
|
+
}
|
|
51
|
+
return this.#resolvePlainOperationId(operationId, document);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Normalizes an `operationPath` —
|
|
56
|
+
* `{$sourceDescriptions.{name}.url}#/json/pointer` — to a canonical locator.
|
|
57
|
+
*
|
|
58
|
+
* The reference is interpolated (resolving
|
|
59
|
+
* `{$sourceDescriptions.{name}.url}` to the source's URL) into a URI
|
|
60
|
+
* reference; its fragment is the JSON Pointer to the operation (decoded from
|
|
61
|
+
* the URI fragment representation) and the part before the fragment is the
|
|
62
|
+
* source document to acquire.
|
|
63
|
+
*
|
|
64
|
+
* The runner only needs a URL and a JSON Pointer, so it does not enforce that
|
|
65
|
+
* the source description is identified by a runtime expression rather than a
|
|
66
|
+
* literal URL (a literal URL interpolates to itself and still resolves) — that
|
|
67
|
+
* is a conformance concern for the validator. It fails only when the URL does
|
|
68
|
+
* not resolve to an OpenAPI source or the JSON Pointer is invalid.
|
|
69
|
+
*/
|
|
70
|
+
async normalizeOperationPath(operationPath, document) {
|
|
71
|
+
const evaluator = new RuntimeExpressionEvaluator({}, {
|
|
72
|
+
strict: false,
|
|
73
|
+
document,
|
|
74
|
+
registry: this.registry
|
|
75
|
+
});
|
|
76
|
+
const uriReference = evaluator.interpolate(operationPath);
|
|
77
|
+
const documentURI = url.sanitize(url.stripHash(uriReference));
|
|
78
|
+
if (documentURI === '') {
|
|
79
|
+
throw new ExtractionError(`operationPath "${operationPath}" does not resolve to a source description URL`, {
|
|
80
|
+
pointer: operationPath,
|
|
81
|
+
uri: document.uri
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const jsonPointer = URIFragmentIdentifier.fromURIReference(uriReference);
|
|
85
|
+
if (!testJSONPointer(jsonPointer)) {
|
|
86
|
+
throw new ExtractionError(`operationPath "${operationPath}" has an invalid JSON Pointer`, {
|
|
87
|
+
pointer: operationPath,
|
|
88
|
+
uri: document.uri
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const source = await this.registry.acquire(documentURI);
|
|
92
|
+
if (!OpenAPIDocument.is(source)) {
|
|
93
|
+
throw new ExtractionError(`operationPath "${operationPath}" does not reference an OpenAPI document`, {
|
|
94
|
+
pointer: operationPath,
|
|
95
|
+
uri: documentURI
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
document: source,
|
|
100
|
+
jsonPointer
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parses a `$sourceDescriptions.{name}.{reference}` expression, asserting
|
|
106
|
+
* it is a source-descriptions expression.
|
|
107
|
+
*/
|
|
108
|
+
#parseSourceDescriptionsExpression(expression) {
|
|
109
|
+
const parsed = this.parseSourceDescriptionsExpression(expression);
|
|
110
|
+
if (parsed === undefined) {
|
|
111
|
+
throw new ExtractionError(`Unsupported operationId expression "${expression}"`, {
|
|
112
|
+
operationId: expression
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return parsed;
|
|
116
|
+
}
|
|
117
|
+
async #resolveSourced(expression, document) {
|
|
118
|
+
const {
|
|
119
|
+
sourceName,
|
|
120
|
+
reference: operationId
|
|
121
|
+
} = this.#parseSourceDescriptionsExpression(expression);
|
|
122
|
+
const openapiDocument = await this.#sourceDocument(sourceName, document);
|
|
123
|
+
const jsonPointer = openapiDocument.operationIndex.get(operationId);
|
|
124
|
+
if (jsonPointer === undefined) {
|
|
125
|
+
throw new ExtractionError(`Operation "${operationId}" not found in source description "${sourceName}"`, {
|
|
126
|
+
operationId,
|
|
127
|
+
uri: openapiDocument.uri
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
document: openapiDocument,
|
|
132
|
+
jsonPointer
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resolves a plain operationId against the single OpenAPI source description.
|
|
138
|
+
*
|
|
139
|
+
* Per the Arazzo Specification a plain operationId is only valid when a single
|
|
140
|
+
* non-arazzo source description is defined; multiple such sources require the
|
|
141
|
+
* `$sourceDescriptions` expression form. The first OpenAPI source is therefore
|
|
142
|
+
* the one it targets: the operationId is looked up there and the lookup fails
|
|
143
|
+
* if it is absent — subsequent OpenAPI sources are not searched (their
|
|
144
|
+
* presence would make the plain operationId ambiguous, and hence invalid).
|
|
145
|
+
* Non-OpenAPI (arazzo) sources are skipped. Sources are acquired one at a time
|
|
146
|
+
* until the first OpenAPI source is reached.
|
|
147
|
+
*/
|
|
148
|
+
async #resolvePlainOperationId(operationId, document) {
|
|
149
|
+
for (const uri of document.sourceDescriptionURIs()) {
|
|
150
|
+
const source = await this.registry.acquire(uri);
|
|
151
|
+
if (!OpenAPIDocument.is(source)) continue;
|
|
152
|
+
const jsonPointer = source.operationIndex.get(operationId);
|
|
153
|
+
if (jsonPointer === undefined) {
|
|
154
|
+
throw new ExtractionError(`Operation "${operationId}" not found in source description at "${source.uri}"`, {
|
|
155
|
+
operationId,
|
|
156
|
+
uri: source.uri
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
document: source,
|
|
161
|
+
jsonPointer
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
throw new ExtractionError(`Operation "${operationId}" has no OpenAPI source description`, {
|
|
165
|
+
operationId,
|
|
166
|
+
uri: document.uri
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Acquires a source description by name and asserts it is an OpenAPI document.
|
|
172
|
+
*/
|
|
173
|
+
async #sourceDocument(sourceName, document) {
|
|
174
|
+
const uri = document.resolveSourceDescriptionURI(sourceName);
|
|
175
|
+
if (uri === undefined) {
|
|
176
|
+
throw new ExtractionError(`Source description "${sourceName}" not found`, {
|
|
177
|
+
uri: document.uri
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const source = await this.registry.acquire(uri);
|
|
181
|
+
if (!OpenAPIDocument.is(source)) {
|
|
182
|
+
throw new ExtractionError(`Source description "${sourceName}" is not an OpenAPI document`, {
|
|
183
|
+
uri
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return source;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export default OpenAPIOperationLocatorNormalizer;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _parser = require("@usearazzo/parser");
|
|
8
|
+
/**
|
|
9
|
+
* Base of the locator normalizers that resolve
|
|
10
|
+
* `$sourceDescriptions.{name}.{reference}` runtime expressions — the OpenAPI
|
|
11
|
+
* operation locator and the Arazzo workflow locator. Owns the registry handle
|
|
12
|
+
* and the one grammar-facing parse, so the expression node's name and field
|
|
13
|
+
* shape are encoded once rather than per subclass.
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
class SourceDescriptionsLocatorNormalizer {
|
|
17
|
+
registry;
|
|
18
|
+
constructor(registry) {
|
|
19
|
+
this.registry = registry;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parses a `$sourceDescriptions.{name}.{reference}` expression, or returns
|
|
24
|
+
* `undefined` when the string is not one (whether it fails to parse or
|
|
25
|
+
* parses to another expression kind) — each subclass raises its own error
|
|
26
|
+
* type, message, and context for its own position.
|
|
27
|
+
*/
|
|
28
|
+
parseSourceDescriptionsExpression(expression) {
|
|
29
|
+
const {
|
|
30
|
+
result,
|
|
31
|
+
tree
|
|
32
|
+
} = (0, _parser.parseRuntimeExpression)(expression);
|
|
33
|
+
if (!result.success || tree.type !== 'SourceDescriptionsExpression') return undefined;
|
|
34
|
+
const {
|
|
35
|
+
sourceName,
|
|
36
|
+
reference
|
|
37
|
+
} = tree;
|
|
38
|
+
return {
|
|
39
|
+
sourceName,
|
|
40
|
+
reference
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
var _default = exports.default = SourceDescriptionsLocatorNormalizer;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parseRuntimeExpression } from '@usearazzo/parser';
|
|
2
|
+
/**
|
|
3
|
+
* Base of the locator normalizers that resolve
|
|
4
|
+
* `$sourceDescriptions.{name}.{reference}` runtime expressions — the OpenAPI
|
|
5
|
+
* operation locator and the Arazzo workflow locator. Owns the registry handle
|
|
6
|
+
* and the one grammar-facing parse, so the expression node's name and field
|
|
7
|
+
* shape are encoded once rather than per subclass.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
class SourceDescriptionsLocatorNormalizer {
|
|
11
|
+
registry;
|
|
12
|
+
constructor(registry) {
|
|
13
|
+
this.registry = registry;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parses a `$sourceDescriptions.{name}.{reference}` expression, or returns
|
|
18
|
+
* `undefined` when the string is not one (whether it fails to parse or
|
|
19
|
+
* parses to another expression kind) — each subclass raises its own error
|
|
20
|
+
* type, message, and context for its own position.
|
|
21
|
+
*/
|
|
22
|
+
parseSourceDescriptionsExpression(expression) {
|
|
23
|
+
const {
|
|
24
|
+
result,
|
|
25
|
+
tree
|
|
26
|
+
} = parseRuntimeExpression(expression);
|
|
27
|
+
if (!result.success || tree.type !== 'SourceDescriptionsExpression') return undefined;
|
|
28
|
+
const {
|
|
29
|
+
sourceName,
|
|
30
|
+
reference
|
|
31
|
+
} = tree;
|
|
32
|
+
return {
|
|
33
|
+
sourceName,
|
|
34
|
+
reference
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export default SourceDescriptionsLocatorNormalizer;
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = exports.STEP_TARGET_FIELDS = void 0;
|
|
8
|
+
var _apidomCore = require("@speclynx/apidom-core");
|
|
9
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
10
|
+
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
11
|
+
var _RuntimeExpressionEvaluator = _interopRequireDefault(require("../expression/RuntimeExpressionEvaluator.cjs"));
|
|
12
|
+
var _CriterionEvaluator = _interopRequireDefault(require("../criterion/CriterionEvaluator.cjs"));
|
|
13
|
+
var _OpenAPIOperationParameterResolver = _interopRequireDefault(require("../resolver/OpenAPIOperationParameterResolver.cjs"));
|
|
14
|
+
var _RequestBodyResolver = _interopRequireDefault(require("../resolver/RequestBodyResolver.cjs"));
|
|
15
|
+
var _OutputResolver = _interopRequireDefault(require("../resolver/OutputResolver.cjs"));
|
|
16
|
+
var _ActionResolver = _interopRequireDefault(require("../action/ActionResolver.cjs"));
|
|
17
|
+
var _OpenAPIOperationLocatorNormalizer = _interopRequireDefault(require("./OpenAPIOperationLocatorNormalizer.cjs"));
|
|
18
|
+
var _ExecutionError = _interopRequireDefault(require("../errors/ExecutionError.cjs"));
|
|
19
|
+
var _RuntimeExpressionError = _interopRequireDefault(require("../errors/RuntimeExpressionError.cjs"));
|
|
20
|
+
var _abort = require("./abort.cjs");
|
|
21
|
+
/**
|
|
22
|
+
* The mutually exclusive fields by which a step names its target. A step may
|
|
23
|
+
* declare at most one. Extend this as the Arazzo Specification adds targets
|
|
24
|
+
* (e.g. `channelPath` in 1.1.0) so every exclusivity check stays complete —
|
|
25
|
+
* both this module's and the workflow executor's, which makes the same check
|
|
26
|
+
* for a step it intercepts before {@link StepExecutor.execute}.
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
const STEP_TARGET_FIELDS = exports.STEP_TARGET_FIELDS = ['operationId', 'operationPath', 'workflowId'];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A read-only source of the runtime expression context for a step.
|
|
33
|
+
*
|
|
34
|
+
* The step executor only reads accumulated run state — it produces the
|
|
35
|
+
* pre-request context (`toContext()`) and the post-request context
|
|
36
|
+
* (`toContext(request, response)`) — and never mutates it, so it depends on
|
|
37
|
+
* this narrow interface rather than the full execution state.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Options for the StepExecutor.
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The outcome of executing a single step.
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Executes a single Arazzo step that invokes an OpenAPI operation.
|
|
53
|
+
*
|
|
54
|
+
* The pipeline locates the step's operation (`operationId` / `operationPath`),
|
|
55
|
+
* resolves the step's parameters and request body against the pre-request
|
|
56
|
+
* context, delegates to {@link OpenAPIOperationExecutor} to run the located
|
|
57
|
+
* operation, then evaluates `successCriteria`, resolves `outputs`, and selects
|
|
58
|
+
* the `onSuccess`/`onFailure` action against the post-request context.
|
|
59
|
+
*
|
|
60
|
+
* It reads run state through a {@link ContextSource} and mutates nothing — the
|
|
61
|
+
* caller records the returned outputs and interprets the returned action. A step
|
|
62
|
+
* targeting a `workflowId` (a sub-workflow) is not an operation step and throws
|
|
63
|
+
* {@link ExecutionError}; running sub-workflows is the workflow executor's
|
|
64
|
+
* concern. A received response with unmet criteria is a normal
|
|
65
|
+
* `successful: false` outcome; malformed input (e.g. an invalid criterion or
|
|
66
|
+
* expression) throws, as does a run the caller has cancelled through an
|
|
67
|
+
* `AbortSignal` in the execute options.
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
class StepExecutor {
|
|
71
|
+
#document;
|
|
72
|
+
#registry;
|
|
73
|
+
#locatorNormalizer;
|
|
74
|
+
#operationExecutor;
|
|
75
|
+
#parameterResolver = new _OpenAPIOperationParameterResolver.default();
|
|
76
|
+
#requestBodyResolver = new _RequestBodyResolver.default();
|
|
77
|
+
#outputResolver = new _OutputResolver.default();
|
|
78
|
+
#criterionEvaluator = new _CriterionEvaluator.default();
|
|
79
|
+
#actionResolver = new _ActionResolver.default();
|
|
80
|
+
constructor(options) {
|
|
81
|
+
this.#document = options.document;
|
|
82
|
+
this.#registry = options.registry;
|
|
83
|
+
this.#locatorNormalizer = new _OpenAPIOperationLocatorNormalizer.default(options.registry);
|
|
84
|
+
this.#operationExecutor = options.operationExecutor;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A step executor bound to the given document, sharing this one's registry
|
|
89
|
+
* and operation executor — this instance itself when the document is
|
|
90
|
+
* already the one it is bound to.
|
|
91
|
+
*
|
|
92
|
+
* A step executor's identity includes the document its steps belong to: a
|
|
93
|
+
* plain `operationId` resolves against *that* document's source
|
|
94
|
+
* descriptions, and `$components` / `$sourceDescriptions` expressions
|
|
95
|
+
* resolve against it too. A cross-document workflow run therefore derives
|
|
96
|
+
* a sibling per foreign document rather than re-pointing this one.
|
|
97
|
+
*/
|
|
98
|
+
forDocument(document) {
|
|
99
|
+
if (document === this.#document) return this;
|
|
100
|
+
return new StepExecutor({
|
|
101
|
+
document,
|
|
102
|
+
registry: this.#registry,
|
|
103
|
+
operationExecutor: this.#operationExecutor
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Executes the step against the given run state, returning its outcome.
|
|
109
|
+
*
|
|
110
|
+
* `executeOptions` is an opaque bag passed through to the operation executor
|
|
111
|
+
* (e.g. a `server` to run the step against, or an `AbortSignal`). The
|
|
112
|
+
* Arazzo-derived options (`operationId`, `parameters`, `requestBody`) always
|
|
113
|
+
* take precedence over it.
|
|
114
|
+
*
|
|
115
|
+
* A `signal` in that bag is honored here and not only at the transport: an
|
|
116
|
+
* already-aborted signal throws `reason: 'aborted'` instead of issuing a
|
|
117
|
+
* request nobody is waiting for, whose wire-level cancellation would surface
|
|
118
|
+
* as a transport {@link ClientError} — a request that failed on its own terms
|
|
119
|
+
* — rather than as the withdrawal it is.
|
|
120
|
+
*/
|
|
121
|
+
async execute(step, state, executeOptions = {}) {
|
|
122
|
+
const stepId = (0, _apidomCore.toValue)(step.stepId);
|
|
123
|
+
|
|
124
|
+
// a step's target fields are mutually exclusive; declaring more than one is
|
|
125
|
+
// malformed and has no defined resolution.
|
|
126
|
+
const declaredTargets = STEP_TARGET_FIELDS.filter(field => (0, _apidomDatamodel.isStringElement)(step[field]));
|
|
127
|
+
if (declaredTargets.length > 1) {
|
|
128
|
+
throw new _ExecutionError.default(`Step "${stepId}" declares more than one of ${STEP_TARGET_FIELDS.join(', ')} (mutually exclusive)`, {
|
|
129
|
+
stepId,
|
|
130
|
+
reason: 'ambiguous-target'
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if ((0, _apidomDatamodel.isStringElement)(step.workflowId)) {
|
|
134
|
+
throw new _ExecutionError.default(`Step "${stepId}" targets a workflowId; use the workflow executor`, {
|
|
135
|
+
stepId,
|
|
136
|
+
reason: 'workflow-step'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// locate which operation the step invokes — reduced to a (document, JSON
|
|
141
|
+
// Pointer) locator that the operation executor extracts, normalizes,
|
|
142
|
+
// assembles into a standalone doc, and runs.
|
|
143
|
+
const locator = await this.#locateOperation(step, stepId);
|
|
144
|
+
|
|
145
|
+
// resolve parameters and request body against the pre-request context.
|
|
146
|
+
// keyed by '{in}.{name}' rather than bare name, so two parameters that
|
|
147
|
+
// legally differ only in their location each reach their own place.
|
|
148
|
+
const scope = {
|
|
149
|
+
stepId
|
|
150
|
+
};
|
|
151
|
+
const preContext = state.toContext();
|
|
152
|
+
const parameters = this.#parameterResolver.resolve(step.parameters, expression => this.#evaluate(preContext, expression), scope);
|
|
153
|
+
const requestBody = this.#requestBodyResolver.resolve(step.requestBody, expression => this.#evaluate(preContext, expression), scope);
|
|
154
|
+
|
|
155
|
+
// checked here rather than on entry: locating the operation and resolving
|
|
156
|
+
// the expressions above are awaited, so a run cancelled during them would
|
|
157
|
+
// still reach this line — and this is the last moment before the step has a
|
|
158
|
+
// live side effect. The signal stays in the bag, so a request already in
|
|
159
|
+
// flight is aborted by the transport as before.
|
|
160
|
+
const signal = (0, _abort.readAbortSignal)(executeOptions);
|
|
161
|
+
(0, _abort.throwIfAborted)(signal, {
|
|
162
|
+
stepId
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// the Arazzo-derived parameters and request body are spread after the opaque
|
|
166
|
+
// executeOptions bag so they take precedence over it. the operation executor
|
|
167
|
+
// then forces the operation target on top of this before executing.
|
|
168
|
+
let response;
|
|
169
|
+
try {
|
|
170
|
+
response = await this.#operationExecutor.execute(locator, {
|
|
171
|
+
...executeOptions,
|
|
172
|
+
parameters,
|
|
173
|
+
...this.#requestBodyOptions(requestBody)
|
|
174
|
+
});
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// a transport that honors the signal rejects the request it was told to
|
|
177
|
+
// drop, which the operation executor wraps as a `ClientError` — the shape
|
|
178
|
+
// reserved for a request that failed on its own terms. Cancelled while
|
|
179
|
+
// cancelled, it did not: reporting it as the abort keeps one error shape
|
|
180
|
+
// whether the run was withdrawn between two steps or mid-flight, and keeps
|
|
181
|
+
// the whole feature from reading differently per transport.
|
|
182
|
+
(0, _abort.throwIfAborted)(signal, {
|
|
183
|
+
stepId
|
|
184
|
+
});
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// evaluate criteria, outputs, and the next action against the post-request
|
|
189
|
+
// context (with $request / $url / $method and $response / $statusCode).
|
|
190
|
+
const postContext = state.toContext(this.#requestContext(response), this.#responseContext(response));
|
|
191
|
+
const successful = this.evaluateCriteria(step, postContext);
|
|
192
|
+
const outputs = this.#outputResolver.resolve(step.outputs, expression => this.#evaluate(postContext, expression), scope);
|
|
193
|
+
const matchedActions = this.selectActions(step, successful, postContext);
|
|
194
|
+
return {
|
|
195
|
+
stepId,
|
|
196
|
+
response,
|
|
197
|
+
successful,
|
|
198
|
+
outputs,
|
|
199
|
+
action: matchedActions[0],
|
|
200
|
+
matchedActions
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The request-body execute options for the step, as a typed partial to spread
|
|
206
|
+
* into the client options.
|
|
207
|
+
*
|
|
208
|
+
* Empty when the step has no request body; the `requestContentType` key is
|
|
209
|
+
* present only when a content type was resolved, so it is omitted rather than
|
|
210
|
+
* spread as `undefined`.
|
|
211
|
+
*/
|
|
212
|
+
#requestBodyOptions(requestBody) {
|
|
213
|
+
if (requestBody === undefined) return {};
|
|
214
|
+
return requestBody.contentType === undefined ? {
|
|
215
|
+
requestBody: requestBody.payload
|
|
216
|
+
} : {
|
|
217
|
+
requestBody: requestBody.payload,
|
|
218
|
+
requestContentType: requestBody.contentType
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async #locateOperation(step, stepId) {
|
|
222
|
+
if ((0, _apidomDatamodel.isStringElement)(step.operationId)) {
|
|
223
|
+
return this.#locatorNormalizer.normalizeOperationId((0, _apidomCore.toValue)(step.operationId), this.#document);
|
|
224
|
+
}
|
|
225
|
+
if ((0, _apidomDatamodel.isStringElement)(step.operationPath)) {
|
|
226
|
+
return this.#locatorNormalizer.normalizeOperationPath((0, _apidomCore.toValue)(step.operationPath), this.#document);
|
|
227
|
+
}
|
|
228
|
+
throw new _ExecutionError.default(`Step "${stepId}" has no operationId or operationPath`, {
|
|
229
|
+
stepId,
|
|
230
|
+
reason: 'missing-operation'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Evaluates the step's `successCriteria`; all criteria must pass. A step with
|
|
236
|
+
* no criteria succeeds on a received response.
|
|
237
|
+
*
|
|
238
|
+
* Public for the same reason as {@link StepExecutor.selectActions}: a step
|
|
239
|
+
* targeting a `workflowId` is run by the workflow executor and never reaches
|
|
240
|
+
* {@link StepExecutor.execute}, but its criteria are still the author's
|
|
241
|
+
* assertion about the step and must be evaluated somewhere.
|
|
242
|
+
*/
|
|
243
|
+
evaluateCriteria(step, context) {
|
|
244
|
+
const criteria = step.successCriteria;
|
|
245
|
+
// no (or non-array) successCriteria: the step succeeds on a received response.
|
|
246
|
+
if (!(0, _apidomDatamodel.isArrayElement)(criteria)) return true;
|
|
247
|
+
for (const criterion of criteria) {
|
|
248
|
+
if (!(0, _apidomNsArazzo.isCriterionElement)(criterion)) continue;
|
|
249
|
+
const met = this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression));
|
|
250
|
+
if (!met) return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Selects every matching `onSuccess` / `onFailure` action for the outcome, in
|
|
257
|
+
* list order, gating each action's criteria against the context.
|
|
258
|
+
*
|
|
259
|
+
* The step's own list is the whole story here: a step that declares none of
|
|
260
|
+
* its own already carries the workflow-level defaults, inherited by
|
|
261
|
+
* {@link ArazzoWorkflowNormalizer} — the same place a step's inherited
|
|
262
|
+
* `parameters` come from — so there is no fallback left to apply at selection
|
|
263
|
+
* time.
|
|
264
|
+
*
|
|
265
|
+
* The full matching list (not just the first) is returned so the caller can
|
|
266
|
+
* honor "retryLimit exhausted prior to subsequent failure actions"; the common
|
|
267
|
+
* caller simply takes the first.
|
|
268
|
+
*
|
|
269
|
+
* Public because selecting a step's actions is meaningful beyond running one:
|
|
270
|
+
* the workflow executor selects them for a step targeting a `workflowId`, which
|
|
271
|
+
* it runs itself and which therefore never reaches
|
|
272
|
+
* {@link StepExecutor.execute}. Both callers reach the criteria evaluator and
|
|
273
|
+
* the expression context through this one method rather than wiring their own.
|
|
274
|
+
*/
|
|
275
|
+
selectActions(step, successful, context) {
|
|
276
|
+
const isCriterionMet = criterion => {
|
|
277
|
+
try {
|
|
278
|
+
return this.#criterionEvaluator.evaluate(criterion, expression => this.#evaluate(context, expression));
|
|
279
|
+
} catch (error) {
|
|
280
|
+
// an action's criteria decide whether it applies to this outcome, and
|
|
281
|
+
// actions are shared — inherited from the workflow, reused from
|
|
282
|
+
// components. A condition on an HTTP exchange cannot apply to a step
|
|
283
|
+
// that calls a workflow, so the action does not match; the run does not
|
|
284
|
+
// abort over a default written for the operation steps.
|
|
285
|
+
if (error instanceof _RuntimeExpressionError.default && error.reason === 'unavailable-expression') {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
const scope = {
|
|
292
|
+
stepId: (0, _apidomCore.toValue)(step.stepId)
|
|
293
|
+
};
|
|
294
|
+
return successful ? this.#actionResolver.resolveOnSuccess(step.onSuccess, isCriterionMet, scope) : this.#actionResolver.resolveOnFailure(step.onFailure, isCriterionMet, scope);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Resolves a runtime expression leniently against a context, forwarding
|
|
299
|
+
* `$components` / `$sourceDescriptions` resolution to the document and
|
|
300
|
+
* registry.
|
|
301
|
+
*/
|
|
302
|
+
#evaluate(context, expression) {
|
|
303
|
+
return new _RuntimeExpressionEvaluator.default(context, {
|
|
304
|
+
strict: false,
|
|
305
|
+
document: this.#document,
|
|
306
|
+
registry: this.#registry
|
|
307
|
+
}).evaluate(expression);
|
|
308
|
+
}
|
|
309
|
+
#responseContext(response) {
|
|
310
|
+
return {
|
|
311
|
+
statusCode: response.status,
|
|
312
|
+
header: response.headers,
|
|
313
|
+
body: response.body
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The request context (`$url`, `$method`, `$request.header.*`,
|
|
319
|
+
* `$request.body`) from the request the client actually sent, or `undefined`
|
|
320
|
+
* when the client did not report one.
|
|
321
|
+
*/
|
|
322
|
+
#requestContext(response) {
|
|
323
|
+
const request = response.request;
|
|
324
|
+
if (request === undefined) return undefined;
|
|
325
|
+
return {
|
|
326
|
+
url: request.url,
|
|
327
|
+
method: request.method,
|
|
328
|
+
header: request.headers,
|
|
329
|
+
body: request.body
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
var _default = exports.default = StepExecutor;
|