@usearazzo/runner 0.0.0-stage → 1.0.1-alpha.9

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.
Files changed (152) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +20 -0
  4. package/README.md +777 -2
  5. package/dist/arazzo-runner.browser.js +111247 -0
  6. package/dist/arazzo-runner.browser.min.js +1 -0
  7. package/package.json +90 -4
  8. package/src/action/ActionResolver.cjs +160 -0
  9. package/src/action/ActionResolver.mjs +150 -0
  10. package/src/assembler/OpenAPI2DocumentAssembler.cjs +111 -0
  11. package/src/assembler/OpenAPI2DocumentAssembler.mjs +104 -0
  12. package/src/assembler/OpenAPI30DocumentAssembler.cjs +110 -0
  13. package/src/assembler/OpenAPI30DocumentAssembler.mjs +103 -0
  14. package/src/assembler/OpenAPI31DocumentAssembler.cjs +111 -0
  15. package/src/assembler/OpenAPI31DocumentAssembler.mjs +104 -0
  16. package/src/assembler/OpenAPIDocumentAssembler.cjs +58 -0
  17. package/src/assembler/OpenAPIDocumentAssembler.mjs +50 -0
  18. package/src/client/HTTPClient.cjs +5 -0
  19. package/src/client/HTTPClient.mjs +1 -0
  20. package/src/client/HTTPClientFetch.cjs +16 -0
  21. package/src/client/HTTPClientFetch.mjs +10 -0
  22. package/src/client/OpenAPIOperationRequest.cjs +5 -0
  23. package/src/client/OpenAPIOperationRequest.mjs +1 -0
  24. package/src/client/OpenAPIOperationResponse.cjs +42 -0
  25. package/src/client/OpenAPIOperationResponse.mjs +36 -0
  26. package/src/client/OpenAPIOperationResponseNormalizer.cjs +48 -0
  27. package/src/client/OpenAPIOperationResponseNormalizer.mjs +40 -0
  28. package/src/client/ParameterDelivery.cjs +119 -0
  29. package/src/client/ParameterDelivery.mjs +113 -0
  30. package/src/constants.cjs +9 -0
  31. package/src/constants.mjs +3 -0
  32. package/src/criterion/CriterionEvaluator.cjs +205 -0
  33. package/src/criterion/CriterionEvaluator.mjs +196 -0
  34. package/src/criterion/JSONPathCriterionEvaluator.cjs +47 -0
  35. package/src/criterion/JSONPathCriterionEvaluator.mjs +40 -0
  36. package/src/criterion/RegexCriterionEvaluator.cjs +67 -0
  37. package/src/criterion/RegexCriterionEvaluator.mjs +60 -0
  38. package/src/criterion/SimpleCriterionEvaluator.cjs +50 -0
  39. package/src/criterion/SimpleCriterionEvaluator.mjs +43 -0
  40. package/src/criterion/XPathCriterionEvaluator.cjs +117 -0
  41. package/src/criterion/XPathCriterionEvaluator.mjs +109 -0
  42. package/src/document/APIDocument.cjs +35 -0
  43. package/src/document/APIDocument.mjs +30 -0
  44. package/src/document/ArazzoDocument.cjs +81 -0
  45. package/src/document/ArazzoDocument.mjs +74 -0
  46. package/src/document/ArazzoSourceDescriptionIndex.cjs +26 -0
  47. package/src/document/ArazzoSourceDescriptionIndex.mjs +20 -0
  48. package/src/document/ArazzoStepIndex.cjs +25 -0
  49. package/src/document/ArazzoStepIndex.mjs +19 -0
  50. package/src/document/ArazzoWorkflowIndex.cjs +17 -0
  51. package/src/document/ArazzoWorkflowIndex.mjs +11 -0
  52. package/src/document/OpenAPIDocument.cjs +28 -0
  53. package/src/document/OpenAPIDocument.mjs +21 -0
  54. package/src/document/OpenAPIOperationIndex.cjs +24 -0
  55. package/src/document/OpenAPIOperationIndex.mjs +18 -0
  56. package/src/document/ParameterIdentity.cjs +93 -0
  57. package/src/document/ParameterIdentity.mjs +88 -0
  58. package/src/document/openapi-types.cjs +5 -0
  59. package/src/document/openapi-types.mjs +1 -0
  60. package/src/errors/ArazzoRunnerError.cjs +10 -0
  61. package/src/errors/ArazzoRunnerError.mjs +5 -0
  62. package/src/errors/AssemblerError.cjs +11 -0
  63. package/src/errors/AssemblerError.mjs +4 -0
  64. package/src/errors/ClientError.cjs +11 -0
  65. package/src/errors/ClientError.mjs +4 -0
  66. package/src/errors/CriterionError.cjs +11 -0
  67. package/src/errors/CriterionError.mjs +4 -0
  68. package/src/errors/ExecutionError.cjs +11 -0
  69. package/src/errors/ExecutionError.mjs +4 -0
  70. package/src/errors/ExtractionError.cjs +11 -0
  71. package/src/errors/ExtractionError.mjs +4 -0
  72. package/src/errors/InvalidEntryDocumentError.cjs +11 -0
  73. package/src/errors/InvalidEntryDocumentError.mjs +4 -0
  74. package/src/errors/NormalizationError.cjs +11 -0
  75. package/src/errors/NormalizationError.mjs +4 -0
  76. package/src/errors/ResolverError.cjs +11 -0
  77. package/src/errors/ResolverError.mjs +4 -0
  78. package/src/errors/RuntimeExpressionError.cjs +11 -0
  79. package/src/errors/RuntimeExpressionError.mjs +4 -0
  80. package/src/errors/UnmatchedProviderError.cjs +11 -0
  81. package/src/errors/UnmatchedProviderError.mjs +4 -0
  82. package/src/executor/ArazzoWorkflowLocatorNormalizer.cjs +147 -0
  83. package/src/executor/ArazzoWorkflowLocatorNormalizer.mjs +137 -0
  84. package/src/executor/OpenAPIOperationExecutor.cjs +457 -0
  85. package/src/executor/OpenAPIOperationExecutor.mjs +446 -0
  86. package/src/executor/OpenAPIOperationLocatorNormalizer.cjs +197 -0
  87. package/src/executor/OpenAPIOperationLocatorNormalizer.mjs +189 -0
  88. package/src/executor/SourceDescriptionsLocatorNormalizer.cjs +44 -0
  89. package/src/executor/SourceDescriptionsLocatorNormalizer.mjs +38 -0
  90. package/src/executor/StepExecutor.cjs +333 -0
  91. package/src/executor/StepExecutor.mjs +326 -0
  92. package/src/executor/StepRetryRunner.cjs +226 -0
  93. package/src/executor/StepRetryRunner.mjs +215 -0
  94. package/src/executor/StepTransitionInterpreter.cjs +159 -0
  95. package/src/executor/StepTransitionInterpreter.mjs +151 -0
  96. package/src/executor/WorkflowCallStack.cjs +116 -0
  97. package/src/executor/WorkflowCallStack.mjs +106 -0
  98. package/src/executor/WorkflowExecutor.cjs +1091 -0
  99. package/src/executor/WorkflowExecutor.mjs +1077 -0
  100. package/src/executor/abort.cjs +70 -0
  101. package/src/executor/abort.mjs +60 -0
  102. package/src/expression/RuntimeExpressionContext.cjs +5 -0
  103. package/src/expression/RuntimeExpressionContext.mjs +1 -0
  104. package/src/expression/RuntimeExpressionEvaluator.cjs +337 -0
  105. package/src/expression/RuntimeExpressionEvaluator.mjs +328 -0
  106. package/src/extractor/ArazzoStepExtractor.cjs +48 -0
  107. package/src/extractor/ArazzoStepExtractor.mjs +41 -0
  108. package/src/extractor/ArazzoWorkflowExtractor.cjs +50 -0
  109. package/src/extractor/ArazzoWorkflowExtractor.mjs +43 -0
  110. package/src/extractor/OpenAPIOperationExtractor.cjs +84 -0
  111. package/src/extractor/OpenAPIOperationExtractor.mjs +77 -0
  112. package/src/index.cjs +419 -0
  113. package/src/index.mjs +73 -0
  114. package/src/normalizer/ArazzoStepNormalizer.cjs +23 -0
  115. package/src/normalizer/ArazzoStepNormalizer.mjs +17 -0
  116. package/src/normalizer/ArazzoWorkflowNormalizer.cjs +214 -0
  117. package/src/normalizer/ArazzoWorkflowNormalizer.mjs +206 -0
  118. package/src/normalizer/OpenAPI2OperationNormalizer.cjs +128 -0
  119. package/src/normalizer/OpenAPI2OperationNormalizer.mjs +120 -0
  120. package/src/normalizer/OpenAPI30OperationNormalizer.cjs +129 -0
  121. package/src/normalizer/OpenAPI30OperationNormalizer.mjs +121 -0
  122. package/src/normalizer/OpenAPI31OperationNormalizer.cjs +131 -0
  123. package/src/normalizer/OpenAPI31OperationNormalizer.mjs +123 -0
  124. package/src/normalizer/OpenAPIOperationNormalizer.cjs +58 -0
  125. package/src/normalizer/OpenAPIOperationNormalizer.mjs +50 -0
  126. package/src/registry/DocumentRegistry.cjs +160 -0
  127. package/src/registry/DocumentRegistry.mjs +151 -0
  128. package/src/registry/providers/ArazzoDocumentRegistryProvider.cjs +154 -0
  129. package/src/registry/providers/ArazzoDocumentRegistryProvider.mjs +146 -0
  130. package/src/registry/providers/DocumentRegistryProvider.cjs +31 -0
  131. package/src/registry/providers/DocumentRegistryProvider.mjs +25 -0
  132. package/src/registry/providers/OpenAPIDocumentRegistryProvider.cjs +122 -0
  133. package/src/registry/providers/OpenAPIDocumentRegistryProvider.mjs +114 -0
  134. package/src/resolver/ArazzoValueResolver.cjs +42 -0
  135. package/src/resolver/ArazzoValueResolver.mjs +37 -0
  136. package/src/resolver/OpenAPIOperationParameterResolver.cjs +117 -0
  137. package/src/resolver/OpenAPIOperationParameterResolver.mjs +110 -0
  138. package/src/resolver/OutputResolver.cjs +71 -0
  139. package/src/resolver/OutputResolver.mjs +64 -0
  140. package/src/resolver/RequestBodyResolver.cjs +227 -0
  141. package/src/resolver/RequestBodyResolver.mjs +220 -0
  142. package/src/resolver/ResolverScope.cjs +5 -0
  143. package/src/resolver/ResolverScope.mjs +1 -0
  144. package/src/resolver/StepParameterResolver.cjs +22 -0
  145. package/src/resolver/StepParameterResolver.mjs +15 -0
  146. package/src/resolver/WorkflowParameterResolver.cjs +73 -0
  147. package/src/resolver/WorkflowParameterResolver.mjs +66 -0
  148. package/src/state/WorkflowExecutionState.cjs +99 -0
  149. package/src/state/WorkflowExecutionState.mjs +93 -0
  150. package/src/vendor/swagger-client.cjs +1 -0
  151. package/src/vendor/swagger-client.mjs +1 -0
  152. package/types/runner.d.ts +2341 -0
