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

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