@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usearazzo/runner",
3
- "version": "1.0.1-alpha.21",
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.0.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.21",
77
- "@usearazzo/resolver": "1.0.1-alpha.21",
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": "d342b02adbd11f5362aa4850b2417521e59448ce"
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. Whether the resolved
56
- * document *defines* the workflow is deliberately left to the caller, which
57
- * owns the timing of that check (eager for a `dependsOn` list validated
58
- * before any prerequisite runs, lazy for a sub-workflow step resolved per
59
- * attempt).
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
- return this.#resolve(this.#parseReference(reference, context), document, context);
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. Whether the resolved
46
- * document *defines* the workflow is deliberately left to the caller, which
47
- * owns the timing of that check (eager for a `dependsOn` list validated
48
- * before any prerequisite runs, lazy for a sub-workflow step resolved per
49
- * attempt).
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
- return this.#resolve(this.#parseReference(reference, context), document, context);
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 _ExtractionError = _interopRequireDefault(require("../errors/ExtractionError.cjs"));
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 resolved against the first source description that has it.
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 resolved against the
53
- * first source description that has it.
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. It fails only when the URL does
76
- * not resolve to an OpenAPI source or the JSON Pointer is invalid.
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 _ExtractionError.default(`operationPath "${operationPath}" does not resolve to a source description URL`, {
88
- pointer: operationPath,
89
- uri: document.uri
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 _ExtractionError.default(`operationPath "${operationPath}" has an invalid JSON Pointer`, {
95
- pointer: operationPath,
96
- uri: document.uri
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 _ExtractionError.default(`operationPath "${operationPath}" does not reference an OpenAPI document`, {
102
- pointer: operationPath,
103
- uri: documentURI
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 _ExtractionError.default(`Unsupported operationId expression "${expression}"`, {
120
- operationId: expression
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
- } = this.#parseSourceDescriptionsExpression(expression);
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 _ExtractionError.default(`Operation "${operationId}" not found in source description "${sourceName}"`, {
134
- operationId,
135
- uri: openapiDocument.uri
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 _ExtractionError.default(`Operation "${operationId}" not found in source description at "${source.uri}"`, {
163
- operationId,
164
- uri: source.uri
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
- throw new _ExtractionError.default(`Operation "${operationId}" has no OpenAPI source description`, {
173
- operationId,
174
- uri: document.uri
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 _ExtractionError.default(`Source description "${sourceName}" not found`, {
185
- uri: document.uri
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 _ExtractionError.default(`Source description "${sourceName}" is not an OpenAPI document`, {
191
- uri
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 ExtractionError from "../errors/ExtractionError.mjs";
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 resolved against the first source description that has it.
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 resolved against the
45
- * first source description that has it.
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. It fails only when the URL does
68
- * not resolve to an OpenAPI source or the JSON Pointer is invalid.
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 ExtractionError(`operationPath "${operationPath}" does not resolve to a source description URL`, {
80
- pointer: operationPath,
81
- uri: document.uri
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 ExtractionError(`operationPath "${operationPath}" has an invalid JSON Pointer`, {
87
- pointer: operationPath,
88
- uri: document.uri
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 ExtractionError(`operationPath "${operationPath}" does not reference an OpenAPI document`, {
94
- pointer: operationPath,
95
- uri: documentURI
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 ExtractionError(`Unsupported operationId expression "${expression}"`, {
112
- operationId: expression
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
- } = this.#parseSourceDescriptionsExpression(expression);
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 ExtractionError(`Operation "${operationId}" not found in source description "${sourceName}"`, {
126
- operationId,
127
- uri: openapiDocument.uri
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 ExtractionError(`Operation "${operationId}" not found in source description at "${source.uri}"`, {
155
- operationId,
156
- uri: source.uri
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
- throw new ExtractionError(`Operation "${operationId}" has no OpenAPI source description`, {
165
- operationId,
166
- uri: document.uri
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 ExtractionError(`Source description "${sourceName}" not found`, {
177
- uri: document.uri
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 ExtractionError(`Source description "${sourceName}" is not an OpenAPI document`, {
183
- uri
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;