@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,42 @@
1
+ /**
2
+ * @copyright Copyright 2019 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/remove-response-headers.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ /**
10
+ * Transformer to remove headers from Response objects in an OpenAPI 3 doc.
11
+ *
12
+ * This can be useful because Autorest generates strongly typed classes for
13
+ * each response type, which can be an annoyance (especially if the default
14
+ * error response has headers), particularly since x-ms-headers is not working
15
+ * correctly: https://github.com/Azure/autorest/pull/3322
16
+ */
17
+ export default class RemoveResponseHeadersTransformer
18
+ extends OpenApiTransformerBase {
19
+ // eslint-disable-next-line class-methods-use-this
20
+ transformResponse(response) {
21
+ if (response.headers) {
22
+ const newResponse = { ...response };
23
+ delete newResponse.headers;
24
+ return newResponse;
25
+ }
26
+
27
+ return response;
28
+ }
29
+
30
+ // Optimization: Only need to transform responses
31
+ transformComponents(components) {
32
+ if (components.responses) {
33
+ return {
34
+ ...components,
35
+ responses:
36
+ this.transformMap(components.responses, this.transformResponse),
37
+ };
38
+ }
39
+
40
+ return components;
41
+ }
42
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ */
5
+
6
+ import OpenApiTransformerBase from 'openapi-transformer-base';
7
+
8
+ const emptyRequirement = {};
9
+ const predicateSymbol = Symbol('predicate');
10
+ const removedSchemesSymbol = Symbol('removedSchemes');
11
+
12
+ /**
13
+ * Transformer to remove security schemes matching a given predicate.
14
+ */
15
+ export default class RemoveSecuritySchemeIfTransformer
16
+ extends OpenApiTransformerBase {
17
+ /** Constructs a new RemoveSecuritySchemeIfTransformer with a given
18
+ * predicate.
19
+ *
20
+ * @param {function(!object)} predicate Predicate which returns true if
21
+ * the given Security Scheme should be removed and false otherwise.
22
+ */
23
+ constructor(predicate) {
24
+ if (typeof predicate !== 'function') {
25
+ throw new TypeError('predicate must be a function');
26
+ }
27
+
28
+ super();
29
+
30
+ this[predicateSymbol] = predicate;
31
+ this[removedSchemesSymbol] = new Set();
32
+ }
33
+
34
+ transformSecuritySchemesEarly(securitySchemes) {
35
+ const removedSchemes = this[removedSchemesSymbol];
36
+
37
+ let isEmpty = true;
38
+ const newSecuritySchemes = {};
39
+ for (const [schemeName, securityScheme]
40
+ of Object.entries(securitySchemes)) {
41
+ if (securityScheme !== undefined) {
42
+ if (this[predicateSymbol](securityScheme)) {
43
+ removedSchemes.add(schemeName);
44
+ } else {
45
+ isEmpty = false;
46
+ newSecuritySchemes[schemeName] = securityScheme;
47
+ }
48
+ }
49
+ }
50
+
51
+ return isEmpty ? undefined : newSecuritySchemes;
52
+ }
53
+
54
+ transformSecurityRequirement(securityRequirement) {
55
+ if (typeof securityRequirement !== 'object'
56
+ || securityRequirement === null) {
57
+ this.warn(
58
+ 'Ignoring non-object Security Requirement',
59
+ securityRequirement,
60
+ );
61
+ return securityRequirement;
62
+ }
63
+
64
+ if (Array.isArray(securityRequirement)) {
65
+ this.warn(
66
+ 'Ignoring non-object Security Requirement',
67
+ securityRequirement,
68
+ );
69
+ return securityRequirement;
70
+ }
71
+
72
+ const removedSchemes = this[removedSchemesSymbol];
73
+ let isEmpty = true;
74
+ let removedAny = false;
75
+ const newSecurityRequirement = {};
76
+ for (const [schemeName, securityScheme]
77
+ of Object.entries(securityRequirement)) {
78
+ if (securityScheme !== undefined) {
79
+ if (removedSchemes.has(schemeName)) {
80
+ removedAny = true;
81
+ } else {
82
+ isEmpty = false;
83
+ newSecurityRequirement[schemeName] = securityScheme;
84
+ }
85
+ }
86
+ }
87
+
88
+ // Return empty marker only if it was made empty by removing scheme(s).
89
+ // Leave already (effectively) empty Security Requirements as-is.
90
+ return removedAny && isEmpty ? emptyRequirement : newSecurityRequirement;
91
+ }
92
+
93
+ transformOperation(operation) {
94
+ const newOperation = super.transformOperation(operation);
95
+
96
+ if (newOperation
97
+ && Array.isArray(newOperation.security)
98
+ && newOperation.security.length > 0) {
99
+ // Remove any empty requirements which were created.
100
+ newOperation.security =
101
+ newOperation.security.filter((sr) => sr !== emptyRequirement);
102
+
103
+ // Remove the whole array if there are no requirements remaining.
104
+ if (newOperation.security.length === 0) {
105
+ delete newOperation.security;
106
+ }
107
+ }
108
+
109
+ return newOperation;
110
+ }
111
+
112
+ transformOpenApi(openApi) {
113
+ let newSecuritySchemes;
114
+ if (openApi
115
+ && openApi.components
116
+ && openApi.components.securitySchemes) {
117
+ newSecuritySchemes =
118
+ this.transformSecuritySchemesEarly(openApi.components.securitySchemes);
119
+ }
120
+
121
+ let newSecurityDefinitions;
122
+ if (openApi && openApi.securityDefinitions) {
123
+ newSecurityDefinitions =
124
+ this.transformSecuritySchemesEarly(openApi.securityDefinitions);
125
+ }
126
+
127
+ if (this[removedSchemesSymbol].size === 0) {
128
+ return openApi;
129
+ }
130
+
131
+ let newOpenApi = super.transformOpenApi(openApi);
132
+ if (newOpenApi === openApi) {
133
+ newOpenApi = { ...newOpenApi };
134
+ }
135
+
136
+ if (Array.isArray(newOpenApi.security)
137
+ && newOpenApi.security.length > 0) {
138
+ // Remove any empty requirements which were created.
139
+ newOpenApi.security =
140
+ newOpenApi.security.filter((sr) => sr !== emptyRequirement);
141
+
142
+ // Remove the whole array if there are no requirements remaining.
143
+ if (newOpenApi.security.length === 0) {
144
+ delete newOpenApi.security;
145
+ }
146
+ }
147
+
148
+ // Remove securitySchemes where appropriate
149
+ if (newOpenApi.components) {
150
+ if (newSecuritySchemes === undefined) {
151
+ delete newOpenApi.components.securitySchemes;
152
+ } else {
153
+ newOpenApi.components.securitySchemes = newSecuritySchemes;
154
+ }
155
+ }
156
+
157
+ // Remove securityDefinitions where appropriate
158
+ if (newSecurityDefinitions === undefined) {
159
+ delete newOpenApi.securityDefinitions;
160
+ } else {
161
+ newOpenApi.securityDefinitions = newSecurityDefinitions;
162
+ }
163
+
164
+ return newOpenApi;
165
+ }
166
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/remove-type-if.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ const predicateSymbol = Symbol('predicate');
10
+
11
+ /** Checks if a given type constraint validates all types other than null.
12
+ *
13
+ * The type constraint may or may not include null.
14
+ *
15
+ * This predicate is useful for conversion of schemas which explicitly accept
16
+ * any type (and are only valid in OAS 3.1 and JSON Schema) to implicitly
17
+ * accept any type (which are valid in all OAS and JSON Schema versions).
18
+ * "null" is excluded because it must be handled by `nullable` in OAS 3
19
+ * (and `x-nullable` in OAS 2).
20
+ *
21
+ * @param {string|!Array<string>} type Schema Object type.
22
+ * @returns {boolean} true if type is an Array which contains the 5 JSON
23
+ * Schema types other than "null" (handled by `nullable`) and "integer"
24
+ * (covered by "number"), which may or may not be present. Otherwise false.
25
+ */
26
+ export function allNonNullTypes(type) {
27
+ return Array.isArray(type)
28
+ && type.length >= 5
29
+ && [
30
+ 'array',
31
+ 'boolean',
32
+ 'number',
33
+ 'object',
34
+ 'string',
35
+ ].every((t) => type.includes(t));
36
+ }
37
+
38
+ /**
39
+ * Transformer to remove `type` from Schema Objects where `type` matches a
40
+ * given predicate.
41
+ */
42
+ export default class RemoveTypeIfTransformer extends OpenApiTransformerBase {
43
+ constructor(predicate) {
44
+ super();
45
+
46
+ if (typeof predicate !== 'function') {
47
+ throw new TypeError('predicate must be a function');
48
+ }
49
+
50
+ this[predicateSymbol] = predicate;
51
+ }
52
+
53
+ transformSchema(schema) {
54
+ const newSchema = super.transformSchema(schema);
55
+ if (newSchema === null
56
+ || typeof newSchema !== 'object'
57
+ || Array.isArray(newSchema)
58
+ || newSchema.type === undefined) {
59
+ return newSchema;
60
+ }
61
+
62
+ const { type, ...schemaNoType } = newSchema;
63
+ return this[predicateSymbol](type) ? schemaNoType : newSchema;
64
+ }
65
+ }
@@ -0,0 +1,285 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/rename-components.js"
5
+ */
6
+
7
+ // Note: Undocumented functions which are part of the public API:
8
+ // https://github.com/flitbit/json-ptr/issues/29
9
+ import {
10
+ decodeUriFragmentIdentifier,
11
+ encodeUriFragmentIdentifier,
12
+ } from 'json-ptr';
13
+ import OpenApiTransformerBase from 'openapi-transformer-base';
14
+
15
+ const renameFuncsSymbol = Symbol('renameFuncs');
16
+
17
+ // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#fixed-fields-6
18
+ const componentFieldNames = [
19
+ 'schemas',
20
+ 'responses',
21
+ 'parameters',
22
+ 'examples',
23
+ 'requestBodies',
24
+ 'headers',
25
+ 'securitySchemes',
26
+ 'links',
27
+ 'callbacks',
28
+ 'pathItems',
29
+ ];
30
+
31
+ function renameProps(obj, renameFunc) {
32
+ if (!renameFunc
33
+ || typeof obj !== 'object'
34
+ || obj === null
35
+ || Array.isArray(obj)) {
36
+ return obj;
37
+ }
38
+
39
+ const newObj = {};
40
+ for (const [key, value] of Object.entries(obj)) {
41
+ const newKey = renameFunc(key);
42
+ if (Object.hasOwn(newObj, newKey)) {
43
+ throw new Error(
44
+ `New name ${newKey} for ${key} conflicts with existing name`,
45
+ );
46
+ }
47
+ newObj[newKey] = value;
48
+ }
49
+
50
+ return newObj;
51
+ }
52
+
53
+ function isJsonRef(obj) {
54
+ return obj && typeof obj.$ref === 'string';
55
+ }
56
+
57
+ function renameRefObj(obj, getPathIndex, renameFunc) {
58
+ if (!renameFunc) {
59
+ return obj;
60
+ }
61
+
62
+ const { $ref } = obj;
63
+ if ($ref[0] !== '#') {
64
+ return obj;
65
+ }
66
+
67
+ let tokens;
68
+ try {
69
+ tokens = decodeUriFragmentIdentifier($ref);
70
+ } catch {
71
+ return obj;
72
+ }
73
+
74
+ const renameInd = getPathIndex(tokens);
75
+ if (renameInd < 0 || renameInd >= tokens.length) {
76
+ return obj;
77
+ }
78
+
79
+ const name = tokens[renameInd];
80
+ const newName = renameFunc(name);
81
+ if (newName !== name) {
82
+ tokens[renameInd] = newName;
83
+ return {
84
+ ...obj,
85
+ $ref: encodeUriFragmentIdentifier(tokens),
86
+ };
87
+ }
88
+
89
+ return obj;
90
+ }
91
+
92
+ /**
93
+ * Transformer to rename components.
94
+ */
95
+ export default class RenameComponentsTransformer
96
+ extends OpenApiTransformerBase {
97
+ constructor(options) {
98
+ super();
99
+
100
+ if (typeof options !== 'object' || options === null) {
101
+ throw new TypeError('options must be an object');
102
+ }
103
+
104
+ const renameFuncs = {};
105
+ for (const componentFieldName of componentFieldNames) {
106
+ const renameFuncOrMap = options[componentFieldName];
107
+ if (typeof renameFuncOrMap === 'function') {
108
+ renameFuncs[componentFieldName] = renameFuncOrMap;
109
+ } else if (typeof renameFuncOrMap === 'object') {
110
+ const patterns = Object.entries(renameFuncOrMap)
111
+ .map(([k, v]) => [new RegExp(k, 'g'), v]);
112
+ renameFuncs[componentFieldName] = (name) => {
113
+ for (const [pat, repl] of patterns) {
114
+ if (pat.test(name)) {
115
+ return name.replace(pat, repl);
116
+ }
117
+ }
118
+ return name;
119
+ };
120
+ } else if (renameFuncOrMap !== undefined) {
121
+ throw new TypeError(`options.${componentFieldName} must be a function`);
122
+ }
123
+ }
124
+
125
+ this[renameFuncsSymbol] = renameFuncs;
126
+ }
127
+
128
+ transformExample3(example) {
129
+ if (isJsonRef(example)) {
130
+ return renameRefObj(
131
+ example,
132
+ (pp) => (pp[0] === 'components' && pp[1] === 'examples' ? 2 : -1),
133
+ this[renameFuncsSymbol].examples,
134
+ );
135
+ }
136
+
137
+ return super.transformExample3(example);
138
+ }
139
+
140
+ transformSchema(schema) {
141
+ if (isJsonRef(schema)) {
142
+ return renameRefObj(
143
+ schema,
144
+ (pp) => (pp[0] === 'components' && pp[1] === 'schemas' ? 2
145
+ : pp[0] === 'definitions' ? 1 : -1),
146
+ this[renameFuncsSymbol].schemas,
147
+ );
148
+ }
149
+
150
+ return super.transformSchema(schema);
151
+ }
152
+
153
+ transformHeader(header) {
154
+ if (isJsonRef(header)) {
155
+ return renameRefObj(
156
+ header,
157
+ (pp) => (pp[0] === 'components' && pp[1] === 'headers' ? 2 : -1),
158
+ this[renameFuncsSymbol].headers,
159
+ );
160
+ }
161
+
162
+ return super.transformHeader(header);
163
+ }
164
+
165
+ transformLink(link) {
166
+ if (isJsonRef(link)) {
167
+ return renameRefObj(
168
+ link,
169
+ (pp) => (pp[0] === 'components' && pp[1] === 'links' ? 2 : -1),
170
+ this[renameFuncsSymbol].links,
171
+ );
172
+ }
173
+
174
+ return super.transformLink(link);
175
+ }
176
+
177
+ transformResponse(response) {
178
+ if (isJsonRef(response)) {
179
+ return renameRefObj(
180
+ response,
181
+ (pp) => (pp[0] === 'components' && pp[1] === 'responses' ? 2 : -1),
182
+ this[renameFuncsSymbol].responses,
183
+ );
184
+ }
185
+
186
+ return super.transformResponse(response);
187
+ }
188
+
189
+ transformParameter(parameter) {
190
+ if (isJsonRef(parameter)) {
191
+ return renameRefObj(
192
+ parameter,
193
+ (pp) => (pp[0] === 'components' && pp[1] === 'parameters' ? 2 : -1),
194
+ this[renameFuncsSymbol].parameters,
195
+ );
196
+ }
197
+
198
+ return super.transformParameter(parameter);
199
+ }
200
+
201
+ transformCallback(callback) {
202
+ if (isJsonRef(callback)) {
203
+ return renameRefObj(
204
+ callback,
205
+ (pp) => (pp[0] === 'components' && pp[1] === 'callbacks' ? 2 : -1),
206
+ this[renameFuncsSymbol].callbacks,
207
+ );
208
+ }
209
+
210
+ return super.transformCallback(callback);
211
+ }
212
+
213
+ transformRequestBody(requestBody) {
214
+ if (isJsonRef(requestBody)) {
215
+ return renameRefObj(
216
+ requestBody,
217
+ (pp) => (pp[0] === 'components' && pp[1] === 'requestBodies' ? 2 : -1),
218
+ this[renameFuncsSymbol].requestBodies,
219
+ );
220
+ }
221
+
222
+ return super.transformRequestBody(requestBody);
223
+ }
224
+
225
+ transformSecurityRequirement(securityRequirement) {
226
+ return renameProps(
227
+ super.transformSecurityRequirement(securityRequirement),
228
+ this[renameFuncsSymbol].securitySchemes,
229
+ );
230
+ }
231
+
232
+ transformPathItem(pathItem) {
233
+ if (isJsonRef(pathItem)) {
234
+ return renameRefObj(
235
+ pathItem,
236
+ (pp) => (pp[0] === 'components' && pp[1] === 'pathItems' ? 2 : -1),
237
+ this[renameFuncsSymbol].pathItems,
238
+ );
239
+ }
240
+
241
+ return super.transformPathItem(pathItem);
242
+ }
243
+
244
+ transformComponents(components) {
245
+ components = super.transformComponents(components);
246
+
247
+ if (typeof components !== 'object'
248
+ || components === null
249
+ || Array.isArray(components)) {
250
+ return components;
251
+ }
252
+
253
+ const newComponents = { ...components };
254
+ for (const [propName, renameFunc]
255
+ of Object.entries(this[renameFuncsSymbol])) {
256
+ const propValue = components[propName];
257
+ if (propValue) {
258
+ newComponents[propName] = renameProps(propValue, renameFunc);
259
+ }
260
+ }
261
+
262
+ return newComponents;
263
+ }
264
+
265
+ transformOpenApi(openApi) {
266
+ openApi = super.transformOpenApi(openApi);
267
+
268
+ if (typeof openApi !== 'object'
269
+ || openApi === null
270
+ || Array.isArray(openApi)) {
271
+ return openApi;
272
+ }
273
+
274
+ const { definitions } = openApi;
275
+ const renameSchemas = this[renameFuncsSymbol].schemas;
276
+ if (definitions && renameSchemas) {
277
+ openApi = {
278
+ ...openApi,
279
+ definitions: renameProps(definitions, renameSchemas),
280
+ };
281
+ }
282
+
283
+ return openApi;
284
+ }
285
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @copyright Copyright 2019 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/replaced-by-to-description.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ function transformXDeprecated(schema) {
10
+ const xDeprecated = schema['x-deprecated'];
11
+ if (xDeprecated
12
+ && !Object.hasOwn(xDeprecated, 'description')
13
+ && Object.hasOwn(xDeprecated, 'replaced-by')) {
14
+ return {
15
+ ...schema,
16
+ 'x-deprecated': {
17
+ description: `Use ${xDeprecated['replaced-by']} instead.`,
18
+ ...xDeprecated,
19
+ },
20
+ };
21
+ }
22
+
23
+ return schema;
24
+ }
25
+
26
+ /**
27
+ * Transformer to convert x-deprecated.replaced-by to x-deprecated.description,
28
+ * if not present.
29
+ * https://github.com/Azure/autorest/tree/master/Samples/test/deprecated
30
+ *
31
+ * Since Autorest C# doesn't use replaced-by, but does use description.
32
+ */
33
+ export default class ReplacedByToDescriptionTransformer
34
+ extends OpenApiTransformerBase {
35
+ transformSchema(schema) {
36
+ return transformXDeprecated(super.transformSchema(schema));
37
+ }
38
+
39
+ transformParameter(parameter) {
40
+ return transformXDeprecated(super.transformParameter(parameter));
41
+ }
42
+
43
+ transformHeader(header) {
44
+ return transformXDeprecated(super.transformHeader(header));
45
+ }
46
+
47
+ transformOperation(operation) {
48
+ return transformXDeprecated(super.transformOperation(operation));
49
+ }
50
+ }