@kevinoid/openapi-transformers 0.1.0

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 (50) hide show
  1. package/LICENSE.txt +19 -0
  2. package/README.md +134 -0
  3. package/add-tag-to-operation-ids.js +60 -0
  4. package/add-x-ms-enum-name.js +96 -0
  5. package/add-x-ms-enum-value-names.js +142 -0
  6. package/additional-properties-to-object.js +35 -0
  7. package/additional-properties-to-unconstrained.js +115 -0
  8. package/any-of-null-to-nullable.js +50 -0
  9. package/assert-properties.js +56 -0
  10. package/binary-string-to-file.js +54 -0
  11. package/bool-enum-to-bool.js +100 -0
  12. package/clear-html-response-schema.js +77 -0
  13. package/client-params-to-global.js +97 -0
  14. package/const-to-enum.js +49 -0
  15. package/escape-enum-values.js +211 -0
  16. package/exclusive-min-max-to-bool.js +61 -0
  17. package/format-to-type.js +54 -0
  18. package/index.js +94 -0
  19. package/inline-non-object-schemas.js +120 -0
  20. package/lib/component-manager.js +60 -0
  21. package/lib/matching-component-manager.js +74 -0
  22. package/lib/matching-parameter-manager.js +36 -0
  23. package/merge-all-of.js +60 -0
  24. package/merge-any-of.js +48 -0
  25. package/merge-one-of.js +48 -0
  26. package/nullable-not-required.js +240 -0
  27. package/nullable-to-type-null.js +46 -0
  28. package/openapi31to30.js +54 -0
  29. package/package.json +131 -0
  30. package/path-parameters-to-operations.js +63 -0
  31. package/pattern-properties-to-additional-properties.js +62 -0
  32. package/queries-to-x-ms-paths.js +63 -0
  33. package/read-only-not-required.js +111 -0
  34. package/ref-path-parameters.js +73 -0
  35. package/remove-default-only-response-produces.js +58 -0
  36. package/remove-paths-with-servers.js +34 -0
  37. package/remove-query-from-paths.js +526 -0
  38. package/remove-ref-siblings.js +78 -0
  39. package/remove-request-body.js +102 -0
  40. package/remove-response-headers.js +42 -0
  41. package/remove-security-scheme-if.js +166 -0
  42. package/remove-type-if.js +65 -0
  43. package/rename-components.js +285 -0
  44. package/replaced-by-to-description.js +50 -0
  45. package/server-vars-to-path-params.js +224 -0
  46. package/server-vars-to-x-ms-parameterized-host.js +247 -0
  47. package/type-null-to-enum.js +47 -0
  48. package/type-null-to-nullable.js +57 -0
  49. package/urlencoded-to-string.js +160 -0
  50. package/x-enum-to-ms.js +92 -0