@@ -0,0 +1,446 @@
1
+ import { toValue } from '@speclynx/apidom-core';
2
+ import { isArrayElement, isObjectElement } from '@speclynx/apidom-datamodel';
3
+ import { url as urlUtils } from '@speclynx/apidom-reference/configuration/empty';
4
+ import { isSwaggerElement, isParameterElement } from '@speclynx/apidom-ns-openapi-2';
5
+ import { isPlainObject } from 'ramda-adjunct';
6
+
7
+ // @ts-expect-error vendored swagger-client bundle has no type declarations
8
+ import { buildRequest, idFromPathMethodLegacy } from '../vendor/swagger-client.mjs';
9
+ import ParameterDelivery from "../client/ParameterDelivery.mjs";
10
+ import httpClientFetch from "../client/HTTPClientFetch.mjs";
11
+ import OpenAPIOperationResponseNormalizer from "../client/OpenAPIOperationResponseNormalizer.mjs";
12
+ import OpenAPIOperationExtractor from "../extractor/OpenAPIOperationExtractor.mjs";
13
+ import OpenAPIOperationNormalizer from "../normalizer/OpenAPIOperationNormalizer.mjs";
14
+ import OpenAPIDocumentAssembler from "../assembler/OpenAPIDocumentAssembler.mjs";
15
+ import ClientError from "../errors/ClientError.mjs";
16
+ /**
17
+ * Decorates the built request before it is handed to the {@link HTTPClient}.
18
+ *
19
+ * Receives the finalized {@link OpenAPIOperationRequest} and may mutate it in
20
+ * place or return a replacement; returning nothing keeps the received request.
21
+ * May be synchronous or asynchronous.
22
+ * @public
23
+ */
24
+ /**
25
+ * Options for executing an OpenAPI operation.
26
+ *
27
+ * All fields are forwarded to the vendored swagger-client `buildRequest`
28
+ * (which owns parameter serialization, security application, and server URL
29
+ * resolution), except `requestInterceptor`, which the executor runs between
30
+ * building the request and sending it.
31
+ *
32
+ * Typed here are the options the runner itself drives and the ones callers
33
+ * routinely reach for. The rest of `buildRequest`'s surface (`securities`,
34
+ * `scheme`, `parameterBuilders`, `attachContentTypeForEmptyPayload`, …) is
35
+ * deliberately left untyped and passes through the bag's open tail unchanged —
36
+ * see {@link OpenAPIOperationExecuteOptionsBag}. Authentication in particular
37
+ * is not modeled: Arazzo is auth-agnostic, so credentials are applied with a
38
+ * {@link RequestInterceptor}.
39
+ * @public
40
+ */
41
+ /**
42
+ * The execute options forwarded to {@link OpenAPIOperationExecutor.execute}.
43
+ *
44
+ * The {@link OpenAPIOperationExecuteOptions} fields are surfaced for
45
+ * type-checked callers, while the open `Record` tail keeps the bag opaque so
46
+ * the rest of `buildRequest`'s surface (`scheme`, `parameterBuilders`,
47
+ * `attachContentTypeForEmptyPayload`, …) passes through untouched.
48
+ *
49
+ * Nothing routine belongs in the tail. Credentials go through a
50
+ * {@link RequestInterceptor} and `server` covers every case that once needed a
51
+ * `contextUrl`, so reaching for either of those here means an edge case: tests
52
+ * standing in for a document fetched over HTTP, or credentials applied from the
53
+ * document's own security schemes.
54
+ *
55
+ * Two kinds of entry are inert rather than honored, since the executor owns
56
+ * what they used to control. The operation target always comes from the
57
+ * locator, so `operationId`/`operationPath` have no effect; and the options
58
+ * only swagger-client's `execute()` ever read — `userFetch` and
59
+ * `responseInterceptor`, which the runner replaces with `httpClient` and
60
+ * `responseNormalizer` — reach `buildRequest` but nothing acts on them.
61
+ * @public
62
+ */
63
+ /**
64
+ * Options for the OpenAPIOperationExecutor.
65
+ * @public
66
+ */
67
+ /**
68
+ * Executes a single OpenAPI operation named by a canonical
69
+ * {@link OpenAPIOperationLocator}.
70
+ *
71
+ * The pipeline extracts the operation from its owning document, normalizes it,
72
+ * assembles a minimal standalone OpenAPI document containing just that
73
+ * operation, builds the HTTP request with the vendored swagger-client
74
+ * `buildRequest` (parameter serialization, securities, server resolution),
75
+ * sends it through the pluggable {@link HTTPClient}, and normalizes the raw
76
+ * `Response` into the runner's response model. A non-2xx status is a valid
77
+ * outcome (judged by a step's `successCriteria`) and is returned, never
78
+ * thrown; a transport failure is wrapped as a {@link ClientError}.
79
+ *
80
+ * The executor is Arazzo-agnostic: it neither locates the operation (that is
81
+ * the operation locator normalizer's concern) nor resolves parameters or
82
+ * request bodies from runtime expressions (the step executor's concern). It
83
+ * receives an already-resolved locator and an opaque bag of execute options,
84
+ * and returns the normalized operation response carrying the request that was
85
+ * actually sent, so `$url` / `$method` / `$request.*` can be evaluated after
86
+ * execution.
87
+ * @public
88
+ */
89
+ class OpenAPIOperationExecutor {
90
+ #httpClient;
91
+ #requestInterceptor;
92
+ #responseNormalizer;
93
+ #operationExtractor;
94
+ #operationNormalizer;
95
+ #documentAssembler;
96
+ constructor(options = {}) {
97
+ this.#httpClient = options.httpClient ?? httpClientFetch;
98
+ this.#requestInterceptor = options.requestInterceptor;
99
+ this.#responseNormalizer = options.responseNormalizer ?? new OpenAPIOperationResponseNormalizer();
100
+ this.#operationExtractor = options.operationExtractor ?? new OpenAPIOperationExtractor();
101
+ this.#operationNormalizer = options.operationNormalizer ?? new OpenAPIOperationNormalizer();
102
+ this.#documentAssembler = options.documentAssembler ?? new OpenAPIDocumentAssembler();
103
+ }
104
+
105
+ /**
106
+ * Executes the located operation, returning the normalized operation
107
+ * response.
108
+ *
109
+ * `executeOptions` is an opaque bag of `buildRequest` options (e.g. resolved
110
+ * `parameters`, a `requestBody` and its `requestContentType`, or a `server`).
111
+ * The operation target always takes precedence over it: the
112
+ * locator's JSON Pointer selects the operation in the assembled document, so
113
+ * `operationId`/`operationPath` entries in the bag cannot hijack the
114
+ * operation.
115
+ */
116
+ async execute(locator, executeOptions = {}) {
117
+ // extraction validates the locator first: a pointer naming anything but an
118
+ // operation would otherwise crash inside normalization
119
+ const operation = this.#operationExtractor.extractByPointer(locator.document, locator.jsonPointer);
120
+ // the legacy id (`{method}-{path}`) matches regardless of an explicit
121
+ // `operationId` in the assembled document
122
+ const operationId = idFromPathMethodLegacy(toValue(operation.meta.get('path')), toValue(operation.meta.get('http-method')));
123
+ const normalizedOperation = await this.#operationNormalizer.normalize(operation, locator.document);
124
+ const assembled = this.#documentAssembler.assemble(normalizedOperation, locator.document);
125
+
126
+ // the interceptor is consumed here, and the target always comes from the
127
+ // locator, so both operation keys are dropped rather than rest
128
+ const {
129
+ requestInterceptor,
130
+ operationId: _,
131
+ operationPath: __,
132
+ ...rest
133
+ } = executeOptions;
134
+
135
+ // each step derives the next options rather than editing in place
136
+ const withServer = this.#applyServerOptions(rest, normalizedOperation, locator.document);
137
+ const withBody = this.#adaptRequestBody(withServer, normalizedOperation, locator.document, locator.jsonPointer);
138
+ const buildOptions = this.#adaptRequestContentType(withBody, normalizedOperation, locator.document, locator.jsonPointer);
139
+ let request;
140
+ try {
141
+ const spec = assembled.toJSON();
142
+ request = buildRequest({
143
+ ...buildOptions,
144
+ spec,
145
+ operationId
146
+ });
147
+ } catch (error) {
148
+ throw new ClientError('Failed to build request for OpenAPI operation', {
149
+ operationPath: locator.jsonPointer,
150
+ cause: error
151
+ });
152
+ }
153
+
154
+ // a differing value means #adaptRequestContentType swapped the authored
155
+ // media type for the declared one so the body would attach; the header
156
+ // still goes out as authored, parameters and all
157
+ const authoredContentType = executeOptions.requestContentType;
158
+ if (typeof authoredContentType === 'string' && buildOptions.requestContentType !== authoredContentType) {
159
+ request.headers['Content-Type'] = authoredContentType;
160
+ }
161
+ request = await this.#finalizeRequest(request, requestInterceptor);
162
+
163
+ // capture the request as sent, so `$url` / `$method` / `$request.*` can be
164
+ // evaluated after execution; headers are copied so a transport-side
165
+ // mutation cannot rewrite the record
166
+ const requestInfo = {
167
+ url: request.url,
168
+ method: request.method,
169
+ headers: {
170
+ ...request.headers
171
+ },
172
+ body: request.body
173
+ };
174
+ let rawResponse;
175
+ try {
176
+ rawResponse = await this.#httpClient(request);
177
+ } catch (error) {
178
+ throw new ClientError('OpenAPI operation request failed', {
179
+ operationPath: locator.jsonPointer,
180
+ cause: error
181
+ });
182
+ }
183
+ return this.#responseNormalizer.normalize(rawResponse, requestInfo);
184
+ }
185
+
186
+ /**
187
+ * Derives where the request is sent, returning the options to build with.
188
+ *
189
+ * swagger-client already selects a declared server, substitutes its variables
190
+ * and resolves relative URLs — the one thing it gets wrong is what to do with
191
+ * a `server` matching nothing it declared, which it silently swaps for the
192
+ * first declared server. Here that value becomes an override instead: the URL
193
+ * you name is the URL that is called, whatever the document declares. Passing
194
+ * it as `baseURL` bypasses swagger-client's own server resolution, which also
195
+ * makes `server` work on Swagger 2.0, where it is otherwise ignored.
196
+ *
197
+ * A relative server resolves against the URL the document was loaded from,
198
+ * which is what `contextUrl` means, so it is derived rather than asked for.
199
+ * Only an HTTP(S) document URL qualifies. Swagger 2.0 falls back to the
200
+ * context URL's protocol when the document declares no `schemes`, so a `file:`
201
+ * URL would yield `file://host/basePath` instead of `http://…`. The registry
202
+ * normalizes `file:` URIs to bare filesystem paths, which carry no protocol
203
+ * and are harmless, so this is a guard on the invariant rather than on a
204
+ * reachable state today. A caller-supplied `contextUrl` still wins, for
205
+ * resolving against somewhere the document did not come from.
206
+ */
207
+ #applyServerOptions(buildOptions, operation, document) {
208
+ const resolved = buildOptions.contextUrl === undefined && urlUtils.isHttpUrl(document.uri) ? {
209
+ ...buildOptions,
210
+ contextUrl: document.uri
211
+ } : buildOptions;
212
+ const {
213
+ server,
214
+ ...withoutServer
215
+ } = resolved;
216
+ if (typeof server !== 'string') return resolved;
217
+
218
+ // the operation normalizer has already inherited servers down from the
219
+ // document root, including the spec's default `[{ url: '/' }]`. spread into
220
+ // a real array rather than using ArrayElement's own find(), which returns an
221
+ // empty ArrayElement on a miss — truthy, so every server would look declared.
222
+ // the members are Server elements, but isObjectElement keeps this one code
223
+ // path valid for every version: isServerElement is per-namespace, and the
224
+ // 3.1 one rejects 3.0 servers
225
+ const servers = operation.get('servers');
226
+ const declared = isArrayElement(servers) ? [...servers] : [];
227
+ const isDeclared = declared.some(candidate => isObjectElement(candidate) && toValue(candidate.get('url')) === server);
228
+ if (isDeclared) return resolved;
229
+ const baseURL = urlUtils.hasProtocol(server) ? server : urlUtils.resolve(resolved.contextUrl ?? '', server);
230
+
231
+ // buildRequest concatenates the base and the path verbatim, so a trailing
232
+ // slash would double up. swagger-client trims one in its own resolution too
233
+ return {
234
+ ...withoutServer,
235
+ baseURL: baseURL.replace(/\/$/, '')
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Reconciles the requested body media type with the ones the operation
241
+ * declares, returning the options to build with. A rewritten
242
+ * `requestContentType` is what tells `execute` to restore the authored value
243
+ * onto the header afterwards.
244
+ *
245
+ * swagger-client attaches the body only when `requestContentType` is an exact
246
+ * key of `requestBody.content`, and otherwise drops the body *and* the header
247
+ * without a word. Media types are richer than that: they carry optional
248
+ * parameters and their names are case-insensitive, so the Arazzo
249
+ * `application/json; charset=utf-8` and `Application/JSON` both name a
250
+ * declared `application/json`. Both are matched here, the request is built
251
+ * under the declared spelling, and the authored value is restored onto the
252
+ * header afterwards. A type naming nothing declared is an authoring error and
253
+ * is raised rather than turned into an empty request.
254
+ *
255
+ * Only OpenAPI 3.x declares media types this way. A Swagger 2.0 operation has
256
+ * no `requestBody` to check against, so its content type passes through for
257
+ * `consumes` to govern.
258
+ */
259
+ #adaptRequestContentType(buildOptions, operation, document, jsonPointer) {
260
+ const requested = buildOptions.requestContentType;
261
+ if (typeof requested !== 'string' || buildOptions.requestBody === undefined) {
262
+ return buildOptions;
263
+ }
264
+ if (isSwaggerElement(document.parseResult.api)) return buildOptions;
265
+ const requestBody = operation.get('requestBody');
266
+ const content = isObjectElement(requestBody) ? requestBody.get('content') : undefined;
267
+ if (!isObjectElement(content)) {
268
+ throw new ClientError(`Request body media type "${requested}" cannot be sent: the operation declares ` + 'no request body content. Omit the content type to send the payload as-is', {
269
+ operationPath: jsonPointer
270
+ });
271
+ }
272
+ const declared = content.keys();
273
+ if (declared.includes(requested)) return buildOptions;
274
+
275
+ // media type names are case-insensitive and their parameters are not part of
276
+ // the name, so both are normalized away before matching
277
+ const baseType = requested.split(';')[0].trim().toLowerCase();
278
+ const [type] = baseType.split('/');
279
+ const covers = (mediaType, by) => {
280
+ const candidate = mediaType.toLowerCase();
281
+ if (by === 'exact') return candidate === baseType;
282
+ if (by === 'subtype') return candidate === `${type}/*`;
283
+ return candidate === '*/*';
284
+ };
285
+
286
+ // a form media type decides how the body is encoded, and buildRequest applies
287
+ // that encoding only for an exactly declared type. matching one against a
288
+ // range would put a JSON body behind a form Content-Type
289
+ const isFormEncoded = baseType === 'application/x-www-form-urlencoded' || baseType === 'multipart/form-data';
290
+
291
+ // otherwise a declared range covers a concrete type, so `*/*` (what several
292
+ // generators emit by default) accepts anything; the most specific wins
293
+ const exact = declared.find(mediaType => covers(mediaType, 'exact'));
294
+ const match = exact ?? (isFormEncoded ? undefined : declared.find(mediaType => covers(mediaType, 'subtype')) ?? declared.find(mediaType => covers(mediaType, 'any')));
295
+ if (match === undefined) {
296
+ throw new ClientError(isFormEncoded ? `Request body media type "${requested}" must be declared by the operation exactly, ` + `since it decides how the body is encoded (declared: [${declared.join(', ')}])` : `Request body media type "${requested}" is not declared by the operation ` + `(declared: [${declared.join(', ')}])`, {
297
+ operationPath: jsonPointer
298
+ });
299
+ }
300
+ return {
301
+ ...buildOptions,
302
+ requestContentType: match
303
+ };
304
+ }
305
+
306
+ /**
307
+ * Routes a request body through the convention the target OpenAPI version
308
+ * uses, leaving OpenAPI 3.x calls untouched.
309
+ *
310
+ * Swagger 2.0 has no request body concept — the payload is an ordinary
311
+ * parameter declared `in: body`, or a set of `in: formData` ones — so it must
312
+ * travel through `parameters` under the names the operation declares. Arazzo
313
+ * cannot express that itself (its parameter `in` allows only `path`, `query`,
314
+ * `querystring`, `header`, and `cookie`), which is why the translation lives
315
+ * here, where the assembled document has those parameters dereferenced and
316
+ * inherited.
317
+ *
318
+ * The payload travels under {@link ParameterDelivery} keys
319
+ * (`body.{name}`, `formData.{name}`), never bare names: a bare key would
320
+ * capture a same-named parameter in another location — 2.0 legally declares
321
+ * e.g. `petId` in `path` and in `formData` at once — per the lookup order
322
+ * documented on {@link ParameterDelivery}.
323
+ *
324
+ * A *bare* `parameters` entry bearing a payload target's name is rejected
325
+ * with {@link ClientError} rather than resolved either way: before the
326
+ * qualified keys the payload silently overwrote it, after them the bare key
327
+ * would silently outrank the payload at the client's bare-first lookup —
328
+ * both wrong for somebody, so the ambiguity is reported instead. Such an
329
+ * entry can come from a direct caller mixing bare parameters with a request
330
+ * body, or from the runner's own pipeline: the parameter resolver keeps a
331
+ * `querystring` parameter under its bare name.
332
+ */
333
+ #adaptRequestBody(buildOptions, operation, document, jsonPointer) {
334
+ if (!isSwaggerElement(document.parseResult.api) || buildOptions.requestBody === undefined) {
335
+ return buildOptions;
336
+ }
337
+ const {
338
+ requestBody,
339
+ parameters: given,
340
+ ...rest
341
+ } = buildOptions;
342
+ const callerParameters = given;
343
+ // an operation declaring none leaves `parameters` absent rather than empty,
344
+ // and that case still has to reach the throw below
345
+ const declared = operation.parameters;
346
+ const parameters = isArrayElement(declared) ? [...declared.filter(isParameterElement)] : [];
347
+
348
+ // the body parameter's name is arbitrary, so the payload wins over a
349
+ // same-keyed entry the caller supplied
350
+ const body = parameters.find(parameter => toValue(parameter.in) === 'body');
351
+ if (body !== undefined) {
352
+ const name = toValue(body.name);
353
+ this.#rejectBarePayloadConflicts(callerParameters, [name], jsonPointer);
354
+ return {
355
+ ...rest,
356
+ parameters: {
357
+ ...callerParameters,
358
+ [ParameterDelivery.keyFor('body', name)]: requestBody
359
+ }
360
+ };
361
+ }
362
+
363
+ // formData models each field as its own parameter, so the payload's keys
364
+ // are the parameter names; buildRequest reports any that do not match
365
+ const hasForm = parameters.some(parameter => toValue(parameter.in) === 'formData');
366
+ if (hasForm) {
367
+ if (!isPlainObject(requestBody)) {
368
+ throw new ClientError('Request body cannot be sent: the OpenAPI 2.0 operation carries it in "formData" ' + 'parameters, so the payload must be an object keyed by their names', {
369
+ operationPath: jsonPointer
370
+ });
371
+ }
372
+ const payload = requestBody;
373
+ this.#rejectBarePayloadConflicts(callerParameters, Object.keys(payload), jsonPointer);
374
+ const merged = {
375
+ ...callerParameters
376
+ };
377
+ for (const [field, value] of Object.entries(payload)) {
378
+ merged[ParameterDelivery.keyFor('formData', field)] = value;
379
+ }
380
+ return {
381
+ ...rest,
382
+ parameters: merged
383
+ };
384
+ }
385
+ throw new ClientError('Request body cannot be sent: the OpenAPI 2.0 operation declares no "body" or ' + '"formData" parameter to carry it', {
386
+ operationPath: jsonPointer
387
+ });
388
+ }
389
+
390
+ /**
391
+ * Rejects bare `parameters` entries bearing the names the payload is about
392
+ * to be delivered under — the client consults bare names before qualified
393
+ * keys, so such an entry would silently displace the request body (see
394
+ * {@link OpenAPIOperationExecutor.#adaptRequestBody}).
395
+ */
396
+ #rejectBarePayloadConflicts(callerParameters, payloadNames, jsonPointer) {
397
+ if (callerParameters === undefined) return;
398
+ const conflicts = payloadNames.filter(name => Object.hasOwn(callerParameters, name));
399
+ if (conflicts.length === 0) return;
400
+ throw new ClientError(`Request body cannot be sent: bare parameter ${conflicts.length === 1 ? 'entry' : 'entries'} ` + `[${conflicts.join(', ')}] would displace the payload at delivery. Supply the ` + `parameter under its "{in}.{name}" key, or drop the duplicate`, {
401
+ operationPath: jsonPointer
402
+ });
403
+ }
404
+
405
+ /**
406
+ * Applies the wire-level fixups swagger-client performs between building a
407
+ * request and fetching it, and runs the request interceptors: the
408
+ * executor-level one first, the per-call one from the options bag second.
409
+ *
410
+ * Ported rather than imported: upstream keeps them inline in `execute()` and
411
+ * `http()`, which also fetch and throw on a non-2xx. Diff against those two
412
+ * when bumping swagger-client.
413
+ */
414
+ async #finalizeRequest(request, perCallInterceptor) {
415
+ let finalized = request;
416
+
417
+ // JSON-encode plain-object and array bodies, exactly as swagger-client's
418
+ // execute() does before handing the request to its transport
419
+ if (finalized.body && (isPlainObject(finalized.body) || Array.isArray(finalized.body))) {
420
+ finalized.body = JSON.stringify(finalized.body);
421
+ }
422
+
423
+ // an embedded newline makes a header value invalid per the Fetch standard,
424
+ // so fetch throws a TypeError before sending anything. squashing repairs it,
425
+ // and since the request is captured after this, the record shows the value
426
+ // that actually went out
427
+ Object.keys(finalized.headers).forEach(headerName => {
428
+ const value = finalized.headers[headerName];
429
+ if (typeof value === 'string') {
430
+ finalized.headers[headerName] = value.replace(/\n+/g, ' ');
431
+ }
432
+ });
433
+ finalized = (await this.#requestInterceptor?.(finalized)) ?? finalized;
434
+ finalized = (await perCallInterceptor?.(finalized)) ?? finalized;
435
+
436
+ // for multipart/form-data, remove the built content-type so the transport
437
+ // sets one carrying the correct boundary
438
+ const contentType = finalized.headers['content-type'] ?? finalized.headers['Content-Type'];
439
+ if (typeof contentType === 'string' && /multipart\/form-data/i.test(contentType)) {
440
+ delete finalized.headers['content-type'];
441
+ delete finalized.headers['Content-Type'];
442
+ }
443
+ return finalized;
444
+ }
445
+ }
446
+ export default OpenAPIOperationExecutor;
@@ -0,0 +1,197 @@
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 = void 0;
8
+ var _arazzoRuntimeExpression = require("@swaggerexpert/arazzo-runtime-expression");
9
+ var _jsonPointer = require("@swaggerexpert/json-pointer");
10
+ var _empty = require("@speclynx/apidom-reference/configuration/empty");
11
+ var _OpenAPIDocument = _interopRequireDefault(require("../document/OpenAPIDocument.cjs"));
12
+ var _RuntimeExpressionEvaluator = _interopRequireDefault(require("../expression/RuntimeExpressionEvaluator.cjs"));
13
+ var _ExtractionError = _interopRequireDefault(require("../errors/ExtractionError.cjs"));
14
+ var _SourceDescriptionsLocatorNormalizer = _interopRequireDefault(require("./SourceDescriptionsLocatorNormalizer.cjs"));
15
+ /**
16
+ * A canonical OpenAPI operation locator: the OpenAPI document that owns the
17
+ * operation and a JSON Pointer locating the operation within it
18
+ * (`/paths/{path}/{method}`).
19
+ *
20
+ * A step names its operation in one of several ways — a plain `operationId`, a
21
+ * `$sourceDescriptions` runtime expression, or an `operationPath` — which are
22
+ * all locators for the same thing. The normalizer reduces any of them to this
23
+ * single canonical form.
24
+ * @public
25
+ */
26
+
27
+ /**
28
+ * Normalizes a step's OpenAPI operation locator to a canonical
29
+ * {@link OpenAPIOperationLocator} — the OpenAPI document and a JSON Pointer to
30
+ * the operation within it.
31
+ *
32
+ * A step names its operation with either `operationId` or `operationPath`:
33
+ * - `operationId` is a plain operation name, or a whole runtime expression
34
+ * `$sourceDescriptions.{name}.{operationId}`. Per the Arazzo Specification, a
35
+ * plain name is only valid when a single (non-arazzo) source description is
36
+ * defined; multiple sources require the expression form. A plain name is
37
+ * therefore resolved against the first source description that has it.
38
+ * - `operationPath` embeds a `{$sourceDescriptions.{name}.url}` runtime
39
+ * expression followed by a JSON Pointer to the operation.
40
+ *
41
+ * The `type` declared on a source description is not trusted (it may be absent
42
+ * or wrong); a source is classified by what it actually parses to. The
43
+ * normalizer acquires the referenced source documents from the registry on
44
+ * demand.
45
+ * @public
46
+ */
47
+ class OpenAPIOperationLocatorNormalizer extends _SourceDescriptionsLocatorNormalizer.default {
48
+ /**
49
+ * Normalizes a plain or expression `operationId` to a canonical locator.
50
+ *
51
+ * 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.
54
+ */
55
+ async normalizeOperationId(operationId, document) {
56
+ if ((0, _arazzoRuntimeExpression.test)(operationId)) {
57
+ return this.#resolveSourced(operationId, document);
58
+ }
59
+ return this.#resolvePlainOperationId(operationId, document);
60
+ }
61
+
62
+ /**
63
+ * Normalizes an `operationPath` —
64
+ * `{$sourceDescriptions.{name}.url}#/json/pointer` — to a canonical locator.
65
+ *
66
+ * The reference is interpolated (resolving
67
+ * `{$sourceDescriptions.{name}.url}` to the source's URL) into a URI
68
+ * reference; its fragment is the JSON Pointer to the operation (decoded from
69
+ * the URI fragment representation) and the part before the fragment is the
70
+ * source document to acquire.
71
+ *
72
+ * The runner only needs a URL and a JSON Pointer, so it does not enforce that
73
+ * the source description is identified by a runtime expression rather than a
74
+ * 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.
77
+ */
78
+ async normalizeOperationPath(operationPath, document) {
79
+ const evaluator = new _RuntimeExpressionEvaluator.default({}, {
80
+ strict: false,
81
+ document,
82
+ registry: this.registry
83
+ });
84
+ const uriReference = evaluator.interpolate(operationPath);
85
+ const documentURI = _empty.url.sanitize(_empty.url.stripHash(uriReference));
86
+ if (documentURI === '') {
87
+ throw new _ExtractionError.default(`operationPath "${operationPath}" does not resolve to a source description URL`, {
88
+ pointer: operationPath,
89
+ uri: document.uri
90
+ });
91
+ }
92
+ const jsonPointer = _jsonPointer.URIFragmentIdentifier.fromURIReference(uriReference);
93
+ if (!(0, _jsonPointer.testJSONPointer)(jsonPointer)) {
94
+ throw new _ExtractionError.default(`operationPath "${operationPath}" has an invalid JSON Pointer`, {
95
+ pointer: operationPath,
96
+ uri: document.uri
97
+ });
98
+ }
99
+ const source = await this.registry.acquire(documentURI);
100
+ if (!_OpenAPIDocument.default.is(source)) {
101
+ throw new _ExtractionError.default(`operationPath "${operationPath}" does not reference an OpenAPI document`, {
102
+ pointer: operationPath,
103
+ uri: documentURI
104
+ });
105
+ }
106
+ return {
107
+ document: source,
108
+ jsonPointer
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Parses a `$sourceDescriptions.{name}.{reference}` expression, asserting
114
+ * it is a source-descriptions expression.
115
+ */
116
+ #parseSourceDescriptionsExpression(expression) {
117
+ const parsed = this.parseSourceDescriptionsExpression(expression);
118
+ if (parsed === undefined) {
119
+ throw new _ExtractionError.default(`Unsupported operationId expression "${expression}"`, {
120
+ operationId: expression
121
+ });
122
+ }
123
+ return parsed;
124
+ }
125
+ async #resolveSourced(expression, document) {
126
+ const {
127
+ sourceName,
128
+ reference: operationId
129
+ } = this.#parseSourceDescriptionsExpression(expression);
130
+ const openapiDocument = await this.#sourceDocument(sourceName, document);
131
+ const jsonPointer = openapiDocument.operationIndex.get(operationId);
132
+ if (jsonPointer === undefined) {
133
+ throw new _ExtractionError.default(`Operation "${operationId}" not found in source description "${sourceName}"`, {
134
+ operationId,
135
+ uri: openapiDocument.uri
136
+ });
137
+ }
138
+ return {
139
+ document: openapiDocument,
140
+ jsonPointer
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Resolves a plain operationId against the single OpenAPI source description.
146
+ *
147
+ * Per the Arazzo Specification a plain operationId is only valid when a single
148
+ * non-arazzo source description is defined; multiple such sources require the
149
+ * `$sourceDescriptions` expression form. The first OpenAPI source is therefore
150
+ * the one it targets: the operationId is looked up there and the lookup fails
151
+ * if it is absent — subsequent OpenAPI sources are not searched (their
152
+ * presence would make the plain operationId ambiguous, and hence invalid).
153
+ * Non-OpenAPI (arazzo) sources are skipped. Sources are acquired one at a time
154
+ * until the first OpenAPI source is reached.
155
+ */
156
+ async #resolvePlainOperationId(operationId, document) {
157
+ for (const uri of document.sourceDescriptionURIs()) {
158
+ const source = await this.registry.acquire(uri);
159
+ if (!_OpenAPIDocument.default.is(source)) continue;
160
+ const jsonPointer = source.operationIndex.get(operationId);
161
+ if (jsonPointer === undefined) {
162
+ throw new _ExtractionError.default(`Operation "${operationId}" not found in source description at "${source.uri}"`, {
163
+ operationId,
164
+ uri: source.uri
165
+ });
166
+ }
167
+ return {
168
+ document: source,
169
+ jsonPointer
170
+ };
171
+ }
172
+ throw new _ExtractionError.default(`Operation "${operationId}" has no OpenAPI source description`, {
173
+ operationId,
174
+ uri: document.uri
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Acquires a source description by name and asserts it is an OpenAPI document.
180
+ */
181
+ async #sourceDocument(sourceName, document) {
182
+ const uri = document.resolveSourceDescriptionURI(sourceName);
183
+ if (uri === undefined) {
184
+ throw new _ExtractionError.default(`Source description "${sourceName}" not found`, {
185
+ uri: document.uri
186
+ });
187
+ }
188
+ const source = await this.registry.acquire(uri);
189
+ if (!_OpenAPIDocument.default.is(source)) {
190
+ throw new _ExtractionError.default(`Source description "${sourceName}" is not an OpenAPI document`, {
191
+ uri
192
+ });
193
+ }
194
+ return source;
195
+ }
196
+ }
197
+ var _default = exports.default = OpenAPIOperationLocatorNormalizer;