@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,61 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/exclusive-min-max-to-bool.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ /**
10
+ * Transformer to convert Schema Objects with numeric values for
11
+ * `exclusiveMaximum` and/or `exclusiveMinimum` (as in JSON Schema Draft
12
+ * 2020-12 referenced by OAS 3.1.0:
13
+ * https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-6.2.3
14
+ * ) to boolean values with corresponding `maximum` and/or `minimum` (as in
15
+ * JSON Schema Write 00 referenced by OAS 3.0:
16
+ * https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00#section-5.3
17
+ * and JSON Schema Draft 4 referenced by OAS 2:
18
+ * https://datatracker.ietf.org/doc/html/draft-fge-json-schema-validation-00#section-5.1.2
19
+ * ).
20
+ */
21
+ export default class ExclusiveMinMaxToBoolTransformer
22
+ extends OpenApiTransformerBase {
23
+ transformSchema(schema) {
24
+ schema = super.transformSchema(schema);
25
+ if (schema === null
26
+ || typeof schema !== 'object'
27
+ || Array.isArray(schema)) {
28
+ return schema;
29
+ }
30
+
31
+ const exclusiveMaximum = +schema.exclusiveMaximum;
32
+ const exclusiveMinimum = +schema.exclusiveMinimum;
33
+ if (Number.isNaN(exclusiveMaximum) && Number.isNaN(exclusiveMinimum)) {
34
+ return schema;
35
+ }
36
+
37
+ const newSchema = { ...schema };
38
+
39
+ if (!Number.isNaN(exclusiveMaximum)) {
40
+ const { maximum } = schema;
41
+ if (maximum < exclusiveMaximum) {
42
+ delete newSchema.exclusiveMaximum;
43
+ } else {
44
+ newSchema.maximum = exclusiveMaximum;
45
+ newSchema.exclusiveMaximum = true;
46
+ }
47
+ }
48
+
49
+ if (!Number.isNaN(exclusiveMinimum)) {
50
+ const { minimum } = schema;
51
+ if (minimum > exclusiveMinimum) {
52
+ delete newSchema.exclusiveMinimum;
53
+ } else {
54
+ newSchema.minimum = exclusiveMinimum;
55
+ newSchema.exclusiveMinimum = true;
56
+ }
57
+ }
58
+
59
+ return newSchema;
60
+ }
61
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @copyright Copyright 2019 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/format-to-type.js"
5
+ */
6
+
7
+ import OpenApiTransformerBase from 'openapi-transformer-base';
8
+
9
+ function transformSchemaType(schema) {
10
+ if (schema.type === 'string') {
11
+ const formatToType = {
12
+ decimal: 'number',
13
+ double: 'number',
14
+ float: 'number',
15
+ integer: 'integer',
16
+ int32: 'integer',
17
+ int64: 'integer',
18
+ };
19
+ const newType = formatToType[schema.format];
20
+ if (newType) {
21
+ const newSchema = {
22
+ ...schema,
23
+ type: newType,
24
+ };
25
+ if (newSchema.format === 'integer') {
26
+ // format: integer is redundant with type: integer
27
+ delete newSchema.format;
28
+ }
29
+ return newSchema;
30
+ }
31
+ }
32
+
33
+ return schema;
34
+ }
35
+
36
+ /**
37
+ * Transformer to convert known formats in an OAS3 doc to types (for Autorest).
38
+ *
39
+ * Autorest doesn't generate int/decimal/double properties for type: string.
40
+ * Change the type to generate as desired.
41
+ */
42
+ export default class FormatToTypeTransformer extends OpenApiTransformerBase {
43
+ transformSchema(schema) {
44
+ return transformSchemaType(super.transformSchema(schema));
45
+ }
46
+
47
+ transformParameter(parameter) {
48
+ return transformSchemaType(super.transformParameter(parameter));
49
+ }
50
+
51
+ transformHeader(header) {
52
+ return transformSchemaType(super.transformHeader(header));
53
+ }
54
+ }
package/index.js ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @copyright Copyright 2024 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers"
5
+ */
6
+
7
+ /* eslint-disable import/no-unused-modules */
8
+
9
+ export { default as AdditionalPropertiesToObjectTransformer }
10
+ from './additional-properties-to-object.js';
11
+ export { default as AdditionalPropertiesToUnconstrainedTransformer }
12
+ from './additional-properties-to-unconstrained.js';
13
+ export { default as AddTagToOperationIdsTransformer }
14
+ from './add-tag-to-operation-ids.js';
15
+ export { default as AddXMsEnumNameTransformer }
16
+ from './add-x-ms-enum-name.js';
17
+ export { default as AddXMsEnumValueNamesTransformer }
18
+ from './add-x-ms-enum-value-names.js';
19
+ export { default as AnyOfNullToNullableTransformer }
20
+ from './any-of-null-to-nullable.js';
21
+ export { default as AssertPropertiesTransformer }
22
+ from './assert-properties.js';
23
+ export { default as BinaryStringToFileTransformer }
24
+ from './binary-string-to-file.js';
25
+ export { default as BoolEnumToBoolTransformer }
26
+ from './bool-enum-to-bool.js';
27
+ export { default as ClearHtmlResponseSchemaTransformer }
28
+ from './clear-html-response-schema.js';
29
+ export { default as ClientParamsToGlobalTransformer }
30
+ from './client-params-to-global.js';
31
+ export { default as ConstToEnumTransformer }
32
+ from './const-to-enum.js';
33
+ export { default as EscapeEnumValuesTransformer }
34
+ from './escape-enum-values.js';
35
+ export { default as ExclusiveMinMaxToBoolTransformer }
36
+ from './exclusive-min-max-to-bool.js';
37
+ export { default as FormatToTypeTransformer }
38
+ from './format-to-type.js';
39
+ export { default as InlineNonObjectSchemaTransformer }
40
+ from './inline-non-object-schemas.js';
41
+ export { default as MergeAllOfTransformer }
42
+ from './merge-all-of.js';
43
+ export { default as MergeAnyOfTransformer }
44
+ from './merge-any-of.js';
45
+ export { default as MergeOneOfTransformer }
46
+ from './merge-one-of.js';
47
+ export { default as NullableNotRequiredTransformer }
48
+ from './nullable-not-required.js';
49
+ export { default as NullableToTypeNullTransformer }
50
+ from './nullable-to-type-null.js';
51
+ export { default as OpenApi31To30Transformer }
52
+ from './openapi31to30.js';
53
+ export { default as PathParametersToOperationTransformer }
54
+ from './path-parameters-to-operations.js';
55
+ export { default as PatternPropertiesToAdditionalPropertiesTransformer }
56
+ from './pattern-properties-to-additional-properties.js';
57
+ export { default as QueriesToXMsPathsTransformer }
58
+ from './queries-to-x-ms-paths.js';
59
+ export { default as ReadOnlyNotRequiredTransformer }
60
+ from './read-only-not-required.js';
61
+ export { default as RefPathParametersTransformer }
62
+ from './ref-path-parameters.js';
63
+ export { default as RemoveDefaultOnlyResponseProducesTransformer }
64
+ from './remove-default-only-response-produces.js';
65
+ export { default as RemovePathsWithServersTransformer }
66
+ from './remove-paths-with-servers.js';
67
+ export { default as RemoveQueryFromPathsTransformer }
68
+ from './remove-query-from-paths.js';
69
+ export { default as RemoveRefSiblingsTransformer }
70
+ from './remove-ref-siblings.js';
71
+ export { default as RemoveRequestBodyTransformer }
72
+ from './remove-request-body.js';
73
+ export { default as RemoveResponseHeadersTransformer }
74
+ from './remove-response-headers.js';
75
+ export { default as RemoveSecuritySchemeIfTransformer }
76
+ from './remove-security-scheme-if.js';
77
+ export { default as RemoveTypeIfTransformer }
78
+ from './remove-type-if.js';
79
+ export { default as RenameComponentsTransformer }
80
+ from './rename-components.js';
81
+ export { default as ReplacedByToDescriptionTransformer }
82
+ from './replaced-by-to-description.js';
83
+ export { default as ServerVarsToPathParamsTransformer }
84
+ from './server-vars-to-path-params.js';
85
+ export { default as ServerVarsToParamHostTransformer }
86
+ from './server-vars-to-x-ms-parameterized-host.js';
87
+ export { default as TypeNullToEnumTransformer }
88
+ from './type-null-to-enum.js';
89
+ export { default as TypeNullToNullableTransformer }
90
+ from './type-null-to-nullable.js';
91
+ export { default as UrlencodedToStringTransformer }
92
+ from './urlencoded-to-string.js';
93
+ export { default as XEnumToXMsEnumTransformer }
94
+ from './x-enum-to-ms.js';
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @copyright Copyright 2020 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/inline-non-object-schemas.js"
5
+ */
6
+
7
+ import { debuglog } from 'node:util';
8
+
9
+ import { JsonPointer } from 'json-ptr';
10
+ import OpenApiTransformerBase from 'openapi-transformer-base';
11
+
12
+ const debug = debuglog('inline-non-object-schemas');
13
+
14
+ const inlineAllSymbol = Symbol('inlineAll');
15
+ const resolveRefSymbol = Symbol('resolveRef');
16
+
17
+ // JSON Schema validation keywords supported by Autorest which must be inlined
18
+ // https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/mgmtcommon/ClientRuntime/ClientRuntime/ValidationRules.cs
19
+ // Note: exclusiveMaximum/Minimum only modify maximum/minimum validation.
20
+ const validationKeywords = {
21
+ maxItems: true,
22
+ maxLength: true,
23
+ maximum: true,
24
+ minItems: true,
25
+ minLength: true,
26
+ minimum: true,
27
+ multipleOf: true,
28
+ pattern: true,
29
+ uniqueItems: true,
30
+ };
31
+
32
+ /**
33
+ * Transformer to inline schemas with non-object type so that Autorest will
34
+ * generate validation code.
35
+ *
36
+ * https://github.com/Azure/autorest.csharp/issues/795
37
+ */
38
+ export default class InlineNonObjectSchemaTransformer
39
+ extends OpenApiTransformerBase {
40
+ constructor({ inlineAll, resolveRef } = {}) {
41
+ super();
42
+
43
+ if (resolveRef !== undefined && typeof resolveRef !== 'function') {
44
+ throw new TypeError('resolveRef must be a function');
45
+ }
46
+
47
+ this[inlineAllSymbol] = Boolean(inlineAll);
48
+ this[resolveRefSymbol] = resolveRef;
49
+ }
50
+
51
+ transformSchema(schema) {
52
+ const { $ref, ...nonRef } = schema;
53
+ if (typeof $ref !== 'string') {
54
+ return super.transformSchema(schema);
55
+ }
56
+
57
+ const refSchema = this[resolveRefSymbol]($ref);
58
+ if (refSchema === undefined) {
59
+ debug('Unable to resolve $ref %s', $ref);
60
+ return super.transformSchema(schema);
61
+ }
62
+
63
+ if (!refSchema || !refSchema.type || refSchema.type === 'object') {
64
+ debug('Not inlining %s: type %s', $ref, refSchema && refSchema.type);
65
+ return super.transformSchema(schema);
66
+ }
67
+
68
+ if (refSchema.enum) {
69
+ debug('Not inlining %s: enum', $ref);
70
+ return super.transformSchema(schema);
71
+ }
72
+
73
+ if (!this[inlineAllSymbol]
74
+ && !Object.keys(refSchema).some((prop) => validationKeywords[prop])
75
+ // exclusiveMaximum/exclusiveMinimum are numbers in JSON Schema
76
+ // Draft 2020-12 referenced by OAS 3.1.0 and apply on their own:
77
+ // https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-6.2.3
78
+ // exclusiveMaximum/exclusiveMinimum are boolean in JSON Schema Write 00
79
+ // referenced by OAS 3.0:
80
+ // https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00#section-5.3
81
+ // and JSON Schema Draft 4 referenced by OAS 2:
82
+ // https://datatracker.ietf.org/doc/html/draft-fge-json-schema-validation-00#section-5.1.2
83
+ // which only affect minimum/maximum and have no effect on their own.
84
+ && typeof refSchema.exclusiveMaximum !== 'number'
85
+ && typeof refSchema.exclusiveMinimum !== 'number') {
86
+ debug(
87
+ 'Not inlining %s: No validation keywords require inlining',
88
+ $ref,
89
+ );
90
+ return super.transformSchema(schema);
91
+ }
92
+
93
+ debug('Inlining %s.', $ref);
94
+ return {
95
+ ...super.transformSchema(refSchema),
96
+ // Like Autorest, allow properties other than $ref
97
+ ...nonRef,
98
+ };
99
+ }
100
+
101
+ transformOpenApi(openapi) {
102
+ // If resolveRefSymbol was not set from options, resolve against OpenAPI
103
+ // Object being transformed.
104
+ const optResolve = this[resolveRefSymbol];
105
+ if (!optResolve) {
106
+ this[resolveRefSymbol] = function resolveRef($ref) {
107
+ // JsonPointer.get would throw for non-local refs
108
+ return $ref[0] === '#' ? JsonPointer.get(openapi, $ref) : undefined;
109
+ };
110
+ }
111
+
112
+ try {
113
+ return super.transformOpenApi(openapi);
114
+ } finally {
115
+ if (optResolve) {
116
+ this[resolveRefSymbol] = optResolve;
117
+ }
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ */
5
+
6
+ import { isDeepStrictEqual } from 'node:util';
7
+
8
+ /** Manages named additions to defined components.
9
+ */
10
+ export default class ComponentManager {
11
+ constructor(component) {
12
+ if (component === null
13
+ || typeof component !== 'object'
14
+ || Array.isArray(component)) {
15
+ throw new TypeError('component must be a non-Array, non-null object');
16
+ }
17
+
18
+ this.component = component;
19
+ }
20
+
21
+ /** Adds a given value, optionally with a given (base) name.
22
+ *
23
+ * @param {*} value Value to add to the component being managed.
24
+ * @param {string=} basename Preferred name for value in component.
25
+ * ({@link getNames}).
26
+ * @returns {string} Name of added value.
27
+ */
28
+ add(value, basename) {
29
+ for (const name of this.getNames(basename)) {
30
+ if (this.component[name] === undefined) {
31
+ this.component[name] = value;
32
+ return name;
33
+ }
34
+
35
+ if (this.isMatch(this.component[name], value)) {
36
+ return name;
37
+ }
38
+ }
39
+
40
+ throw new Error('No suitable name matched value to add');
41
+ }
42
+
43
+ /** Gets property names to check for a given base name.
44
+ *
45
+ * @param {string=} basename Base name to use for property.
46
+ * @yields {string} Property names to check for #add().
47
+ */
48
+ // eslint-disable-next-line class-methods-use-this
49
+ * getNames(basename) {
50
+ yield `${basename}`;
51
+ for (let i = 2; ; i += 1) {
52
+ yield `${basename}${i}`;
53
+ }
54
+ }
55
+ }
56
+
57
+ /** Determines if given values match such that an existing value can be used in
58
+ * place of an added value.
59
+ */
60
+ ComponentManager.prototype.isMatch = isDeepStrictEqual;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ */
5
+
6
+ // Any unique, deterministic stringify function would work.
7
+ // TODO: Benchmark against native JSON.stringify result of sort-keys deep
8
+ // https://github.com/sindresorhus/getKey-obj/blob/master/index.js#L17
9
+ // https://github.com/epoberezkin/fast-json-stable-stringify#benchmark
10
+ import stringify from 'fast-json-stable-stringify';
11
+
12
+ import ComponentManager from './component-manager.js';
13
+
14
+ const keyToNameSymbol = Symbol('keyToName');
15
+
16
+ /** Adds values to defined components by first attempting to match an
17
+ * existing value in the component.
18
+ */
19
+ export default class MatchingComponentManager extends ComponentManager {
20
+ constructor(component) {
21
+ super(component);
22
+
23
+ const keyToName = new Map();
24
+ for (const [name, value] of Object.entries(component)) {
25
+ keyToName.set(this.getKey(value), name);
26
+ }
27
+ this[keyToNameSymbol] = keyToName;
28
+ }
29
+
30
+ /** Adds a given value, optionally with a given (base) name.
31
+ *
32
+ * @param {*} value Value to add to the component being managed.
33
+ * @param {string=} basename Preferred name for value in component.
34
+ * ({@link ComponentManager#getNames}).
35
+ * @returns {string} Name of added value.
36
+ */
37
+ add(value, basename) {
38
+ const key = this.getKey(value);
39
+ const name = this[keyToNameSymbol].get(key);
40
+ if (name) {
41
+ return name;
42
+ }
43
+
44
+ const newName = super.add(value, basename);
45
+ this[keyToNameSymbol].set(key, newName);
46
+ return newName;
47
+ }
48
+
49
+ /** Determines if given values match such that an existing value can be used
50
+ * in place of an added value.
51
+ *
52
+ * Overridden to always return false, since values which do not have equal
53
+ * keys do not match (according to this class) and keys are checked before
54
+ * calling super.add().
55
+ *
56
+ * FIXME: This optimization is useful to avoid wasting lots of cycles
57
+ * for properties with many name conflicts with deep values, but it is
58
+ * also a foot-gun if isMatch is called unexpectedly. Find a better fix.
59
+ *
60
+ * @returns {boolean} False.
61
+ */
62
+ // eslint-disable-next-line class-methods-use-this
63
+ isMatch() {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /** Converts a value to an key which uniquely identifies the value for
69
+ * matching.
70
+ *
71
+ * @param {*} value Value to identify.
72
+ * @returns {*} Key which uniquely matches the value.
73
+ */
74
+ MatchingComponentManager.prototype.getKey = stringify;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @copyright Copyright 2021 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ */
5
+
6
+ import MatchingComponentManager from './matching-component-manager.js';
7
+
8
+ /** Gets a copy of a parameter with x-ms-parameter-location removed if
9
+ * its value is 'client'.
10
+ *
11
+ * Autorest treats defined parameters as properties on the client by default.
12
+ * (i.e. the same as x-ms-parameter-location:client) Therefore, remove
13
+ * x-ms-parameter-location:client when comparing/keying parameters so
14
+ * it doesn't affect equality.
15
+ *
16
+ * @param {object=} parameter Parameter Object to strip.
17
+ * @returns {object=} Copy of parameter with x-ms-parameter-location removed if
18
+ * its value is 'client'. Otherwise, parameter.
19
+ */
20
+ function stripClientXMsParamLoc(parameter) {
21
+ if (parameter && parameter['x-ms-parameter-location'] === 'client') {
22
+ const { 'x-ms-parameter-location': _, ...paramNoLoc } = parameter;
23
+ return paramNoLoc;
24
+ }
25
+
26
+ return parameter;
27
+ }
28
+
29
+ /** Adds values to defined components by first attempting to match an
30
+ * existing value in the component.
31
+ */
32
+ export default class MatchingParameterManager extends MatchingComponentManager {
33
+ getKey(parameter) {
34
+ return super.getKey(stripClientXMsParamLoc(parameter));
35
+ }
36
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @copyright Copyright 2021, 2025 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/merge-all-of.js"
5
+ */
6
+
7
+ import { debuglog } from 'node:util';
8
+
9
+ import intersectSchema from 'json-schema-intersect';
10
+ import OpenApiTransformerBase from 'openapi-transformer-base';
11
+
12
+ const debug = debuglog('openapi-transformers:merge-all-of');
13
+
14
+ /**
15
+ * Transformer to merge allOf schemas into the parent schema.
16
+ *
17
+ * This may be useful to accommodate tools which do not support allOf well
18
+ * (e.g. some strongly-typed code generators).
19
+ */
20
+ export default class MergeAllOfTransformer extends OpenApiTransformerBase {
21
+ onlySingle = false;
22
+
23
+ constructor({ onlySingle } = {}) {
24
+ super();
25
+
26
+ this.onlySingle = Boolean(onlySingle);
27
+ }
28
+
29
+ transformSchema(schema) {
30
+ const newSchema = super.transformSchema(schema);
31
+ if (!newSchema || typeof newSchema !== 'object') {
32
+ // Note: warning already emitted by super.transformSchema()
33
+ return newSchema;
34
+ }
35
+
36
+ const {
37
+ allOf,
38
+ ...schemaNoAllOf
39
+ } = newSchema;
40
+ if (!Array.isArray(allOf)) {
41
+ this.warn('Unable to merge non-Array allOf', allOf);
42
+ return newSchema;
43
+ }
44
+
45
+ if (allOf.length === 0) {
46
+ // Empty allOf Array is disallowed by JSON Schema.
47
+ // Considered safe to remove since any instance trivially validates
48
+ // successfully against all 0 schemas.
49
+ debug('Removing empty allOf');
50
+ return schemaNoAllOf;
51
+ }
52
+
53
+ if (this.onlySingle && allOf.length > 1) {
54
+ debug('Skipping allOf with multiple schemas');
55
+ return newSchema;
56
+ }
57
+
58
+ return allOf.reduce(intersectSchema, schemaNoAllOf);
59
+ }
60
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @copyright Copyright 2021, 2025 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/merge-any-of.js"
5
+ */
6
+
7
+ import intersectSchema from 'json-schema-intersect';
8
+ import OpenApiTransformerBase from 'openapi-transformer-base';
9
+
10
+ /**
11
+ * Transformer to merge anyOf schemas into the parent schema.
12
+ *
13
+ * This is useful for converting to OpenAPI 2.0 (which does not support
14
+ * anyOf) from later versions, or to accommodate tools which do not support
15
+ * anyOf well (e.g. many strongly-typed code generators).
16
+ */
17
+ export default class MergeAnyOfTransformer extends OpenApiTransformerBase {
18
+ transformSchema(schema) {
19
+ const newSchema = super.transformSchema(schema);
20
+ if (!newSchema || typeof newSchema !== 'object') {
21
+ // Note: warning already emitted by super.transformSchema()
22
+ return newSchema;
23
+ }
24
+
25
+ const {
26
+ anyOf,
27
+ ...schemaNoAnyOf
28
+ } = newSchema;
29
+ if (!Array.isArray(anyOf)) {
30
+ this.warn('Unable to merge non-Array anyOf', anyOf);
31
+ return newSchema;
32
+ }
33
+
34
+ if (anyOf.length === 0) {
35
+ // Empty anyOf Array is disallowed by JSON Schema.
36
+ // Not safe to remove since all instances will fail to "validate
37
+ // successfully against at least one schema".
38
+ this.warn('Unable to merge empty anyOf Array');
39
+ return newSchema;
40
+ }
41
+
42
+ if (anyOf.length > 1) {
43
+ throw new Error('Merging multiple anyOf schemas not implemented');
44
+ }
45
+
46
+ return intersectSchema(schemaNoAnyOf, anyOf[0]);
47
+ }
48
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @copyright Copyright 2021, 2025 Kevin Locke <kevin@kevinlocke.name>
3
+ * @license MIT
4
+ * @module "openapi-transformers/merge-one-of.js"
5
+ */
6
+
7
+ import intersectSchema from 'json-schema-intersect';
8
+ import OpenApiTransformerBase from 'openapi-transformer-base';
9
+
10
+ /**
11
+ * Transformer to merge oneOf schemas into the parent schema.
12
+ *
13
+ * This is useful for converting to OpenAPI 2.0 (which does not support
14
+ * oneOf) from later versions, or to accommodate tools which do not support
15
+ * oneOf well (e.g. many strongly-typed code generators).
16
+ */
17
+ export default class MergeOneOfTransformer extends OpenApiTransformerBase {
18
+ transformSchema(schema) {
19
+ const newSchema = super.transformSchema(schema);
20
+ if (!newSchema || typeof newSchema !== 'object') {
21
+ // Note: warning already emitted by super.transformSchema()
22
+ return newSchema;
23
+ }
24
+
25
+ const {
26
+ oneOf,
27
+ ...schemaNoOneOf
28
+ } = newSchema;
29
+ if (!Array.isArray(oneOf)) {
30
+ this.warn('Unable to merge non-Array oneOf', oneOf);
31
+ return newSchema;
32
+ }
33
+
34
+ if (oneOf.length === 0) {
35
+ // Empty oneOf Array is disallowed by JSON Schema.
36
+ // Not safe to remove since all instances will fail to "validate
37
+ // successfully against exactly one schema".
38
+ this.warn('Unable to merge empty oneOf Array');
39
+ return newSchema;
40
+ }
41
+
42
+ if (oneOf.length > 1) {
43
+ throw new Error('Merging multiple oneOf schemas not implemented');
44
+ }
45
+
46
+ return intersectSchema(schemaNoOneOf, oneOf[0]);
47
+ }
48
+ }