@redocly/openapi-core 1.25.8 → 1.25.10
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/CHANGELOG.md +14 -0
- package/lib/config/config.js +1 -1
- package/lib/decorators/common/remove-x-internal.js +21 -9
- package/lib/resolve.js +4 -4
- package/lib/rules/oas3/component-name-unique.js +33 -22
- package/package.json +3 -3
- package/src/__tests__/lint.test.ts +0 -1
- package/src/benchmark/benches/rebilly.yaml +0 -3
- package/src/bundle.ts +6 -6
- package/src/config/config.ts +1 -1
- package/src/decorators/__tests__/remove-x-internal.test.ts +97 -0
- package/src/decorators/common/remove-x-internal.ts +28 -11
- package/src/resolve.ts +4 -4
- package/src/rules/oas3/__tests__/component-name-unique.test.ts +176 -33
- package/src/rules/oas3/component-name-unique.ts +37 -35
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @redocly/openapi-core
|
|
2
2
|
|
|
3
|
+
## 1.25.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed `component-name-unique` problems to include correct location.
|
|
8
|
+
- Fixed the `remove-x-internal` decorator, which was not removing the reference in the corresponding discriminator mapping while removing the original `$ref`.
|
|
9
|
+
- Updated @redocly/config to v0.16.0.
|
|
10
|
+
|
|
11
|
+
## 1.25.9
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated @redocly/config to v0.15.0.
|
|
16
|
+
|
|
3
17
|
## 1.25.8
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/lib/config/config.js
CHANGED
|
@@ -11,7 +11,7 @@ const utils_2 = require("./utils");
|
|
|
11
11
|
const ref_utils_1 = require("../ref-utils");
|
|
12
12
|
exports.IGNORE_FILE = '.redocly.lint-ignore.yaml';
|
|
13
13
|
const IGNORE_BANNER = `# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
|
|
14
|
-
`# See https://
|
|
14
|
+
`# See https://redocly.com/docs/cli/ for more information.\n`;
|
|
15
15
|
function getIgnoreFilePath(configFile) {
|
|
16
16
|
if (configFile) {
|
|
17
17
|
return (0, utils_1.doesYamlFileExist)(configFile)
|
|
@@ -4,22 +4,29 @@ exports.RemoveXInternal = void 0;
|
|
|
4
4
|
const utils_1 = require("../../utils");
|
|
5
5
|
const ref_utils_1 = require("../../ref-utils");
|
|
6
6
|
const DEFAULT_INTERNAL_PROPERTY_NAME = 'x-internal';
|
|
7
|
-
const RemoveXInternal = ({ internalFlagProperty }) => {
|
|
8
|
-
|
|
9
|
-
function removeInternal(node, ctx) {
|
|
7
|
+
const RemoveXInternal = ({ internalFlagProperty = DEFAULT_INTERNAL_PROPERTY_NAME, }) => {
|
|
8
|
+
function removeInternal(node, ctx, originalMapping) {
|
|
10
9
|
const { parent, key } = ctx;
|
|
11
10
|
let didDelete = false;
|
|
12
11
|
if (Array.isArray(node)) {
|
|
13
12
|
for (let i = 0; i < node.length; i++) {
|
|
14
13
|
if ((0, ref_utils_1.isRef)(node[i])) {
|
|
15
14
|
const resolved = ctx.resolve(node[i]);
|
|
16
|
-
if (resolved.node?.[
|
|
15
|
+
if (resolved.node?.[internalFlagProperty]) {
|
|
16
|
+
// First, remove the reference in the discriminator mapping, if it exists:
|
|
17
|
+
if ((0, utils_1.isPlainObject)(parent.discriminator?.mapping)) {
|
|
18
|
+
for (const mapping in parent.discriminator.mapping) {
|
|
19
|
+
if (originalMapping?.[mapping] === node[i].$ref) {
|
|
20
|
+
delete parent.discriminator.mapping[mapping];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
17
24
|
node.splice(i, 1);
|
|
18
25
|
didDelete = true;
|
|
19
26
|
i--;
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
|
-
if (node[i]?.[
|
|
29
|
+
if (node[i]?.[internalFlagProperty]) {
|
|
23
30
|
node.splice(i, 1);
|
|
24
31
|
didDelete = true;
|
|
25
32
|
i--;
|
|
@@ -28,15 +35,14 @@ const RemoveXInternal = ({ internalFlagProperty }) => {
|
|
|
28
35
|
}
|
|
29
36
|
else if ((0, utils_1.isPlainObject)(node)) {
|
|
30
37
|
for (const key of Object.keys(node)) {
|
|
31
|
-
node = node;
|
|
32
38
|
if ((0, ref_utils_1.isRef)(node[key])) {
|
|
33
39
|
const resolved = ctx.resolve(node[key]);
|
|
34
|
-
if (resolved.node?.[
|
|
40
|
+
if ((0, utils_1.isPlainObject)(resolved.node) && resolved.node?.[internalFlagProperty]) {
|
|
35
41
|
delete node[key];
|
|
36
42
|
didDelete = true;
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
|
-
if (node[key]?.[
|
|
45
|
+
if ((0, utils_1.isPlainObject)(node[key]) && node[key]?.[internalFlagProperty]) {
|
|
40
46
|
delete node[key];
|
|
41
47
|
didDelete = true;
|
|
42
48
|
}
|
|
@@ -46,10 +52,16 @@ const RemoveXInternal = ({ internalFlagProperty }) => {
|
|
|
46
52
|
delete parent[key];
|
|
47
53
|
}
|
|
48
54
|
}
|
|
55
|
+
let originalMapping = {};
|
|
49
56
|
return {
|
|
57
|
+
DiscriminatorMapping: {
|
|
58
|
+
enter: (mapping) => {
|
|
59
|
+
originalMapping = structuredClone(mapping);
|
|
60
|
+
},
|
|
61
|
+
},
|
|
50
62
|
any: {
|
|
51
63
|
enter: (node, ctx) => {
|
|
52
|
-
removeInternal(node, ctx);
|
|
64
|
+
removeInternal(node, ctx, originalMapping);
|
|
53
65
|
},
|
|
54
66
|
},
|
|
55
67
|
};
|
package/lib/resolve.js
CHANGED
|
@@ -101,7 +101,7 @@ class BaseResolver {
|
|
|
101
101
|
}
|
|
102
102
|
else {
|
|
103
103
|
if (fs.lstatSync(absoluteRef).isDirectory()) {
|
|
104
|
-
throw new Error(`Expected a file but received a folder at ${absoluteRef}
|
|
104
|
+
throw new Error(`Expected a file but received a folder at ${absoluteRef}.`);
|
|
105
105
|
}
|
|
106
106
|
const content = await fs.promises.readFile(absoluteRef, 'utf-8');
|
|
107
107
|
// In some cases file have \r\n line delimeters like on windows, we should skip it.
|
|
@@ -165,7 +165,7 @@ const resolvableScalarType = { name: 'scalar', properties: {} };
|
|
|
165
165
|
async function resolveDocument(opts) {
|
|
166
166
|
const { rootDocument, externalRefResolver, rootType } = opts;
|
|
167
167
|
const resolvedRefMap = new Map();
|
|
168
|
-
const
|
|
168
|
+
const seenNodes = new Set(); // format "${type}::${absoluteRef}${pointer}"
|
|
169
169
|
const resolvePromises = [];
|
|
170
170
|
resolveRefsInParallel(rootDocument.parsed, rootDocument, '#/', rootType);
|
|
171
171
|
let resolved;
|
|
@@ -182,10 +182,10 @@ async function resolveDocument(opts) {
|
|
|
182
182
|
return;
|
|
183
183
|
}
|
|
184
184
|
const nodeId = `${type.name}::${nodeAbsoluteRef}`;
|
|
185
|
-
if (
|
|
185
|
+
if (seenNodes.has(nodeId)) {
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
188
|
-
|
|
188
|
+
seenNodes.add(nodeId);
|
|
189
189
|
const [_, anchor] = Object.entries(node).find(([key]) => key === '$anchor') || [];
|
|
190
190
|
if (anchor) {
|
|
191
191
|
anchorRefsMap.set(`#${anchor}`, node);
|
|
@@ -34,27 +34,31 @@ const ComponentNameUnique = (options) => {
|
|
|
34
34
|
const resolvedRef = resolve(ref);
|
|
35
35
|
if (!resolvedRef.location)
|
|
36
36
|
return;
|
|
37
|
-
addComponentFromAbsoluteLocation(typeName, resolvedRef.location
|
|
37
|
+
addComponentFromAbsoluteLocation(typeName, resolvedRef.location);
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
41
|
Root: {
|
|
42
42
|
leave(root, ctx) {
|
|
43
43
|
components.forEach((value, key, _) => {
|
|
44
|
-
if (value.size > 1) {
|
|
44
|
+
if (value.absolutePointers.size > 1) {
|
|
45
45
|
const component = getComponentFromKey(key);
|
|
46
46
|
const optionComponentName = getOptionComponentNameForTypeName(component.typeName);
|
|
47
|
-
const definitions = Array.from(value)
|
|
48
|
-
.map((v) => `- ${v}`)
|
|
49
|
-
.join('\n');
|
|
50
|
-
const problem = {
|
|
51
|
-
message: `Component '${optionComponentName}/${component.componentName}' is not unique. It is defined at:\n${definitions}`,
|
|
52
|
-
};
|
|
53
47
|
const componentSeverity = optionComponentName ? options[optionComponentName] : null;
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
for (const location of value.locations) {
|
|
49
|
+
const definitions = Array.from(value.absolutePointers)
|
|
50
|
+
.filter((v) => v !== location.absolutePointer.toString())
|
|
51
|
+
.map((v) => `- ${v}`)
|
|
52
|
+
.join('\n');
|
|
53
|
+
const problem = {
|
|
54
|
+
message: `Component '${optionComponentName}/${component.componentName}' is not unique. It is also defined at:\n${definitions}`,
|
|
55
|
+
location: location,
|
|
56
|
+
};
|
|
57
|
+
if (componentSeverity) {
|
|
58
|
+
problem.forceSeverity = componentSeverity;
|
|
59
|
+
}
|
|
60
|
+
ctx.report(problem);
|
|
56
61
|
}
|
|
57
|
-
ctx.report(problem);
|
|
58
62
|
}
|
|
59
63
|
});
|
|
60
64
|
},
|
|
@@ -63,28 +67,28 @@ const ComponentNameUnique = (options) => {
|
|
|
63
67
|
if (options.schemas != 'off') {
|
|
64
68
|
rule.NamedSchemas = {
|
|
65
69
|
Schema(_, { location }) {
|
|
66
|
-
addComponentFromAbsoluteLocation(TYPE_NAME_SCHEMA, location
|
|
70
|
+
addComponentFromAbsoluteLocation(TYPE_NAME_SCHEMA, location);
|
|
67
71
|
},
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
74
|
if (options.responses != 'off') {
|
|
71
75
|
rule.NamedResponses = {
|
|
72
76
|
Response(_, { location }) {
|
|
73
|
-
addComponentFromAbsoluteLocation(TYPE_NAME_RESPONSE, location
|
|
77
|
+
addComponentFromAbsoluteLocation(TYPE_NAME_RESPONSE, location);
|
|
74
78
|
},
|
|
75
79
|
};
|
|
76
80
|
}
|
|
77
81
|
if (options.parameters != 'off') {
|
|
78
82
|
rule.NamedParameters = {
|
|
79
83
|
Parameter(_, { location }) {
|
|
80
|
-
addComponentFromAbsoluteLocation(TYPE_NAME_PARAMETER, location
|
|
84
|
+
addComponentFromAbsoluteLocation(TYPE_NAME_PARAMETER, location);
|
|
81
85
|
},
|
|
82
86
|
};
|
|
83
87
|
}
|
|
84
88
|
if (options.requestBodies != 'off') {
|
|
85
89
|
rule.NamedRequestBodies = {
|
|
86
90
|
RequestBody(_, { location }) {
|
|
87
|
-
addComponentFromAbsoluteLocation(TYPE_NAME_REQUEST_BODY, location
|
|
91
|
+
addComponentFromAbsoluteLocation(TYPE_NAME_REQUEST_BODY, location);
|
|
88
92
|
},
|
|
89
93
|
};
|
|
90
94
|
}
|
|
@@ -98,15 +102,22 @@ const ComponentNameUnique = (options) => {
|
|
|
98
102
|
}
|
|
99
103
|
return componentName;
|
|
100
104
|
}
|
|
101
|
-
function addFoundComponent(typeName, componentName,
|
|
105
|
+
function addFoundComponent(typeName, componentName, location) {
|
|
102
106
|
const key = getKeyForComponent(typeName, componentName);
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
const entry = components.get(key) ?? {
|
|
108
|
+
absolutePointers: new Set(),
|
|
109
|
+
locations: [],
|
|
110
|
+
};
|
|
111
|
+
const absoluteLocation = location.absolutePointer.toString();
|
|
112
|
+
if (!entry.absolutePointers.has(absoluteLocation)) {
|
|
113
|
+
entry.absolutePointers.add(absoluteLocation);
|
|
114
|
+
entry.locations.push(location);
|
|
115
|
+
}
|
|
116
|
+
components.set(key, entry);
|
|
106
117
|
}
|
|
107
|
-
function addComponentFromAbsoluteLocation(typeName,
|
|
108
|
-
const componentName = getComponentNameFromAbsoluteLocation(
|
|
109
|
-
addFoundComponent(typeName, componentName,
|
|
118
|
+
function addComponentFromAbsoluteLocation(typeName, location) {
|
|
119
|
+
const componentName = getComponentNameFromAbsoluteLocation(location.absolutePointer.toString());
|
|
120
|
+
addFoundComponent(typeName, componentName, location);
|
|
110
121
|
}
|
|
111
122
|
};
|
|
112
123
|
exports.ComponentNameUnique = ComponentNameUnique;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/openapi-core",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.10",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"oas"
|
|
33
33
|
],
|
|
34
34
|
"contributors": [
|
|
35
|
-
"Roman Hotsiy <roman@
|
|
35
|
+
"Roman Hotsiy <roman@redocly.com> (https://redocly.com/)"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@redocly/ajv": "^8.11.2",
|
|
39
|
-
"@redocly/config": "^0.
|
|
39
|
+
"@redocly/config": "^0.16.0",
|
|
40
40
|
"colorette": "^1.2.0",
|
|
41
41
|
"https-proxy-agent": "^7.0.4",
|
|
42
42
|
"js-levenshtein": "^1.1.6",
|
|
@@ -10,9 +10,6 @@ info:
|
|
|
10
10
|
name: Rebilly
|
|
11
11
|
url: 'https://www.rebilly.com/api-license/'
|
|
12
12
|
termsOfService: 'https://www.rebilly.com/terms-of-use/'
|
|
13
|
-
x-logo:
|
|
14
|
-
url: 'https://rebilly-core.redoc.ly/rb_apiLogo.svg'
|
|
15
|
-
backgroundColor: '#0033A0'
|
|
16
13
|
description: >
|
|
17
14
|
# Introduction
|
|
18
15
|
|
package/src/bundle.ts
CHANGED
|
@@ -398,7 +398,7 @@ function makeBundleVisitor(
|
|
|
398
398
|
},
|
|
399
399
|
},
|
|
400
400
|
Root: {
|
|
401
|
-
enter(root: any, ctx:
|
|
401
|
+
enter(root: any, ctx: UserContext) {
|
|
402
402
|
rootLocation = ctx.location;
|
|
403
403
|
if (version === SpecMajorVersion.OAS3) {
|
|
404
404
|
components = root.components = root.components || {};
|
|
@@ -417,7 +417,7 @@ function makeBundleVisitor(
|
|
|
417
417
|
|
|
418
418
|
if (version === SpecMajorVersion.OAS3) {
|
|
419
419
|
visitor.DiscriminatorMapping = {
|
|
420
|
-
leave(mapping: Record<string, string>, ctx:
|
|
420
|
+
leave(mapping: Record<string, string>, ctx: UserContext) {
|
|
421
421
|
for (const name of Object.keys(mapping)) {
|
|
422
422
|
const $ref = mapping[name];
|
|
423
423
|
const resolved = ctx.resolve({ $ref });
|
|
@@ -446,7 +446,7 @@ function makeBundleVisitor(
|
|
|
446
446
|
|
|
447
447
|
function saveComponent(
|
|
448
448
|
componentType: string,
|
|
449
|
-
target: { node:
|
|
449
|
+
target: { node: unknown; location: Location },
|
|
450
450
|
ctx: UserContext
|
|
451
451
|
) {
|
|
452
452
|
components[componentType] = components[componentType] || {};
|
|
@@ -464,8 +464,8 @@ function makeBundleVisitor(
|
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
function isEqualOrEqualRef(
|
|
467
|
-
node:
|
|
468
|
-
target: { node:
|
|
467
|
+
node: unknown,
|
|
468
|
+
target: { node: unknown; location: Location },
|
|
469
469
|
ctx: UserContext
|
|
470
470
|
) {
|
|
471
471
|
if (
|
|
@@ -480,7 +480,7 @@ function makeBundleVisitor(
|
|
|
480
480
|
}
|
|
481
481
|
|
|
482
482
|
function getComponentName(
|
|
483
|
-
target: { node:
|
|
483
|
+
target: { node: unknown; location: Location },
|
|
484
484
|
componentType: string,
|
|
485
485
|
ctx: UserContext
|
|
486
486
|
) {
|
package/src/config/config.ts
CHANGED
|
@@ -34,7 +34,7 @@ import type {
|
|
|
34
34
|
export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
|
|
35
35
|
const IGNORE_BANNER =
|
|
36
36
|
`# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
|
|
37
|
-
`# See https://
|
|
37
|
+
`# See https://redocly.com/docs/cli/ for more information.\n`;
|
|
38
38
|
|
|
39
39
|
function getIgnoreFilePath(configFile?: string): string | undefined {
|
|
40
40
|
if (configFile) {
|
|
@@ -271,6 +271,103 @@ describe('oas3 remove-x-internal', () => {
|
|
|
271
271
|
|
|
272
272
|
`);
|
|
273
273
|
});
|
|
274
|
+
|
|
275
|
+
it('should remove $refs and the corresponding discriminator mapping', async () => {
|
|
276
|
+
const testDoc = parseYamlToDocument(
|
|
277
|
+
outdent`
|
|
278
|
+
openapi: 3.1.0
|
|
279
|
+
info: {}
|
|
280
|
+
paths:
|
|
281
|
+
/test:
|
|
282
|
+
post:
|
|
283
|
+
requestBody:
|
|
284
|
+
content:
|
|
285
|
+
application/json:
|
|
286
|
+
schema:
|
|
287
|
+
$ref: '#/components/schemas/christmas-tree'
|
|
288
|
+
components:
|
|
289
|
+
schemas:
|
|
290
|
+
christmas-tree:
|
|
291
|
+
type: array
|
|
292
|
+
items:
|
|
293
|
+
discriminator:
|
|
294
|
+
propertyName: type
|
|
295
|
+
mapping:
|
|
296
|
+
candy-cane: '#/components/schemas/candy-cane'
|
|
297
|
+
popcorn: '#/components/schemas/popcorn'
|
|
298
|
+
cranberry: '#/components/schemas/cranberry'
|
|
299
|
+
anyOf:
|
|
300
|
+
- $ref: '#/components/schemas/candy-cane'
|
|
301
|
+
- $ref: '#/components/schemas/popcorn'
|
|
302
|
+
- $ref: '#/components/schemas/cranberry'
|
|
303
|
+
candy-cane:
|
|
304
|
+
x-internal: true
|
|
305
|
+
title: candy-cane
|
|
306
|
+
type: object
|
|
307
|
+
properties:
|
|
308
|
+
type:
|
|
309
|
+
type: string
|
|
310
|
+
enum: [candy-cane]
|
|
311
|
+
popcorn:
|
|
312
|
+
type: object
|
|
313
|
+
properties:
|
|
314
|
+
type:
|
|
315
|
+
type: string
|
|
316
|
+
enum: [popcorn]
|
|
317
|
+
cranberry:
|
|
318
|
+
type: object
|
|
319
|
+
properties:
|
|
320
|
+
type:
|
|
321
|
+
type: string
|
|
322
|
+
enum: [cranberry]
|
|
323
|
+
`
|
|
324
|
+
);
|
|
325
|
+
const { bundle: res } = await bundleDocument({
|
|
326
|
+
document: testDoc,
|
|
327
|
+
externalRefResolver: new BaseResolver(),
|
|
328
|
+
config: await makeConfig({ rules: {}, decorators: { 'remove-x-internal': 'on' } }),
|
|
329
|
+
});
|
|
330
|
+
expect(res.parsed).toMatchInlineSnapshot(`
|
|
331
|
+
openapi: 3.1.0
|
|
332
|
+
info: {}
|
|
333
|
+
paths:
|
|
334
|
+
/test:
|
|
335
|
+
post:
|
|
336
|
+
requestBody:
|
|
337
|
+
content:
|
|
338
|
+
application/json:
|
|
339
|
+
schema:
|
|
340
|
+
$ref: '#/components/schemas/christmas-tree'
|
|
341
|
+
components:
|
|
342
|
+
schemas:
|
|
343
|
+
christmas-tree:
|
|
344
|
+
type: array
|
|
345
|
+
items:
|
|
346
|
+
discriminator:
|
|
347
|
+
propertyName: type
|
|
348
|
+
mapping:
|
|
349
|
+
popcorn: '#/components/schemas/popcorn'
|
|
350
|
+
cranberry: '#/components/schemas/cranberry'
|
|
351
|
+
anyOf:
|
|
352
|
+
- $ref: '#/components/schemas/popcorn'
|
|
353
|
+
- $ref: '#/components/schemas/cranberry'
|
|
354
|
+
popcorn:
|
|
355
|
+
type: object
|
|
356
|
+
properties:
|
|
357
|
+
type:
|
|
358
|
+
type: string
|
|
359
|
+
enum:
|
|
360
|
+
- popcorn
|
|
361
|
+
cranberry:
|
|
362
|
+
type: object
|
|
363
|
+
properties:
|
|
364
|
+
type:
|
|
365
|
+
type: string
|
|
366
|
+
enum:
|
|
367
|
+
- cranberry
|
|
368
|
+
|
|
369
|
+
`);
|
|
370
|
+
});
|
|
274
371
|
});
|
|
275
372
|
|
|
276
373
|
describe('oas2 remove-x-internal', () => {
|
|
@@ -6,23 +6,35 @@ import type { UserContext } from '../../walk';
|
|
|
6
6
|
|
|
7
7
|
const DEFAULT_INTERNAL_PROPERTY_NAME = 'x-internal';
|
|
8
8
|
|
|
9
|
-
export const RemoveXInternal: Oas3Decorator | Oas2Decorator = ({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function removeInternal(
|
|
9
|
+
export const RemoveXInternal: Oas3Decorator | Oas2Decorator = ({
|
|
10
|
+
internalFlagProperty = DEFAULT_INTERNAL_PROPERTY_NAME,
|
|
11
|
+
}) => {
|
|
12
|
+
function removeInternal(
|
|
13
|
+
node: unknown,
|
|
14
|
+
ctx: UserContext,
|
|
15
|
+
originalMapping: Record<string, string>
|
|
16
|
+
) {
|
|
13
17
|
const { parent, key } = ctx;
|
|
14
18
|
let didDelete = false;
|
|
15
19
|
if (Array.isArray(node)) {
|
|
16
20
|
for (let i = 0; i < node.length; i++) {
|
|
17
21
|
if (isRef(node[i])) {
|
|
18
22
|
const resolved = ctx.resolve(node[i]);
|
|
19
|
-
if (resolved.node?.[
|
|
23
|
+
if (resolved.node?.[internalFlagProperty]) {
|
|
24
|
+
// First, remove the reference in the discriminator mapping, if it exists:
|
|
25
|
+
if (isPlainObject(parent.discriminator?.mapping)) {
|
|
26
|
+
for (const mapping in parent.discriminator.mapping) {
|
|
27
|
+
if (originalMapping?.[mapping] === node[i].$ref) {
|
|
28
|
+
delete parent.discriminator.mapping[mapping];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
20
32
|
node.splice(i, 1);
|
|
21
33
|
didDelete = true;
|
|
22
34
|
i--;
|
|
23
35
|
}
|
|
24
36
|
}
|
|
25
|
-
if (node[i]?.[
|
|
37
|
+
if (node[i]?.[internalFlagProperty]) {
|
|
26
38
|
node.splice(i, 1);
|
|
27
39
|
didDelete = true;
|
|
28
40
|
i--;
|
|
@@ -30,15 +42,14 @@ export const RemoveXInternal: Oas3Decorator | Oas2Decorator = ({ internalFlagPro
|
|
|
30
42
|
}
|
|
31
43
|
} else if (isPlainObject(node)) {
|
|
32
44
|
for (const key of Object.keys(node)) {
|
|
33
|
-
node = node as any;
|
|
34
45
|
if (isRef(node[key])) {
|
|
35
|
-
const resolved = ctx.resolve
|
|
36
|
-
if (resolved.node?.[
|
|
46
|
+
const resolved = ctx.resolve(node[key]);
|
|
47
|
+
if (isPlainObject(resolved.node) && resolved.node?.[internalFlagProperty]) {
|
|
37
48
|
delete node[key];
|
|
38
49
|
didDelete = true;
|
|
39
50
|
}
|
|
40
51
|
}
|
|
41
|
-
if (node[key]?.[
|
|
52
|
+
if (isPlainObject(node[key]) && node[key]?.[internalFlagProperty]) {
|
|
42
53
|
delete node[key];
|
|
43
54
|
didDelete = true;
|
|
44
55
|
}
|
|
@@ -50,10 +61,16 @@ export const RemoveXInternal: Oas3Decorator | Oas2Decorator = ({ internalFlagPro
|
|
|
50
61
|
}
|
|
51
62
|
}
|
|
52
63
|
|
|
64
|
+
let originalMapping: Record<string, string> = {};
|
|
53
65
|
return {
|
|
66
|
+
DiscriminatorMapping: {
|
|
67
|
+
enter: (mapping: Record<string, string>) => {
|
|
68
|
+
originalMapping = structuredClone(mapping);
|
|
69
|
+
},
|
|
70
|
+
},
|
|
54
71
|
any: {
|
|
55
72
|
enter: (node, ctx) => {
|
|
56
|
-
removeInternal(node, ctx);
|
|
73
|
+
removeInternal(node, ctx, originalMapping);
|
|
57
74
|
},
|
|
58
75
|
},
|
|
59
76
|
};
|
package/src/resolve.ts
CHANGED
|
@@ -126,7 +126,7 @@ export class BaseResolver {
|
|
|
126
126
|
return new Source(absoluteRef, body, mimeType);
|
|
127
127
|
} else {
|
|
128
128
|
if (fs.lstatSync(absoluteRef).isDirectory()) {
|
|
129
|
-
throw new Error(`Expected a file but received a folder at ${absoluteRef}
|
|
129
|
+
throw new Error(`Expected a file but received a folder at ${absoluteRef}.`);
|
|
130
130
|
}
|
|
131
131
|
const content = await fs.promises.readFile(absoluteRef, 'utf-8');
|
|
132
132
|
// In some cases file have \r\n line delimeters like on windows, we should skip it.
|
|
@@ -233,7 +233,7 @@ export async function resolveDocument(opts: {
|
|
|
233
233
|
}): Promise<ResolvedRefMap> {
|
|
234
234
|
const { rootDocument, externalRefResolver, rootType } = opts;
|
|
235
235
|
const resolvedRefMap: ResolvedRefMap = new Map();
|
|
236
|
-
const
|
|
236
|
+
const seenNodes = new Set<string>(); // format "${type}::${absoluteRef}${pointer}"
|
|
237
237
|
|
|
238
238
|
const resolvePromises: Array<Promise<void>> = [];
|
|
239
239
|
resolveRefsInParallel(rootDocument.parsed, rootDocument, '#/', rootType);
|
|
@@ -262,11 +262,11 @@ export async function resolveDocument(opts: {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
const nodeId = `${type.name}::${nodeAbsoluteRef}`;
|
|
265
|
-
if (
|
|
265
|
+
if (seenNodes.has(nodeId)) {
|
|
266
266
|
return;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
seenNodes.add(nodeId);
|
|
270
270
|
|
|
271
271
|
const [_, anchor] = Object.entries(node).find(([key]) => key === '$anchor') || [];
|
|
272
272
|
if (anchor) {
|