Skip to content

Commit be3de37

Browse files
committed
fix: eliminate cycles introduced by normalization
1 parent 96d2d08 commit be3de37

File tree

3 files changed

+104
-440
lines changed

3 files changed

+104
-440
lines changed

packages/apidom-ast/src/traversal/visitor.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ export const visit = (
455455
nodePredicate = isNode,
456456
nodeCloneFn = cloneNode,
457457
detectCycles = true,
458+
detectCyclesCallback = null,
458459
} = {},
459460
) => {
460461
const visitorKeys = keyMap || {};
@@ -531,6 +532,11 @@ export const visit = (
531532

532533
// cycle detected; skipping over a sub-tree to avoid recursion
533534
if (detectCycles && ancestors.includes(node)) {
535+
if (typeof detectCyclesCallback === 'function') {
536+
// @ts-ignore
537+
detectCyclesCallback(node, key, parent, path, ancestors);
538+
}
539+
534540
path.pop();
535541
continue;
536542
}
@@ -636,6 +642,7 @@ visit[Symbol.for('nodejs.util.promisify.custom')] = async (
636642
nodePredicate = isNode,
637643
nodeCloneFn = cloneNode,
638644
detectCycles = true,
645+
detectCyclesCallback = null,
639646
} = {},
640647
) => {
641648
const visitorKeys = keyMap || {};
@@ -712,6 +719,11 @@ visit[Symbol.for('nodejs.util.promisify.custom')] = async (
712719

713720
// cycle detected; skipping over a sub-tree to avoid recursion
714721
if (detectCycles && ancestors.includes(node)) {
722+
if (typeof detectCyclesCallback === 'function') {
723+
// @ts-ignore
724+
detectCyclesCallback(node, key, parent, path, ancestors);
725+
}
726+
715727
path.pop();
716728
continue;
717729
}

packages/apidom-ns-openapi-3-1/src/refractor/plugins/normalize-discriminator-mapping.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import {
55
isArrayElement,
66
ObjectElement,
77
StringElement,
8+
MemberElement,
89
toValue,
10+
visit,
11+
isMemberElement,
12+
isStringElement,
913
} from '@swagger-api/apidom-core';
1014
import { isReferenceLikeElement, isDiscriminatorElement } from '@swagger-api/apidom-ns-openapi-3-0';
1115

@@ -14,15 +18,21 @@ import OpenApi3_1Element from '../../elements/OpenApi3-1.ts';
1418
import NormalizeStorage from './normalize-header-examples/NormalizeStorage.ts';
1519
import { SchemaElement } from '../registration.ts';
1620
import { isSchemaElement } from '../../predicates.ts';
21+
import DiscriminatorElement from '../../elements/Discriminator.ts';
1722

1823
/**
1924
* Normalization of Discriminator.mapping field.
2025
*
2126
* Discriminator.mapping fields are normalized by adding missing mappings from oneOf/anyOf items
2227
* of the parent Schema Object and transforming existing mappings to Schema Objects.
2328
*
29+
* In case of allOf discriminator, the plugin will add missing mappings based on
30+
* allOf items of other Schema Objects.
31+
*
2432
* The normalized mapping is stored in the Schema.discriminator field as `x-normalized-mapping`.
2533
*
34+
* This plugin is designed to be used on dereferenced OpenAPI 3.1 documents.
35+
*
2636
* NOTE: this plugin is idempotent
2737
* @public
2838
*/
@@ -191,6 +201,48 @@ const plugin =
191201

192202
if (isNormalized) {
193203
schemaElement.discriminator.set('x-normalized-mapping', normalizedMapping);
204+
205+
// dive in and eliminate cycles that might be created by normalization
206+
visit(
207+
schemaElement,
208+
{},
209+
{
210+
// @ts-ignore
211+
detectCyclesCallback: <T extends Element>(
212+
node: T,
213+
nodeKey: string | number,
214+
nodeParent: Element | undefined,
215+
) => {
216+
if (
217+
!nodeParent ||
218+
!isMemberElement(node) ||
219+
!isStringElement(node.key) ||
220+
!node.key.equals('discriminator') ||
221+
!isDiscriminatorElement(node.value)
222+
) {
223+
return;
224+
}
225+
226+
const discriminator = cloneShallow(node.value);
227+
const discriminatorCopy = new DiscriminatorElement();
228+
229+
if (discriminator.get('mapping')) {
230+
discriminatorCopy.mapping = discriminator.get('mapping');
231+
}
232+
233+
if (discriminator.get('propertyName')) {
234+
discriminatorCopy.propertyName = discriminator.get('propertyName');
235+
}
236+
237+
// eslint-disable-next-line no-param-reassign
238+
nodeParent[nodeKey] = new MemberElement(
239+
new StringElement('discriminator'),
240+
discriminatorCopy,
241+
);
242+
},
243+
},
244+
);
245+
194246
storage!.append(schemaJSONPointer);
195247
}
196248
},

0 commit comments

Comments
 (0)