@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,240 @@
1
+ /**
2
+ * @copyright Copyright 2019-2024 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/nullable-not-required.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ /**
10
+ * Determines whether a given Schema can have a null value.
11
+ *
12
+ * @param {!object} schema OpenAPI Schema object.
13
+ * @param {boolean} hasNullType Whether 'null' is a valid Schema type.
14
+ * @param {boolean} refNullable Treat $ref as nullable.
15
+ * @returns {boolean} <c>true</c> if <c>null</c> is a valid value for
16
+ * <c>schema</c>.
17
+ */
18
+ function isNullable(schema, hasNullType, refNullable) {
19
+ const {
20
+ $ref,
21
+ nullable,
22
+ 'x-nullable': xNullable,
23
+ type,
24
+ } = schema;
25
+
26
+ if ($ref) {
27
+ if (typeof nullable === 'boolean') {
28
+ return nullable;
29
+ }
30
+
31
+ if (typeof xNullable === 'boolean') {
32
+ return xNullable;
33
+ }
34
+
35
+ return refNullable;
36
+ }
37
+
38
+ if (hasNullType) {
39
+ if (typeof type === 'string') {
40
+ if (type !== 'null') {
41
+ // null forbidden by non-null type
42
+ return false;
43
+ }
44
+ } else if (Array.isArray(type)
45
+ && !type.includes('null')) {
46
+ // null forbidden by null not in types
47
+ return false;
48
+ }
49
+ } else if (!nullable && !xNullable) {
50
+ // Without null type, null is only allowed with nullable (or x-nullable)
51
+ return false;
52
+ }
53
+
54
+ return true;
55
+ }
56
+
57
+ /**
58
+ * Determines whether a given property of a given schema can have a null value.
59
+ *
60
+ * @param {!object} schema OpenAPI Schema object.
61
+ * @param {string} propName Name of property to check.
62
+ * @param {boolean} hasNullType Whether 'null' is a valid Schema type.
63
+ * @param {boolean} refNullable Treat $ref as nullable.
64
+ * @returns {?boolean} <c>true</c> if <c>null</c> is known to be a valid value
65
+ * for <c>propName</c> in <c>schema</c>, <c>false</c> if <c>null</c> is known
66
+ * to be an invalid value for <c>propName</c> in <c>schema</c>.
67
+ * <c>undefined</c> if <c>propName</c> is not constrained by <c>schema</c>.
68
+ */
69
+ function isPropNullable(schema, propName, hasNullType, refNullable) {
70
+ const {
71
+ additionalProperties,
72
+ allOf,
73
+ anyOf,
74
+ oneOf,
75
+ properties,
76
+ } = schema;
77
+ let constrained = false;
78
+ const propSchema = properties?.[propName];
79
+ if (propSchema) {
80
+ constrained = true;
81
+ if (!isNullable(propSchema, hasNullType, refNullable)) {
82
+ return false;
83
+ }
84
+ } else if (additionalProperties) {
85
+ constrained = true;
86
+ if (!isNullable(additionalProperties, hasNullType, refNullable)) {
87
+ // schema in additionalProperties does not allow null
88
+ return false;
89
+ }
90
+ } else if (additionalProperties === false) {
91
+ // would fail validation if property is present
92
+ return false;
93
+ }
94
+
95
+ if (Array.isArray(allOf)) {
96
+ for (const allSchema of allOf) {
97
+ switch (isPropNullable(allSchema, propName, hasNullType, refNullable)) {
98
+ case false:
99
+ // If an allOf schema doesn't allow null, it's not allowed.
100
+ return false;
101
+
102
+ case true:
103
+ // An allOf schema allows null
104
+ constrained = true;
105
+ break;
106
+
107
+ default:
108
+ // An allOf schema doesn't constrain propName
109
+ break;
110
+ }
111
+ }
112
+ }
113
+
114
+ if (Array.isArray(anyOf)) {
115
+ let anyNullable = false;
116
+ let anyUnconstrained = false;
117
+ for (const anySchema of anyOf) {
118
+ switch (isPropNullable(anySchema, propName, hasNullType, refNullable)) {
119
+ case false:
120
+ // An anyOf schema disallows null
121
+ break;
122
+
123
+ case true:
124
+ // An anyOf schema allows null
125
+ anyNullable = true;
126
+ break;
127
+
128
+ default:
129
+ // An anyOf schema doesn't constrain propName
130
+ anyUnconstrained = true;
131
+ break;
132
+ }
133
+ }
134
+
135
+ if (!anyNullable && !anyUnconstrained) {
136
+ return false;
137
+ }
138
+
139
+ if (!anyUnconstrained) {
140
+ constrained = true;
141
+ }
142
+ }
143
+
144
+ if (Array.isArray(oneOf)) {
145
+ let anyNullable = false;
146
+ let anyUnconstrained = false;
147
+ for (const oneSchema of oneOf) {
148
+ switch (isPropNullable(oneSchema, propName, hasNullType, refNullable)) {
149
+ case false:
150
+ // A oneOf schema disallows null
151
+ break;
152
+
153
+ case true:
154
+ // A oneOf schema allows null
155
+ anyNullable = true;
156
+ break;
157
+
158
+ default:
159
+ // A oneOf schema doesn't constrain propName
160
+ anyUnconstrained = true;
161
+ break;
162
+ }
163
+ }
164
+
165
+ if (!anyNullable && !anyUnconstrained) {
166
+ return false;
167
+ }
168
+
169
+ if (!anyUnconstrained) {
170
+ constrained = true;
171
+ }
172
+ }
173
+
174
+ return constrained ? true : undefined;
175
+ }
176
+
177
+ /**
178
+ * Transformer to make properties which are nullable non-required to work around
179
+ * partial support for x-nullable in Autorest. Currently nullable data types
180
+ * are used (e.g. Nullable<int>) but the Validate() method doesn't allow null.
181
+ * See: https://github.com/Azure/autorest/issues/3300
182
+ */
183
+ export default class NullableNotRequiredTransformer
184
+ extends OpenApiTransformerBase {
185
+ hasNullType = undefined;
186
+
187
+ refNullable = false;
188
+
189
+ requireUnconstrained = false;
190
+
191
+ constructor(options) {
192
+ super();
193
+ this.refNullable = Boolean(options?.refNullable);
194
+ this.requireUnconstrained = Boolean(options?.requireUnconstrained);
195
+ }
196
+
197
+ transformSchema(schema) {
198
+ const newSchema = super.transformSchema(schema);
199
+ if (!Array.isArray(newSchema?.required)
200
+ || newSchema.required.length === 0) {
201
+ // No properties are required. None to remove.
202
+ return newSchema;
203
+ }
204
+
205
+ const transformed = {
206
+ ...newSchema,
207
+ required: newSchema.required
208
+ .filter(
209
+ (reqName) => !(isPropNullable(
210
+ newSchema,
211
+ reqName,
212
+ this.hasNullType,
213
+ this.refNullable,
214
+ ) ?? !this.requireUnconstrained),
215
+ ),
216
+ };
217
+
218
+ if (transformed.required.length === 0) {
219
+ delete transformed.required;
220
+ }
221
+
222
+ return transformed;
223
+ }
224
+
225
+ transformOpenApi(openApi) {
226
+ const version = openApi?.openapi || openApi?.swagger;
227
+ const verParts = version ? String(version).split('.').map(Number) : [];
228
+ if (verParts[0] > 3 || (verParts[0] === 3 && verParts[1] >= 1)) {
229
+ this.hasNullType = true;
230
+ } else if (verParts[0] < 3 || (verParts[0] === 3 && verParts[1] < 1)) {
231
+ this.hasNullType = false;
232
+ }
233
+
234
+ try {
235
+ return super.transformOpenApi(openApi);
236
+ } finally {
237
+ this.hasNullType = undefined;
238
+ }
239
+ }
240
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/nullable-to-type-null.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ /**
10
+ * Transformer to add `'null'` to `type` of Schema Objects with
11
+ * `nullable: true` or `x-nullable: true`.
12
+ *
13
+ * This is useful for converting from OpenAPI 2.0/3.0 to OpenAPI 3.1 or
14
+ * JSON Schema.
15
+ */
16
+ export default class NullableToTypeNullTransformer
17
+ extends OpenApiTransformerBase {
18
+ transformSchema(schema) {
19
+ schema = super.transformSchema(schema);
20
+ if (schema === null
21
+ || typeof schema !== 'object'
22
+ || Array.isArray(schema)) {
23
+ return schema;
24
+ }
25
+
26
+ const {
27
+ nullable,
28
+ 'x-nullable': xNullable,
29
+ ...schemaNoNullable
30
+ } = schema;
31
+ if (nullable !== true && xNullable !== true) {
32
+ return schema;
33
+ }
34
+
35
+ const { type } = schema;
36
+ if (Array.isArray(type)) {
37
+ if (!type.includes('null')) {
38
+ schemaNoNullable.type = [...type, 'null'];
39
+ }
40
+ } else if (type !== undefined) {
41
+ schemaNoNullable.type = [type, 'null'];
42
+ }
43
+
44
+ return schemaNoNullable;
45
+ }
46
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/openapi31to30.js"
5
+ */
6
+
7
+ import AnyOfNullToNullableTransformer from './any-of-null-to-nullable.js';
8
+ import ConstToEnumTransformer from './const-to-enum.js';
9
+ import ExclusiveMinMaxToBoolTransformer from './exclusive-min-max-to-bool.js';
10
+ import PatternPropertiesToAdditionalPropertiesTransformer
11
+ from './pattern-properties-to-additional-properties.js';
12
+ import RemoveTypeIfTransformer, { allNonNullTypes } from './remove-type-if.js';
13
+ import TypeNullToEnumTransformer from './type-null-to-enum.js';
14
+ import TypeNullToNullableTransformer from './type-null-to-nullable.js';
15
+
16
+ /**
17
+ * Transformer to convert an OpenAPI 3.1.* document to OpenAPI 3.0.3.
18
+ */
19
+ export default class OpenApi31To30Transformer {
20
+ constructor() {
21
+ this.transformers = [
22
+ new ExclusiveMinMaxToBoolTransformer(),
23
+ new ConstToEnumTransformer(),
24
+ new AnyOfNullToNullableTransformer(),
25
+ new TypeNullToEnumTransformer(),
26
+ new TypeNullToNullableTransformer(),
27
+ new RemoveTypeIfTransformer(allNonNullTypes),
28
+ new PatternPropertiesToAdditionalPropertiesTransformer(),
29
+ ];
30
+ }
31
+
32
+ transformOpenApi(openApi) {
33
+ if (typeof openApi !== 'object'
34
+ || openApi === null
35
+ || Array.isArray(openApi)) {
36
+ this.warn('Ignoring non-object OpenAPI', openApi);
37
+ return openApi;
38
+ }
39
+
40
+ if (typeof openApi.openapi !== 'string'
41
+ || !openApi.openapi.startsWith('3.1.')) {
42
+ this.warn('Expected OpenAPI 3.1, got', openApi.openapi);
43
+ }
44
+
45
+ for (const transformer of this.transformers) {
46
+ openApi = transformer.transformOpenApi(openApi);
47
+ }
48
+
49
+ return {
50
+ ...openApi,
51
+ openapi: '3.0.3',
52
+ };
53
+ }
54
+ }
package/package.json ADDED
@@ -0,0 +1,131 @@
1
+ {
2
+ "name": "@kevinoid/openapi-transformers",
3
+ "version": "0.1.0",
4
+ "description": "A collection of classes for transforming OpenAPI documents, particularly for compatibility with code generators like Autorest and OpenAPI Generator.",
5
+ "keywords": [
6
+ "autorest",
7
+ "openapi",
8
+ "openapi-transformer",
9
+ "swagger"
10
+ ],
11
+ "license": "MIT",
12
+ "homepage": "https://github.com/kevinoid/openapi-transformers",
13
+ "bugs": "https://github.com/kevinoid/openapi-transformers/issues",
14
+ "author": "Kevin Locke <kevin@kevinlocke.name>",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/kevinoid/openapi-transformers.git"
18
+ },
19
+ "type": "module",
20
+ "files": [
21
+ "*.js",
22
+ "!eslint.config.js",
23
+ "lib/",
24
+ "!**/.*"
25
+ ],
26
+ "exports": {
27
+ ".": "./index.js",
28
+ "./additional-properties-to-object.js": "./additional-properties-to-object.js",
29
+ "./additional-properties-to-unconstrained.js": "./additional-properties-to-unconstrained.js",
30
+ "./add-tag-to-operation-ids.js": "./add-tag-to-operation-ids.js",
31
+ "./add-x-ms-enum-name.js": "./add-x-ms-enum-name.js",
32
+ "./add-x-ms-enum-value-names.js": "./add-x-ms-enum-value-names.js",
33
+ "./any-of-null-to-nullable.js": "./any-of-null-to-nullable.js",
34
+ "./assert-properties.js": "./assert-properties.js",
35
+ "./binary-string-to-file.js": "./binary-string-to-file.js",
36
+ "./bool-enum-to-bool.js": "./bool-enum-to-bool.js",
37
+ "./clear-html-response-schema.js": "./clear-html-response-schema.js",
38
+ "./client-params-to-global.js": "./client-params-to-global.js",
39
+ "./const-to-enum.js": "./const-to-enum.js",
40
+ "./escape-enum-values.js": "./escape-enum-values.js",
41
+ "./exclusive-min-max-to-bool.js": "./exclusive-min-max-to-bool.js",
42
+ "./format-to-type.js": "./format-to-type.js",
43
+ "./inline-non-object-schemas.js": "./inline-non-object-schemas.js",
44
+ "./merge-all-of.js": "./merge-all-of.js",
45
+ "./merge-any-of.js": "./merge-any-of.js",
46
+ "./merge-one-of.js": "./merge-one-of.js",
47
+ "./nullable-not-required.js": "./nullable-not-required.js",
48
+ "./nullable-to-type-null.js": "./nullable-to-type-null.js",
49
+ "./openapi31to30.js": "./openapi31to30.js",
50
+ "./package.json": "./package.json",
51
+ "./path-parameters-to-operations.js": "./path-parameters-to-operations.js",
52
+ "./pattern-properties-to-additional-properties.js": "./pattern-properties-to-additional-properties.js",
53
+ "./queries-to-x-ms-paths.js": "./queries-to-x-ms-paths.js",
54
+ "./read-only-not-required.js": "./read-only-not-required.js",
55
+ "./ref-path-parameters.js": "./ref-path-parameters.js",
56
+ "./remove-default-only-response-produces.js": "./remove-default-only-response-produces.js",
57
+ "./remove-paths-with-servers.js": "./remove-paths-with-servers.js",
58
+ "./remove-query-from-paths.js": "./remove-query-from-paths.js",
59
+ "./remove-ref-siblings.js": "./remove-ref-siblings.js",
60
+ "./remove-request-body.js": "./remove-request-body.js",
61
+ "./remove-response-headers.js": "./remove-response-headers.js",
62
+ "./remove-security-scheme-if.js": "./remove-security-scheme-if.js",
63
+ "./remove-type-if.js": "./remove-type-if.js",
64
+ "./rename-components.js": "./rename-components.js",
65
+ "./replaced-by-to-description.js": "./replaced-by-to-description.js",
66
+ "./server-vars-to-path-params.js": "./server-vars-to-path-params.js",
67
+ "./server-vars-to-x-ms-parameterized-host.js": "./server-vars-to-x-ms-parameterized-host.js",
68
+ "./type-null-to-enum.js": "./type-null-to-enum.js",
69
+ "./type-null-to-nullable.js": "./type-null-to-nullable.js",
70
+ "./urlencoded-to-string.js": "./urlencoded-to-string.js",
71
+ "./x-enum-to-ms.js": "./x-enum-to-ms.js"
72
+ },
73
+ "//": "All scripts should run in POSIX sh and Windows cmd.exe",
74
+ "scripts": {
75
+ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -u",
76
+ "clean": "rimraf coverage && rimraf doc",
77
+ "doc": "npm run doc-js && npm run doc-spec",
78
+ "doc-js": "rimraf doc/api && jsdoc -c jsdoc.conf.json .",
79
+ "doc-spec": "rimraf doc/spec && mkdir doc/spec && mocha --reporter doc --recursive test | nodecat doc-src/spec/header.xhtml - doc-src/spec/footer.xhtml > doc/spec/index.xhtml",
80
+ "lint": "npm run lint-js && npm run lint-doc",
81
+ "lint-doc": "jsdoc -t templates/silent -c jsdoc-lint.conf.json . && echo JSDoc passed.",
82
+ "lint-js": "eslint --report-unused-disable-directives . && echo ESLint passed.",
83
+ "release": "git -C doc push && git push --follow-tags origin main gh-pages && echo \"Create a GitHub Release (from CHANGELOG.md) to run publish workflow.\"",
84
+ "postversion": "rimraf doc && git clone -b gh-pages -l -q . doc && npm run doc && git -C doc add . && git -C doc commit -n -m \"Docs for v$npm_package_version\"",
85
+ "preversion": "node ./scripts/preversion.js && npm run test-cov && c8 check-coverage --statements 85 --lines 85 && depcheck --ignore-dirs doc --ignores=\"eslint-*,rimraf\" && david && git-branch-is main && hub-ci-status -vv --wait",
86
+ "test": "npm run lint && npm run test-unit",
87
+ "test-cov": "npm run lint && npm run test-unit-cov",
88
+ "test-unit": "node --disable-proto=throw --throw-deprecation --unhandled-rejections=strict node_modules/mocha/bin/mocha.js --parallel --recursive test",
89
+ "test-unit-cov": "c8 --reporter=lcov --reporter=text npm run test-unit",
90
+ "version": "npm run changelog && echo && echo === Please edit CHANGELOG.md as desired, then exit === && echo && \"${npm_config_shell:-${SHELL:-bash}}\" && git commit -m \"Update CHANGELOG.md for $npm_package_version\" CHANGELOG.md",
91
+ "version-deps": "npm install conventional-changelog-cli david depcheck git-branch-is hub-ci-status"
92
+ },
93
+ "dependencies": {
94
+ "@kevinoid/dotnet-identifier-case": "^1.0.0",
95
+ "fast-json-stable-stringify": "^2.1.0",
96
+ "json-ptr": "^3.0.1",
97
+ "json-schema-intersect": "^0.1.0",
98
+ "openapi-transformer-base": "^1.1.0"
99
+ },
100
+ "devDependencies": {
101
+ "@kevinoid/eslint-config": "^34.0.0",
102
+ "c8": "^11.0.0",
103
+ "deep-freeze": "^0.0.1",
104
+ "eslint": "^9.35.0",
105
+ "globals": "^17.0.0",
106
+ "jsdoc": "^4.0.0",
107
+ "mocha": "^11.0.1",
108
+ "nodecat": "^3.0.0",
109
+ "rimraf": "^6.1.2"
110
+ },
111
+ "engines": {
112
+ "node": "20 || >=22",
113
+ "npm": ">=1.3.7"
114
+ },
115
+ "mocha": {
116
+ "checkLeaks": true,
117
+ "exit": false
118
+ },
119
+ "c8": {
120
+ "exclude": [
121
+ "test",
122
+ "test-lib"
123
+ ]
124
+ },
125
+ "david": {
126
+ "// eslint": "Must match version from peerDependencies of @kevinoid/eslint-plugin",
127
+ "ignore": [
128
+ "eslint"
129
+ ]
130
+ }
131
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @copyright Copyright 2019 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/path-parameters-to-operations.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ const PATH_METHODS = [
10
+ 'delete',
11
+ 'get',
12
+ 'head',
13
+ 'options',
14
+ 'patch',
15
+ 'post',
16
+ 'put',
17
+ 'trace',
18
+ ];
19
+
20
+ /**
21
+ * Transformer to move parameters defined on Path Item Objects to the beginning
22
+ * of the parameters array of the Operation Objects so that they will be
23
+ * generated in the desired order (path-first).
24
+ */
25
+ export default class PathParametersToOperationTransformer
26
+ extends OpenApiTransformerBase {
27
+ // eslint-disable-next-line class-methods-use-this
28
+ transformPathItem(pathItem) {
29
+ if (!Array.isArray(pathItem.parameters)
30
+ || pathItem.parameters.length === 0) {
31
+ return pathItem;
32
+ }
33
+
34
+ const {
35
+ parameters,
36
+ ...newPathItem
37
+ } = pathItem;
38
+ for (const method of PATH_METHODS) {
39
+ if (Object.hasOwn(pathItem, method)) {
40
+ const operation = pathItem[method];
41
+ const opParams = operation.parameters;
42
+ newPathItem[method] = {
43
+ ...operation,
44
+ // TODO: Operation parameters can override path parameters (where
45
+ // .name and .in match). Either can be $ref. Handle these.
46
+ parameters:
47
+ !Array.isArray(opParams) || opParams.length === 0 ? parameters
48
+ : [...parameters, ...opParams],
49
+ };
50
+ }
51
+ }
52
+
53
+ return newPathItem;
54
+ }
55
+
56
+ // Performance optimization: Only need to transform paths.
57
+ transformOpenApi(spec) {
58
+ return {
59
+ ...spec,
60
+ paths: this.transformPaths(spec.paths),
61
+ };
62
+ }
63
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/pattern-properties-to-additional-properties.js"
5
+ */
6
+
7
+ import { isDeepStrictEqual } from 'node:util';
8
+
9
+ import OpenApiTransformerBase from 'openapi-transformer-base';
10
+
11
+ /**
12
+ * Transformer to combine any patternProperties into additionalProperties (for
13
+ * conversion from OpenAPI 3.1 to prior versions before patternProperties
14
+ * was supported).
15
+ * https://github.com/OAI/OpenAPI-Specification/issues/687
16
+ */
17
+ export default class PatternPropertiesToAdditionalPropertiesTransformer
18
+ extends OpenApiTransformerBase {
19
+ transformSchema(schema) {
20
+ if (typeof schema !== 'object' || schema === null) {
21
+ return schema;
22
+ }
23
+
24
+ schema = super.transformSchema(schema);
25
+
26
+ const {
27
+ patternProperties,
28
+ ...newSchema
29
+ } = schema;
30
+ if (typeof patternProperties !== 'object' || patternProperties === null) {
31
+ return schema;
32
+ }
33
+
34
+ const uniquePropSchemas = [];
35
+ for (const propSchema of Object.values(patternProperties)) {
36
+ if (propSchema !== undefined
37
+ && !uniquePropSchemas.some((s) => isDeepStrictEqual(s, propSchema))) {
38
+ uniquePropSchemas.push(propSchema);
39
+ }
40
+ }
41
+
42
+ if (uniquePropSchemas.length === 0) {
43
+ // Remove empty patternProperties object.
44
+ return newSchema;
45
+ }
46
+
47
+ const { additionalProperties } = schema;
48
+ if (additionalProperties !== undefined
49
+ && !uniquePropSchemas
50
+ .some((s) => isDeepStrictEqual(s, additionalProperties))) {
51
+ uniquePropSchemas.push(additionalProperties);
52
+ }
53
+
54
+ if (uniquePropSchemas.length === 1) {
55
+ newSchema.additionalProperties = uniquePropSchemas[0];
56
+ } else {
57
+ newSchema.additionalProperties = { anyOf: uniquePropSchemas };
58
+ }
59
+
60
+ return newSchema;
61
+ }
62
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @copyright Copyright 2019 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/queries-to-x-ms-paths.js"
5
+ */
6
+
7
+ import assert from 'node:assert';
8
+ import { debuglog } from 'node:util';
9
+
10
+ import OpenApiTransformerBase from 'openapi-transformer-base';
11
+
12
+ const debug = debuglog('openapi-transformers:queries-to-x-ms-paths');
13
+
14
+ /**
15
+ * Transformer to move paths with query parameters from paths to x-ms-paths for
16
+ * Autorest.
17
+ *
18
+ * https://github.com/Azure/autorest/tree/master/docs/extensions#x-ms-paths
19
+ *
20
+ * This is used to work around lack of support for multiple operations with the
21
+ * same path and method:
22
+ * https://github.com/OAI/OpenAPI-Specification/issues/791
23
+ *
24
+ * Putting query in path is not allowed by any current version of OpenAPI
25
+ * https://github.com/OAI/OpenAPI-Specification/issues/468#issuecomment-142393969
26
+ * but it has the benefit of working with lots of tooling without support for
27
+ * x-ms-paths (e.g. for linting, docs generation, etc.)
28
+ */
29
+ export default class QueriesToXMsPathsTransformer
30
+ extends OpenApiTransformerBase {
31
+ // Override as performance optimization, since only transforming paths
32
+ // eslint-disable-next-line class-methods-use-this
33
+ transformOpenApi(spec) {
34
+ if (!spec || !spec.paths) {
35
+ return spec;
36
+ }
37
+
38
+ const queryPaths = Object.keys(spec.paths)
39
+ .filter((path) => path.includes('?'));
40
+
41
+ if (queryPaths.length === 0) {
42
+ return spec;
43
+ }
44
+
45
+ const paths = { ...spec.paths };
46
+ const xMsPaths = { ...spec['x-ms-paths'] };
47
+ for (const path of queryPaths) {
48
+ debug('moving %s from paths to x-ms-paths', path);
49
+ assert.ok(
50
+ !Object.hasOwn(xMsPaths, path),
51
+ `${path} already present in x-ms-paths`,
52
+ );
53
+ xMsPaths[path] = paths[path];
54
+ delete paths[path];
55
+ }
56
+
57
+ return {
58
+ ...spec,
59
+ paths,
60
+ 'x-ms-paths': xMsPaths,
61
+ };
62
+ }
63
+ }