@@ -0,0 +1,526 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/remove-query-from-paths.js"
5
+ */
6
+
7
+ import { METHODS } from 'node:http';
8
+ import { isDeepStrictEqual } from 'node:util';
9
+
10
+ import OpenApiTransformerBase from 'openapi-transformer-base';
11
+ import visit from 'openapi-transformer-base/visit.js';
12
+
13
+ const oasVersionSymbol = Symbol('openApiVersion');
14
+ const pathVarNameToParamsSymbol = Symbol('pathVarNameToParams');
15
+
16
+ const httpMethodSet = new Set(METHODS);
17
+
18
+ function isArrayEqual(array1, array2) {
19
+ if (array1 === undefined) {
20
+ array1 = [];
21
+ }
22
+ if (array2 === undefined) {
23
+ array2 = [];
24
+ }
25
+ return isDeepStrictEqual(array1, array2);
26
+ }
27
+
28
+ function valueToType(value) {
29
+ const type = typeof value;
30
+ if (value === undefined || type === 'symbol') {
31
+ throw new TypeError(`${type} type not available in JSON Schema`);
32
+ }
33
+
34
+ return type === 'number' && value % 1 === 0 ? 'integer'
35
+ : value === null ? 'null'
36
+ : type;
37
+ }
38
+
39
+ function createConstQueryParam(name, value) {
40
+ const oasVersion = this[oasVersionSymbol];
41
+
42
+ if (oasVersion.startsWith('2.')) {
43
+ return {
44
+ name,
45
+ in: 'query',
46
+ required: true,
47
+ type: valueToType(value),
48
+ enum: [value],
49
+ };
50
+ }
51
+
52
+ if (oasVersion.startsWith('3.0')) {
53
+ return {
54
+ name,
55
+ in: 'query',
56
+ required: true,
57
+ schema: {
58
+ enum: [value],
59
+ },
60
+ };
61
+ }
62
+
63
+ return {
64
+ name,
65
+ in: 'query',
66
+ required: true,
67
+ schema: {
68
+ const: value,
69
+ },
70
+ };
71
+ }
72
+
73
+ // FIXME: How to handle unpaired {}
74
+ // FIXME: How to handle = in {}
75
+ // FIXME: How to handle %-encoded characters in {}
76
+ // FIXME: How to handle missing variable name (e.g. /a/{}/b)
77
+ function parseQueryParams(query) {
78
+ const ampQuery = `&${query}`;
79
+ const paramValueRE =
80
+ /&([^{}&=]*(?:\{[^{}]*\}[^{}&=]*)*)(?:=([^{}&]*(?:\{[^{}]*\}[^{}&]*)*))?/y;
81
+ const paramMap = new Map();
82
+ let paramValueMatch;
83
+ while (paramValueRE.lastIndex < ampQuery.length
84
+ // eslint-disable-next-line no-cond-assign
85
+ && (paramValueMatch = paramValueRE.exec(ampQuery)) !== null) {
86
+ if (paramValueMatch[0] === '&') {
87
+ // Nothing after &
88
+ continue;
89
+ }
90
+
91
+ const param = paramValueMatch[1];
92
+ const pvarStart = param.indexOf('{');
93
+ const pvarEnd = param.indexOf('}');
94
+ if (pvarStart > 0) {
95
+ if (pvarEnd > pvarStart) {
96
+ this.warn('Unable to handle query param with variable name', param);
97
+ } else {
98
+ this.warn('Unpaired "{" in query param name', param);
99
+ }
100
+
101
+ return undefined;
102
+ }
103
+
104
+ if (pvarEnd >= 0) {
105
+ this.warn('Unpaired "}" in query param name', param);
106
+ return undefined;
107
+ }
108
+
109
+ const value = paramValueMatch[2] || '';
110
+ const qvarStart = value.indexOf('{');
111
+ const qvarEnd = value.indexOf('}');
112
+ if (qvarStart > 0 || (qvarStart === 0 && qvarEnd !== value.length - 1)) {
113
+ this.warn(
114
+ 'Unable to handle query value with constant and variable parts',
115
+ value,
116
+ );
117
+ return undefined;
118
+ }
119
+
120
+ if (qvarEnd < qvarStart) {
121
+ this.warn(
122
+ 'Unpaired "%s" in query value',
123
+ qvarEnd >= 0 ? '}' : '{',
124
+ param,
125
+ );
126
+ return undefined;
127
+ }
128
+
129
+ // TODO: Convert to param with `type: array` and `explode: true`?
130
+ if (paramMap.has(param)) {
131
+ this.warn('Ignoring path with duplicate query parameter', param);
132
+ return undefined;
133
+ }
134
+
135
+ paramMap.set(param, value);
136
+ }
137
+
138
+ if (paramValueRE.lastIndex !== ampQuery.length) {
139
+ this.warn('Unable to parse query parameters', query);
140
+ return undefined;
141
+ }
142
+
143
+ return paramMap;
144
+ }
145
+
146
+ function combineParameters(opParams, pathParams) {
147
+ if (opParams === undefined) {
148
+ return pathParams;
149
+ }
150
+
151
+ if (!Array.isArray(opParams)) {
152
+ return opParams;
153
+ }
154
+
155
+ if (opParams.length === 0) {
156
+ return pathParams;
157
+ }
158
+
159
+ return [
160
+ ...opParams,
161
+ ...pathParams
162
+ // Don't include params from Path Item overridden by Operation
163
+ .filter((pp) => pp && !opParams.some((op) => op
164
+ && op.name === pp.name
165
+ && op.in === pp.in)),
166
+ ];
167
+ }
168
+
169
+ function moveParameters(pathItem) {
170
+ const { parameters, ...newPathItem } = pathItem;
171
+ if (parameters === undefined
172
+ || (Array.isArray(parameters) && parameters.length === 0)) {
173
+ return pathItem;
174
+ }
175
+
176
+ for (const [method, operation] of Object.entries(pathItem)) {
177
+ if (operation !== null
178
+ && typeof operation === 'object'
179
+ && httpMethodSet.has(method.toUpperCase())) {
180
+ newPathItem[method] = {
181
+ ...operation,
182
+ parameters: combineParameters(operation.parameters, parameters),
183
+ };
184
+ }
185
+ }
186
+
187
+ return newPathItem;
188
+ }
189
+
190
+ function moveServers(pathItem) {
191
+ const { servers, ...newPathItem } = pathItem;
192
+ if (servers === undefined
193
+ || (Array.isArray(servers) && servers.length === 0)) {
194
+ return pathItem;
195
+ }
196
+
197
+ for (const [method, operation] of Object.entries(pathItem)) {
198
+ if (operation !== null
199
+ && typeof operation === 'object'
200
+ && operation.servers === undefined
201
+ && httpMethodSet.has(method.toUpperCase())) {
202
+ newPathItem[method] = {
203
+ ...operation,
204
+ servers,
205
+ };
206
+ }
207
+ }
208
+
209
+ return newPathItem;
210
+ }
211
+
212
+ function mergePathItems(pathItem1, pathItem2) {
213
+ if (!isArrayEqual(pathItem1.parameters, pathItem2.parameters)) {
214
+ // TODO: Keep common parameters at Path Item level
215
+ pathItem1 = moveParameters(pathItem1);
216
+ pathItem2 = moveParameters(pathItem2);
217
+ }
218
+
219
+ if (!isArrayEqual(pathItem1.servers, pathItem2.servers)) {
220
+ pathItem1 = moveServers(pathItem1);
221
+ pathItem2 = moveServers(pathItem2);
222
+ }
223
+
224
+ const combined = { ...pathItem2 };
225
+ for (const [prop, value1] of Object.entries(pathItem1)) {
226
+ if (value1 !== undefined
227
+ && prop !== 'parameters'
228
+ && prop !== 'servers') {
229
+ const value2 = pathItem2[prop];
230
+ if (value2 !== undefined && !isDeepStrictEqual(value1, value2)) {
231
+ this.transformPath.push(prop);
232
+ this.warn(
233
+ 'Refusing to overwrite %o with %o',
234
+ value2,
235
+ value1,
236
+ );
237
+ this.transformPath.pop();
238
+ return undefined;
239
+ }
240
+
241
+ combined[prop] = value1;
242
+ }
243
+ }
244
+
245
+ return combined;
246
+ }
247
+
248
+ function transformParameters(parameters) {
249
+ const pathVarNameToParams = this[pathVarNameToParamsSymbol];
250
+ return parameters.flatMap((param) => {
251
+ if (param === null
252
+ || typeof param !== 'object'
253
+ || param.in !== 'path') {
254
+ return param;
255
+ }
256
+
257
+ const newParams = pathVarNameToParams.get(param.name);
258
+ if (newParams === undefined) {
259
+ return param;
260
+ }
261
+
262
+ return newParams
263
+ .map((newParam) => ({ ...param, ...newParam }))
264
+ // If the parameter already exists, avoid duplicating it
265
+ .filter((newParam) => !parameters.some((p) => p
266
+ && p !== param
267
+ && p.name === newParam.name
268
+ && p.in === newParam.in));
269
+ });
270
+ }
271
+
272
+ /**
273
+ * Transformer to remove query component of path in paths object.
274
+ *
275
+ * Including the query portion of a URL in path is not allowed in any current
276
+ * version of OpenAPI
277
+ * https://github.com/OAI/OpenAPI-Specification/issues/468#issuecomment-142393969
278
+ * but some authors include it for various reasons.
279
+ *
280
+ * Operations which would conflict with existing operations on the same path
281
+ * (excluding query parameters) are left unchanged.
282
+ * See QueriesToXMsPathsTransformer for another way to handle these.
283
+ */
284
+ export default class RemoveQueryFromPathsTransformer
285
+ extends OpenApiTransformerBase {
286
+ constructor() {
287
+ super();
288
+ this[oasVersionSymbol] = undefined;
289
+ this[pathVarNameToParamsSymbol] = undefined;
290
+ }
291
+
292
+ transformOperation(operation) {
293
+ if (typeof operation !== 'object'
294
+ || operation === null
295
+ || Array.isArray(operation)) {
296
+ this.warn('Ignoring non-object Operation', operation);
297
+ return operation;
298
+ }
299
+
300
+ const { parameters } = operation;
301
+ if (!Array.isArray(parameters) || parameters.length === 0) {
302
+ if (parameters !== undefined && !Array.isArray(parameters)) {
303
+ this.warn('Ignoring non-Array parameters', parameters);
304
+ }
305
+
306
+ return operation;
307
+ }
308
+
309
+ return {
310
+ ...operation,
311
+ parameters: visit(
312
+ this,
313
+ transformParameters,
314
+ 'parameters',
315
+ parameters,
316
+ ),
317
+ };
318
+ }
319
+
320
+ transformPathItem(pathItem) {
321
+ pathItem = super.transformPathItem(pathItem);
322
+ if (typeof pathItem !== 'object'
323
+ || pathItem === null
324
+ || Array.isArray(pathItem)) {
325
+ // Already warned by super.transformPathItem(pathItem)
326
+ return pathItem;
327
+ }
328
+
329
+ const { parameters } = pathItem;
330
+ if (!Array.isArray(parameters) || parameters.length === 0) {
331
+ // Already warned by super.transformPathItem(pathItem)
332
+ return pathItem;
333
+ }
334
+
335
+ return {
336
+ ...pathItem,
337
+ parameters: visit(
338
+ this,
339
+ transformParameters,
340
+ 'parameters',
341
+ parameters,
342
+ ),
343
+ };
344
+ }
345
+
346
+ transformPaths(paths) {
347
+ if (typeof paths !== 'object' || paths === null || Array.isArray(paths)) {
348
+ this.warn('Ignoring non-object Paths', paths);
349
+ return paths;
350
+ }
351
+
352
+ // RegExp to match a path with a query part and optionally fragment part
353
+ // Careful to avoid matching ? or # in variable name as part of path
354
+ // (OAS 3.1.0 is clear that they are disallowed, but some path templating
355
+ // languages use ? in particular, so be prepared.)
356
+ const pathRE =
357
+ /^([^{?#]*(?:\{[^}]*\}[^{?#]*)*)\?([^{#]*(?:\{[^}]*\}[^{#]*)*)(#.*)?$/;
358
+ const pathMatches = Object.keys(paths)
359
+ .map((path) => pathRE.exec(path))
360
+ .filter(Boolean);
361
+ if (pathMatches.length === 0) {
362
+ return paths;
363
+ }
364
+
365
+ paths = { ...paths };
366
+
367
+ for (const [pathQuery, pathname, query, frag] of pathMatches) {
368
+ const srcPathItem = paths[pathQuery];
369
+ if (srcPathItem === undefined) {
370
+ continue;
371
+ }
372
+
373
+ if (srcPathItem === null
374
+ || typeof srcPathItem !== 'object'
375
+ || Array.isArray(srcPathItem)) {
376
+ this.transformPath.push(pathQuery);
377
+ this.warn('Ignoring non-object Path Item', srcPathItem);
378
+ this.transformPath.pop();
379
+ continue;
380
+ }
381
+
382
+ if (srcPathItem.parameters !== undefined
383
+ && !Array.isArray(srcPathItem.parameters)) {
384
+ this.transformPath.push(pathQuery);
385
+ this.warn('Ignoring path with non-Array parameters', srcPathItem);
386
+ this.transformPath.pop();
387
+ continue;
388
+ }
389
+
390
+ const dstPath = pathname + (frag || '');
391
+ const dstPathItem = paths[dstPath];
392
+ if (dstPathItem === null
393
+ || (dstPathItem !== undefined && typeof dstPathItem !== 'object')
394
+ || Array.isArray(dstPathItem)) {
395
+ this.transformPath.push(dstPath);
396
+ this.warn(
397
+ 'Refusing to overwrite non-object Path Item',
398
+ dstPathItem,
399
+ );
400
+ this.transformPath.pop();
401
+ continue;
402
+ }
403
+
404
+ let queryParams;
405
+ this.transformPath.push(pathQuery);
406
+ try {
407
+ queryParams = parseQueryParams(query);
408
+ } finally {
409
+ this.transformPath.pop();
410
+ }
411
+
412
+ if (queryParams === undefined) {
413
+ continue;
414
+ }
415
+
416
+ const constParameters = [];
417
+ const pathVarNameToParams = new Map();
418
+ for (const [name, value] of queryParams) {
419
+ if (value[0] !== '{') {
420
+ constParameters.push(createConstQueryParam.call(
421
+ this,
422
+ name,
423
+ value,
424
+ ));
425
+ } else {
426
+ const valueName = value.slice(1, -1);
427
+ let params = pathVarNameToParams.get(valueName);
428
+ if (params === undefined) {
429
+ params = [];
430
+ if (dstPath.includes(value)) {
431
+ // parameter occurs in path and query parts
432
+ params.push({ name: valueName, in: 'path' });
433
+ }
434
+ pathVarNameToParams.set(valueName, params);
435
+ }
436
+ params.push({ name, in: 'query' });
437
+ }
438
+ }
439
+
440
+ let newPathItem = constParameters.length === 0 ? srcPathItem
441
+ : {
442
+ ...srcPathItem,
443
+ parameters: combineParameters(
444
+ srcPathItem.parameters,
445
+ constParameters,
446
+ ),
447
+ };
448
+
449
+ this[pathVarNameToParamsSymbol] = pathVarNameToParams;
450
+ try {
451
+ newPathItem = visit(
452
+ this,
453
+ this.transformPathItem,
454
+ pathQuery,
455
+ newPathItem,
456
+ );
457
+ } finally {
458
+ this[pathVarNameToParamsSymbol] = undefined;
459
+ }
460
+
461
+ const mergedPathItem = dstPathItem === undefined ? newPathItem : visit(
462
+ this,
463
+ mergePathItems,
464
+ dstPath,
465
+ dstPathItem,
466
+ newPathItem,
467
+ );
468
+ if (mergedPathItem !== undefined) {
469
+ paths[dstPath] = mergedPathItem;
470
+ delete paths[pathQuery];
471
+ }
472
+ }
473
+
474
+ return paths;
475
+ }
476
+
477
+ // Override as performance optimization, since only transforming paths
478
+ transformOpenApi(openApi) {
479
+ if (openApi === null
480
+ || typeof openApi !== 'object'
481
+ || Array.isArray(openApi)) {
482
+ this.warn('Ignoring non-object OpenAPI', openApi);
483
+ return openApi;
484
+ }
485
+
486
+ let oasVersion = '3.1.0';
487
+ if (openApi.openapi !== undefined) {
488
+ const openapi = `${openApi.openapi}`;
489
+ if (openapi.startsWith('3.')) {
490
+ oasVersion = openapi;
491
+ } else if (openapi === '3') {
492
+ oasVersion = '3.0.0';
493
+ } else {
494
+ this.warn('Unrecognized OpenAPI version', openApi.openapi);
495
+ }
496
+ } else if (openApi.swagger !== undefined) {
497
+ const swagger = `${openApi.swagger}`;
498
+ if (swagger === '2.0' || swagger === '2') {
499
+ oasVersion = '2.0';
500
+ } else {
501
+ this.warn('Unrecognized Swagger version', openApi.openapi);
502
+ }
503
+ } else {
504
+ this.warn('Document missing OpenAPI and Swagger version', openApi);
505
+ }
506
+
507
+ if (openApi.paths !== undefined) {
508
+ this[oasVersionSymbol] = oasVersion;
509
+ try {
510
+ openApi = {
511
+ ...openApi,
512
+ paths: visit(
513
+ this,
514
+ this.transformPaths,
515
+ 'paths',
516
+ openApi.paths,
517
+ ),
518
+ };
519
+ } finally {
520
+ this[oasVersionSymbol] = undefined;
521
+ }
522
+ }
523
+
524
+ return openApi;
525
+ }
526
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @copyright Copyright 2024 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/remove-ref-siblings.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ /**
10
+ * Transformer to remove properties from Reference Objects.
11
+ *
12
+ * OpenAPI Specification 2.0 and 3.0 forbid properties other than $ref on
13
+ * Reference Object. OAS 3.1 relaxed the restriction to allow summary and
14
+ * description on all Reference Objects and all properties on Schema Objects
15
+ * (due to changes in the referenced version of JSON Schema). There is
16
+ * discussion of allowing more:
17
+ * https://github.com/OAI/OpenAPI-Specification/issues/2026
18
+ * https://github.com/OAI/OpenAPI-Specification/issues/2498
19
+ *
20
+ * Note: Autorest supports description, title, readonly, nullable, and x-*
21
+ * properties:
22
+ * https://github.com/Azure/autorest/blob/main/docs/openapi/howto/$ref-siblings.md
23
+ */
24
+ export default class RemoveRefSiblingsTransformer
25
+ extends OpenApiTransformerBase {
26
+ constructor({ remove, retain } = {}) {
27
+ super();
28
+
29
+ if (remove !== undefined && retain !== undefined) {
30
+ throw new Error('remove and retain options are exclusive');
31
+ }
32
+
33
+ if (typeof remove === 'function') {
34
+ this.removeSchemaRefProp = remove;
35
+ } else if (remove !== undefined) {
36
+ const removeSet = new Set(remove);
37
+ this.removeSchemaRefProp = (propName) => removeSet.has(propName);
38
+ } else if (typeof retain === 'function') {
39
+ this.removeSchemaRefProp = (propName) => !retain(propName);
40
+ } else if (retain !== undefined) {
41
+ const retainSet = new Set(retain);
42
+ this.removeSchemaRefProp = (propName) => !retainSet.has(propName);
43
+ }
44
+ }
45
+
46
+ /** Should a property on a Schema Object with $ref be removed?
47
+ *
48
+ * @param {string} propName Name of the property.
49
+ * @param {!object} schema Schema Object.
50
+ * @returns {boolean} <c>true</c> if <c>propName</c> should be removed,
51
+ * otherwise <c>false</c>.
52
+ */
53
+ // eslint-disable-next-line class-methods-use-this
54
+ removeSchemaRefProp(propName, schema) {
55
+ return true;
56
+ }
57
+
58
+ transformSchema(schema) {
59
+ const newSchema = super.transformSchema(schema);
60
+ if (!newSchema) {
61
+ return newSchema;
62
+ }
63
+
64
+ if (newSchema.$ref === undefined) {
65
+ return newSchema;
66
+ }
67
+
68
+ const removedSchema = {};
69
+ for (const [propName, propVal] of Object.entries(newSchema)) {
70
+ if (propName === '$ref'
71
+ || !this.removeSchemaRefProp(propName, newSchema)) {
72
+ removedSchema[propName] = propVal;
73
+ }
74
+ }
75
+
76
+ return removedSchema;
77
+ }
78
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/remove-request-body.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+ import visit from 'openapi-transformer-base/visit.js';
9
+
10
+ const defaultMethodSet = new Set([
11
+ // https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.5
12
+ // "A payload within a DELETE request message has no defined semantics"
13
+ // However, I suspect it may not be uncommon for API developers to do so.
14
+ // 'DELETE',
15
+
16
+ // https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.1
17
+ // "A payload within a GET request message has no defined semantics"
18
+ 'get',
19
+
20
+ // https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.2
21
+ // "A payload within a HEAD request message has no defined semantics"
22
+ 'head',
23
+
24
+ // https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.8
25
+ // "A client MUST NOT send a message body in a TRACE request."
26
+ 'trace',
27
+ ]);
28
+ const httpMethodSetSymbol = Symbol('httpMethodSet');
29
+
30
+ /**
31
+ * Transformer to remove requestBody from operations on a given set of HTTP
32
+ * methods.
33
+ *
34
+ * This often occurs due to authoring errors where an operation which expects
35
+ * a request body is copied to one that does not.
36
+ */
37
+ export default class RemoveRequestBodyTransformer
38
+ extends OpenApiTransformerBase {
39
+ /** Constructs a RemoveRequestBodyTransformer for a given set of HTTP methods.
40
+ *
41
+ * @param {!module:globals.Iterable=} methods HTTP Methods for which to
42
+ * remove requestBody. (default: [GET, HEAD, TRACE])
43
+ */
44
+ constructor(methods) {
45
+ super();
46
+ this[httpMethodSetSymbol] =
47
+ methods === undefined ? defaultMethodSet : new Set(
48
+ [...methods].map((method) => method.toLowerCase()),
49
+ );
50
+ }
51
+
52
+ transformPathItem(pathItem) {
53
+ if (typeof pathItem !== 'object'
54
+ || pathItem === null
55
+ || Array.isArray(pathItem)) {
56
+ this.warn('Ignoring non-object Path Item', pathItem);
57
+ return pathItem;
58
+ }
59
+
60
+ const newPathItem = { ...pathItem };
61
+
62
+ const httpMethodSet = this[httpMethodSetSymbol];
63
+ for (const [method, operation] of Object.entries(pathItem)) {
64
+ if (operation !== undefined && httpMethodSet.has(method.toLowerCase())) {
65
+ newPathItem[method] = visit(
66
+ this,
67
+ this.transformOperation,
68
+ method,
69
+ operation,
70
+ );
71
+ }
72
+ }
73
+
74
+ return newPathItem;
75
+ }
76
+
77
+ transformOperation(operation) {
78
+ if (typeof operation !== 'object'
79
+ || operation === null
80
+ || Array.isArray(operation)) {
81
+ this.warn('Ignoring non-object Operation', operation);
82
+ return operation;
83
+ }
84
+
85
+ const { requestBody, ...newOperation } = operation;
86
+
87
+ const { parameters } = operation;
88
+ if (parameters !== undefined) {
89
+ if (!Array.isArray(parameters)) {
90
+ this.transformPath.push('parameters');
91
+ this.warn('Ignoring non-Array Parameters', parameters);
92
+ this.transformPath.pop();
93
+ } else {
94
+ newOperation.parameters = parameters
95
+ .filter((parameter) => !parameter
96
+ || (parameter.in !== 'formData' && parameter.in !== 'body'));
97
+ }
98
+ }
99
+
100
+ return newOperation;
101
+ }
102
+ }