@usearazzo/runner 1.0.1-alpha.21 → 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 +12 -0
- package/dist/arazzo-runner.browser.js +2733 -446
- package/dist/arazzo-runner.browser.min.js +1 -1
- package/package.json +5 -5
- package/src/executor/ArazzoWorkflowLocatorNormalizer.cjs +18 -7
- package/src/executor/ArazzoWorkflowLocatorNormalizer.mjs +18 -7
- package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +79 -47
- package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +80 -48
- package/src/executor/StepExecutor.cjs +45 -25
- package/src/executor/StepExecutor.mjs +45 -25
- package/src/executor/StepRetryRunner.cjs +0 -10
- package/src/executor/StepRetryRunner.mjs +0 -9
- package/src/executor/WorkflowExecutor.cjs +11 -22
- package/src/executor/WorkflowExecutor.mjs +11 -22
- package/src/vendor/swagger-client.cjs +1 -1
- package/src/vendor/swagger-client.mjs +1 -1
- package/types/runner.d.ts +49 -21
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",
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
"@speclynx/apidom-traverse": "^6.1.2",
|
|
70
70
|
"@swaggerexpert/arazzo-criterion": "^1.0.1",
|
|
71
71
|
"@swaggerexpert/arazzo-runtime-expression": "^3.3.0",
|
|
72
|
-
"@swaggerexpert/cookie": "^2.
|
|
72
|
+
"@swaggerexpert/cookie": "^2.1.1",
|
|
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
|
}
|
|
@@ -52,11 +52,12 @@ var _SourceDescriptionsLocatorNormalizer = _interopRequireDefault(require("./Sou
|
|
|
52
52
|
* The `type` declared on a source description is not trusted (it may be
|
|
53
53
|
* absent or wrong); a source is classified by what it actually parses to,
|
|
54
54
|
* the same convention the OpenAPI operation locator follows. Source
|
|
55
|
-
* documents are acquired from the registry on demand.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
55
|
+
* documents are acquired from the registry on demand. A reference to a
|
|
56
|
+
* workflow the resolved document does not define is rejected here, so every
|
|
57
|
+
* returned locator names an existing workflow; a caller controls when that
|
|
58
|
+
* check happens by when it normalizes (eagerly for a `dependsOn` list
|
|
59
|
+
* validated before any prerequisite runs, lazily for a sub-workflow step
|
|
60
|
+
* resolved on its first attempt).
|
|
60
61
|
* @public
|
|
61
62
|
*/
|
|
62
63
|
class ArazzoWorkflowLocatorNormalizer extends _SourceDescriptionsLocatorNormalizer.default {
|
|
@@ -64,10 +65,20 @@ class ArazzoWorkflowLocatorNormalizer extends _SourceDescriptionsLocatorNormaliz
|
|
|
64
65
|
* Resolves a reference — a plain workflowId or a
|
|
65
66
|
* `$sourceDescriptions.{name}.{workflowId}` expression, as written — to the
|
|
66
67
|
* canonical locator: the Arazzo document that owns the target workflow,
|
|
67
|
-
* and the bare id within it.
|
|
68
|
+
* and the bare id within it. Rejects a workflow that document does not
|
|
69
|
+
* define as `workflow-not-found`, attributed to the referencing context
|
|
70
|
+
* like every other error raised here.
|
|
68
71
|
*/
|
|
69
72
|
async normalize(reference, document, context) {
|
|
70
|
-
|
|
73
|
+
const locator = await this.#resolve(this.#parseReference(reference, context), document, context);
|
|
74
|
+
if (!locator.document.workflowIndex.has(locator.workflowId)) {
|
|
75
|
+
throw new _ExecutionError.default(`workflow reference "${reference}" in workflow "${context.workflowId}" names workflow "${locator.workflowId}", which the Arazzo document at "${locator.document.uri}" does not define`, {
|
|
76
|
+
workflowId: context.workflowId,
|
|
77
|
+
stepId: context.stepId,
|
|
78
|
+
reason: 'workflow-not-found'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return locator;
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
/**
|
|
@@ -42,11 +42,12 @@ import SourceDescriptionsLocatorNormalizer from "./SourceDescriptionsLocatorNorm
|
|
|
42
42
|
* The `type` declared on a source description is not trusted (it may be
|
|
43
43
|
* absent or wrong); a source is classified by what it actually parses to,
|
|
44
44
|
* the same convention the OpenAPI operation locator follows. Source
|
|
45
|
-
* documents are acquired from the registry on demand.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
45
|
+
* documents are acquired from the registry on demand. A reference to a
|
|
46
|
+
* workflow the resolved document does not define is rejected here, so every
|
|
47
|
+
* returned locator names an existing workflow; a caller controls when that
|
|
48
|
+
* check happens by when it normalizes (eagerly for a `dependsOn` list
|
|
49
|
+
* validated before any prerequisite runs, lazily for a sub-workflow step
|
|
50
|
+
* resolved on its first attempt).
|
|
50
51
|
* @public
|
|
51
52
|
*/
|
|
52
53
|
class ArazzoWorkflowLocatorNormalizer extends SourceDescriptionsLocatorNormalizer {
|
|
@@ -54,10 +55,20 @@ class ArazzoWorkflowLocatorNormalizer extends SourceDescriptionsLocatorNormalize
|
|
|
54
55
|
* Resolves a reference — a plain workflowId or a
|
|
55
56
|
* `$sourceDescriptions.{name}.{workflowId}` expression, as written — to the
|
|
56
57
|
* canonical locator: the Arazzo document that owns the target workflow,
|
|
57
|
-
* and the bare id within it.
|
|
58
|
+
* and the bare id within it. Rejects a workflow that document does not
|
|
59
|
+
* define as `workflow-not-found`, attributed to the referencing context
|
|
60
|
+
* like every other error raised here.
|
|
58
61
|
*/
|
|
59
62
|
async normalize(reference, document, context) {
|
|
60
|
-
|
|
63
|
+
const locator = await this.#resolve(this.#parseReference(reference, context), document, context);
|
|
64
|
+
if (!locator.document.workflowIndex.has(locator.workflowId)) {
|
|
65
|
+
throw new ExecutionError(`workflow reference "${reference}" in workflow "${context.workflowId}" names workflow "${locator.workflowId}", which the Arazzo document at "${locator.document.uri}" does not define`, {
|
|
66
|
+
workflowId: context.workflowId,
|
|
67
|
+
stepId: context.stepId,
|
|
68
|
+
reason: 'workflow-not-found'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return locator;
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
/**
|
|
@@ -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;
|