@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,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
|
+
}
|
package/merge-all-of.js
ADDED
|
@@ -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
|
+
}
|
package/merge-any-of.js
ADDED
|
@@ -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
|
+
}
|
package/merge-one-of.js
ADDED
|
@@ -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
|
+
}
|