@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.
- package/LICENSE.txt +19 -0
- package/README.md +134 -0
- package/add-tag-to-operation-ids.js +60 -0
- package/add-x-ms-enum-name.js +96 -0
- package/add-x-ms-enum-value-names.js +142 -0
- package/additional-properties-to-object.js +35 -0
- package/additional-properties-to-unconstrained.js +115 -0
- package/any-of-null-to-nullable.js +50 -0
- package/assert-properties.js +56 -0
- package/binary-string-to-file.js +54 -0
- package/bool-enum-to-bool.js +100 -0
- package/clear-html-response-schema.js +77 -0
- package/client-params-to-global.js +97 -0
- package/const-to-enum.js +49 -0
- package/escape-enum-values.js +211 -0
- package/exclusive-min-max-to-bool.js +61 -0
- package/format-to-type.js +54 -0
- package/index.js +94 -0
- package/inline-non-object-schemas.js +120 -0
- package/lib/component-manager.js +60 -0
- package/lib/matching-component-manager.js +74 -0
- package/lib/matching-parameter-manager.js +36 -0
- package/merge-all-of.js +60 -0
- package/merge-any-of.js +48 -0
- package/merge-one-of.js +48 -0
- package/nullable-not-required.js +240 -0
- package/nullable-to-type-null.js +46 -0
- package/openapi31to30.js +54 -0
- package/package.json +131 -0
- package/path-parameters-to-operations.js +63 -0
- package/pattern-properties-to-additional-properties.js +62 -0
- package/queries-to-x-ms-paths.js +63 -0
- package/read-only-not-required.js +111 -0
- package/ref-path-parameters.js +73 -0
- package/remove-default-only-response-produces.js +58 -0
- package/remove-paths-with-servers.js +34 -0
- package/remove-query-from-paths.js +526 -0
- package/remove-ref-siblings.js +78 -0
- package/remove-request-body.js +102 -0
- package/remove-response-headers.js +42 -0
- package/remove-security-scheme-if.js +166 -0
- package/remove-type-if.js +65 -0
- package/rename-components.js +285 -0
- package/replaced-by-to-description.js +50 -0
- package/server-vars-to-path-params.js +224 -0
- package/server-vars-to-x-ms-parameterized-host.js +247 -0
- package/type-null-to-enum.js +47 -0
- package/type-null-to-nullable.js +57 -0
- package/urlencoded-to-string.js +160 -0
- 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
|
+
}
|