Skip to content

Commit 9f5bf1d

Browse files
committed
fix: remove superfluous ref for a composed schema. Fixes #4959
1 parent 0b327a6 commit 9f5bf1d

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
11091109
}
11101110
}
11111111

1112+
dropRootRefIfComposed(schemaWithCompositionKeys);
11121113
});
11131114

11141115
if (!composedModelPropertiesAsSibling) {
@@ -1208,6 +1209,38 @@ private Stream<Annotation> getRecordComponentAnnotations(BeanPropertyDefinition
12081209
}
12091210
}
12101211

1212+
private void dropRootRefIfComposed(Schema<?> s) {
1213+
if (s == null || s.get$ref() == null) {
1214+
return;
1215+
}
1216+
1217+
if (!isComposedSchema(s)) {
1218+
return;
1219+
}
1220+
1221+
String ref = s.get$ref();
1222+
if (refMatchesAnyComposedItem(s, ref)) {
1223+
s.set$ref(null);
1224+
}
1225+
}
1226+
1227+
private boolean isComposedSchema(Schema<?> s) {
1228+
return (s.getOneOf() != null && !s.getOneOf().isEmpty())
1229+
|| (s.getAnyOf() != null && !s.getAnyOf().isEmpty())
1230+
|| (s.getAllOf() != null && !s.getAllOf().isEmpty());
1231+
}
1232+
1233+
private boolean refMatchesAnyComposedItem(Schema<?> s, String ref) {
1234+
return refMatchesInList(s.getOneOf(), ref)
1235+
|| refMatchesInList(s.getAllOf(), ref)
1236+
|| refMatchesInList(s.getAnyOf(), ref);
1237+
}
1238+
1239+
private boolean refMatchesInList(List<Schema> schemas, String ref) {
1240+
return schemas != null && schemas.stream()
1241+
.anyMatch(schema -> ref.equals(schema.get$ref()));
1242+
}
1243+
12111244
private Boolean isRecordType(BeanPropertyDefinition propDef) {
12121245
try {
12131246
if (propDef.getPrimaryMember() != null) {
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package io.swagger.v3.core.resolving;
2+
3+
import io.swagger.v3.core.converter.AnnotatedType;
4+
import io.swagger.v3.core.converter.ModelConverters;
5+
import io.swagger.v3.core.converter.ResolvedSchema;
6+
import io.swagger.v3.oas.models.media.Schema;
7+
import org.testng.Assert;
8+
import org.testng.annotations.Test;
9+
10+
public class CompositionSuperfluousRefTest {
11+
12+
static class SomeDto {}
13+
static class OtherDto {}
14+
15+
static class MyDtoOneOf {
16+
@io.swagger.v3.oas.annotations.media.Schema(oneOf = { SomeDto.class, OtherDto.class })
17+
public Object myProperty;
18+
}
19+
20+
static class MyDtoWithAnyOf {
21+
@io.swagger.v3.oas.annotations.media.Schema(anyOf = { SomeDto.class, OtherDto.class })
22+
public Object myProperty;
23+
}
24+
25+
static class MyDtoWithAllOf {
26+
@io.swagger.v3.oas.annotations.media.Schema(allOf = { SomeDto.class, OtherDto.class })
27+
public Object myProperty;
28+
}
29+
30+
static class MyDtoWithoutComposition {
31+
@io.swagger.v3.oas.annotations.media.Schema(implementation = SomeDto.class)
32+
public Object myProperty;
33+
}
34+
35+
static class MyDtoWithNonMatchingRef {
36+
@io.swagger.v3.oas.annotations.media.Schema(
37+
ref = "#/components/schemas/ThirdDto",
38+
oneOf = { SomeDto.class, OtherDto.class }
39+
)
40+
public Object myProperty;
41+
}
42+
43+
@Test
44+
public void oneOf_shouldNotHaveRef() {
45+
ResolvedSchema rs = ModelConverters.getInstance(false)
46+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoOneOf.class));
47+
48+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
49+
50+
Assert.assertNull(prop.get$ref());
51+
Assert.assertNotNull(prop.getOneOf());
52+
Assert.assertEquals(prop.getOneOf().size(), 2);
53+
}
54+
55+
@Test
56+
public void anyOf_shouldNotHaveRef() {
57+
ResolvedSchema rs = ModelConverters.getInstance(false)
58+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAnyOf.class));
59+
60+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
61+
62+
Assert.assertNull(prop.get$ref());
63+
Assert.assertNotNull(prop.getAnyOf());
64+
Assert.assertEquals(prop.getAnyOf().size(), 2);
65+
}
66+
67+
@Test
68+
public void allOf_shouldNotHaveRef() {
69+
70+
ResolvedSchema rs = ModelConverters.getInstance(false)
71+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAllOf.class));
72+
73+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
74+
Assert.assertNull(prop.get$ref());
75+
Assert.assertNotNull(prop.getAllOf());
76+
Assert.assertEquals(prop.getAllOf().size(), 2);
77+
}
78+
79+
@Test
80+
public void testNonMatchingRef_shouldPreserveRef() {
81+
ResolvedSchema rs = ModelConverters.getInstance(false)
82+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoWithNonMatchingRef.class));
83+
84+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
85+
86+
Assert.assertNotNull(prop.get$ref());
87+
Assert.assertEquals(prop.get$ref(), "#/components/schemas/ThirdDto");
88+
//In 3.x refs cannot have siblings
89+
Assert.assertNull(prop.getOneOf());
90+
}
91+
92+
@Test
93+
public void oneOf_shouldNotHaveRef31() {
94+
ResolvedSchema rs = ModelConverters.getInstance(true)
95+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoOneOf.class));
96+
97+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
98+
99+
Assert.assertNull(prop.get$ref());
100+
Assert.assertNotNull(prop.getOneOf());
101+
Assert.assertEquals(prop.getOneOf().size(), 2);
102+
}
103+
104+
@Test
105+
public void anyOf_shouldNotHaveRef31() {
106+
ResolvedSchema rs = ModelConverters.getInstance(true)
107+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAnyOf.class));
108+
109+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
110+
111+
Assert.assertNull(prop.get$ref());
112+
Assert.assertNotNull(prop.getAnyOf());
113+
Assert.assertEquals(prop.getAnyOf().size(), 2);
114+
}
115+
116+
@Test
117+
public void allOf_shouldNotHaveRef31() {
118+
119+
ResolvedSchema rs = ModelConverters.getInstance(true)
120+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoWithAllOf.class));
121+
122+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
123+
Assert.assertNull(prop.get$ref());
124+
Assert.assertNotNull(prop.getAllOf());
125+
Assert.assertEquals(prop.getAllOf().size(), 2);
126+
}
127+
128+
@Test
129+
public void testNonMatchingRef_shouldPreserveRef31() {
130+
ResolvedSchema rs = ModelConverters.getInstance(true)
131+
.resolveAsResolvedSchema(new AnnotatedType(MyDtoWithNonMatchingRef.class));
132+
133+
Schema<?> prop = (Schema<?>) rs.schema.getProperties().get("myProperty");
134+
135+
Assert.assertNotNull(prop.get$ref());
136+
Assert.assertEquals(prop.get$ref(), "#/components/schemas/ThirdDto");
137+
Assert.assertNotNull(prop.getOneOf());
138+
Assert.assertEquals(prop.getOneOf().size(), 2);
139+
}
140+
141+
}
142+
143+

0 commit comments

Comments
 (0)