Skip to content

Commit b98b476

Browse files
committed
fix: reattach enumAsRef for OAS 3.1 and OAS 3.0 ALL_OF. Fixes #4932
1 parent dd250ff commit b98b476

File tree

4 files changed

+214
-20
lines changed

4 files changed

+214
-20
lines changed

modules/swagger-core/src/main/java/io/swagger/v3/core/converter/AnnotatedType.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class AnnotatedType {
2121
private boolean schemaProperty;
2222
private Annotation[] ctxAnnotations;
2323
private boolean resolveAsRef;
24+
private boolean resolveEnumAsRef;
2425
private JsonView jsonViewAnnotation;
2526
private boolean includePropertiesWithoutJSONView = true;
2627
private boolean skipSchemaName;
@@ -88,6 +89,19 @@ public AnnotatedType resolveAsRef(boolean resolveAsRef) {
8889
return this;
8990
}
9091

92+
public boolean isResolveEnumAsRef() {
93+
return resolveEnumAsRef;
94+
}
95+
96+
public void setResolveEnumAsRef(boolean resolveEnumAsRef) {
97+
this.resolveEnumAsRef = resolveEnumAsRef;
98+
}
99+
100+
public AnnotatedType resolveEnumAsRef(boolean resolveEnumAsRef) {
101+
this.resolveEnumAsRef = resolveEnumAsRef;
102+
return this;
103+
}
104+
91105
public boolean isSchemaProperty() {
92106
return schemaProperty;
93107
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
411411
schema.setItems(model);
412412
return schema;
413413
}
414-
if (type.isEnumType() && shouldResolveEnumAsRef(resolvedSchemaAnnotation)) {
414+
if (type.isEnumType() && shouldResolveEnumAsRef(resolvedSchemaAnnotation, annotatedType.isResolveEnumAsRef())) {
415415
// Store off the ref and add the enum as a top-level model
416416
context.defineModel(name, model, annotatedType, null);
417417
// Return the model as a ref only property
@@ -794,7 +794,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
794794
.skipSchemaName(true)
795795
.schemaProperty(true)
796796
.components(annotatedType.getComponents())
797-
.propertyName(propName);
797+
.propertyName(propName)
798+
.resolveEnumAsRef(AnnotationsUtils.computeEnumAsRef(ctxSchema, ctxArraySchema));
798799
if (
799800
Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
800801
Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
@@ -1239,8 +1240,8 @@ private Stream<Annotation> getGenericTypeArgumentAnnotations(java.lang.reflect.A
12391240
.orElseGet(Stream::of);
12401241
}
12411242

1242-
private boolean shouldResolveEnumAsRef(io.swagger.v3.oas.annotations.media.Schema resolvedSchemaAnnotation) {
1243-
return (resolvedSchemaAnnotation != null && resolvedSchemaAnnotation.enumAsRef()) || ModelResolver.enumsAsRef;
1243+
private boolean shouldResolveEnumAsRef(io.swagger.v3.oas.annotations.media.Schema resolvedSchemaAnnotation, boolean isResolveEnumAsRef) {
1244+
return (resolvedSchemaAnnotation != null && resolvedSchemaAnnotation.enumAsRef()) || ModelResolver.enumsAsRef || isResolveEnumAsRef;
12441245
}
12451246

12461247
protected Type findJsonValueType(final BeanDescription beanDesc) {

modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,4 +2907,13 @@ public static Schema.SchemaResolution resolveSchemaResolution(Schema.SchemaResol
29072907
}
29082908
return globalSchemaResolution;
29092909
}
2910+
2911+
public static boolean computeEnumAsRef(io.swagger.v3.oas.annotations.media.Schema ctxSchema, io.swagger.v3.oas.annotations.media.ArraySchema ctxArraySchema) {
2912+
if (ctxSchema != null && ctxSchema.enumAsRef()) {
2913+
return ctxSchema.enumAsRef();
2914+
} else if(ctxArraySchema != null && ctxArraySchema.schema() != null && ctxArraySchema.schema().enumAsRef()) {
2915+
return ctxArraySchema.schema().enumAsRef();
2916+
}
2917+
return false;
2918+
}
29102919
}

modules/swagger-core/src/test/java/io/swagger/v3/core/resolving/EnumTest.java

Lines changed: 186 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@
99
import io.swagger.v3.oas.models.media.StringSchema;
1010
import org.testng.annotations.Test;
1111

12-
import java.util.ArrayList;
13-
import java.util.Arrays;
14-
import java.util.Collection;
12+
import java.util.*;
13+
import java.util.stream.Collectors;
1514

16-
import static org.testng.Assert.assertEquals;
17-
import static org.testng.Assert.assertNotNull;
18-
import static org.testng.Assert.assertTrue;
15+
import static org.testng.Assert.*;
16+
import static org.testng.AssertJUnit.assertFalse;
1917

2018
public class EnumTest extends SwaggerTestBase {
2119

@@ -33,7 +31,6 @@ public void testEnum() {
3331
new ArrayList<String>(Collections2.transform(Arrays.asList(Currency.values()), Functions.toStringFunction()));
3432
assertEquals(strModel.getEnum(), modelValues);
3533

36-
3734
final Schema property = context.resolve(new AnnotatedType().type(Currency.class).schemaProperty(true));
3835
assertNotNull(property);
3936
assertTrue(property instanceof StringSchema);
@@ -50,40 +47,213 @@ public void testEnumGenerics() {
5047
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
5148

5249
final Schema model = context.resolve((new AnnotatedType().type(Contract.class)));
53-
assertNotNull(model);
54-
assertEquals(model.getName(), "Contract");
55-
assertTrue(model.getProperties().containsKey("type"));
56-
assertNotNull(model.getProperties().get("type"));
50+
assertBasicModelStructure(model, "Contract");
51+
assertPropertyExists(model, "type");
5752
}
5853

5954
@Test
60-
public void testEnumPropertyWithSchemaAnnotation() {
55+
public void testEnumPropertyWithSchemaAnnotation31() {
6156
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
6257
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
6358

6459
final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumAsRefProperty.class));
65-
assertNotNull(model);
66-
assertEquals(model.getName(), "ClassWithEnumAsRefProperty");
67-
assertTrue(model.getProperties().containsKey("enumWithSchemaProperty"));
60+
assertBasicModelStructure(model, "ClassWithEnumAsRefProperty");
61+
assertPropertyExists(model, "enumWithSchemaProperty");
62+
63+
final Schema enumPropertySchema = (Schema) model.getProperties().get("enumWithSchemaProperty");
64+
assertEnumAsRefProperty(enumPropertySchema, "#/components/schemas/EnumWithSchemaProperty");
65+
assertEquals(enumPropertySchema.getDescription(), "Property description");
66+
}
67+
68+
@Test
69+
public void testEnumPropertyWithSchemaAnnotation30() {
70+
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false);
71+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
72+
73+
final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumAsRefProperty.class));
74+
assertBasicModelStructure(model, "ClassWithEnumAsRefProperty");
75+
assertPropertyExists(model, "enumWithSchemaProperty");
76+
6877
final Schema enumPropertySchema = (Schema) model.getProperties().get("enumWithSchemaProperty");
78+
assertEnumAsRefProperty(enumPropertySchema, "#/components/schemas/EnumWithSchemaProperty");
79+
80+
}
81+
82+
@Test
83+
public void testEnumPropertyWithGlobalSwitchOnlyOpenApi31() {
84+
ModelResolver.enumsAsRef = true;
85+
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
86+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
87+
88+
final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
89+
assertBasicModelStructure(model, "ClassWithPlainEnum");
90+
assertPropertyExists(model, "plainEnum");
91+
92+
final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
6993
assertNotNull(enumPropertySchema.get$ref());
94+
assertNull(enumPropertySchema.getEnum());
95+
assertEquals(enumPropertySchema.getDescription(), "Plain enum property");
96+
97+
assertEnumComponentExists(context, ClassWithPlainEnum.PlainEnum.values(), null);
98+
99+
// Reset the static field
100+
ModelResolver.enumsAsRef = false;
101+
}
102+
103+
@Test
104+
public void testControlTestNoRefOpenApi31() {
105+
ModelResolver.enumsAsRef = false;
106+
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
107+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
108+
109+
final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
110+
assertBasicModelStructure(model, "ClassWithPlainEnum");
111+
assertPropertyExists(model, "plainEnum");
112+
113+
final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
114+
assertInlineEnumProperty(enumPropertySchema);
115+
116+
// Apply broad assertions - verify no components are created for inline enums
117+
Map<String, Schema> components = context.getDefinedModels();
118+
if (components != null && !components.isEmpty()) {
119+
Set<String> expected = Arrays.stream(ClassWithPlainEnum.PlainEnum.values())
120+
.map(Enum::name)
121+
.collect(Collectors.toSet());
122+
Schema enumComponent = findEnumComponent(components, expected);
123+
assertNull(enumComponent, "No enum component should exist for inline enums");
124+
}
125+
}
126+
127+
@Test
128+
public void testControlTestNoRefOpenApi30() {
129+
ModelResolver.enumsAsRef = false;
130+
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false);
131+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
132+
133+
final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
134+
assertBasicModelStructure(model, "ClassWithPlainEnum");
135+
assertPropertyExists(model, "plainEnum");
136+
137+
final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
138+
assertInlineEnumProperty(enumPropertySchema);
139+
140+
// Apply broad assertions - verify no components are created for inline enums
141+
Map<String, Schema> components = context.getDefinedModels();
142+
if (components != null && !components.isEmpty()) {
143+
Set<String> expected = Arrays.stream(ClassWithPlainEnum.PlainEnum.values())
144+
.map(Enum::name)
145+
.collect(Collectors.toSet());
146+
Schema enumComponent = findEnumComponent(components, expected);
147+
assertNull(enumComponent, "No enum component should exist for inline enums");
148+
}
149+
}
150+
151+
@Test
152+
public void testEnumWithAllOfSchemaResolutionOpenApi30() {
153+
final ModelResolver modelResolver = new ModelResolver(mapper())
154+
.openapi31(false)
155+
.schemaResolution(Schema.SchemaResolution.ALL_OF);
156+
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);
157+
158+
final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumAsRefProperty.class));
159+
assertBasicModelStructure(model, "ClassWithEnumAsRefProperty");
160+
assertPropertyExists(model, "enumWithSchemaProperty");
161+
162+
final Schema enumPropertySchema = (Schema) model.getProperties().get("enumWithSchemaProperty");
163+
164+
boolean hasEnumRef = false;
165+
for (Object allOfItem : enumPropertySchema.getAllOf()) {
166+
if (allOfItem instanceof Schema) {
167+
Schema allOfSchema = (Schema) allOfItem;
168+
if ("#/components/schemas/EnumWithSchemaProperty".equals(allOfSchema.get$ref())) {
169+
hasEnumRef = true;
170+
break;
171+
}
172+
}
173+
}
174+
assertTrue(hasEnumRef, "AllOf should contain reference to enum component");
175+
assertEnumComponentExists(context, ClassWithEnumAsRefProperty.EnumWithSchemaProperty.values(), "Enum description");
176+
}
177+
178+
private void assertBasicModelStructure(Schema model, String expectedName) {
179+
assertNotNull(model);
180+
assertEquals(model.getName(), expectedName);
181+
}
182+
183+
private void assertPropertyExists(Schema model, String propertyName) {
184+
assertTrue(model.getProperties().containsKey(propertyName));
185+
assertNotNull(model.getProperties().get(propertyName));
186+
}
187+
188+
private void assertEnumComponentExists(ModelConverterContextImpl context, Enum<?>[] enumValues, String expectedDescription) {
189+
Map<String, Schema> components = context.getDefinedModels();
190+
assertNotNull(components);
191+
assertFalse(components.isEmpty());
192+
193+
Set<String> expected = Arrays.stream(enumValues)
194+
.map(Enum::name)
195+
.collect(Collectors.toCollection(LinkedHashSet::new));
196+
197+
Schema enumComponent = findEnumComponent(components, expected);
198+
assertNotNull(enumComponent);
199+
assertEquals(enumComponent.getDescription(), expectedDescription);
200+
}
201+
202+
private Schema findEnumComponent(Map<String, Schema> components, Set<String> expectedValues) {
203+
return components.values().stream()
204+
.filter(Objects::nonNull)
205+
.filter(s -> s.getEnum() != null)
206+
.filter(s -> {
207+
List<?> ev = s.getEnum();
208+
Set<String> vals = ev.stream().map(Object::toString).collect(Collectors.toSet());
209+
return vals.containsAll(expectedValues) && expectedValues.containsAll(vals);
210+
})
211+
.findFirst()
212+
.orElse(null);
213+
}
214+
215+
private void assertEnumAsRefProperty(Schema propertySchema, String expectedRef) {
216+
assertEquals(propertySchema.get$ref(), expectedRef);
217+
assertNull(propertySchema.getEnum());
218+
}
219+
220+
private void assertInlineEnumProperty(Schema propertySchema) {
221+
assertNotNull(propertySchema.getEnum());
222+
assertNull(propertySchema.get$ref());
70223
}
71224

72225
public static class ClassWithEnumAsRefProperty {
73226

74-
@io.swagger.v3.oas.annotations.media.Schema(enumAsRef = true)
227+
@io.swagger.v3.oas.annotations.media.Schema(enumAsRef = true, description = "Property description", maximum = "1923234")
75228
public final EnumWithSchemaProperty enumWithSchemaProperty;
76229

77230
public ClassWithEnumAsRefProperty(EnumWithSchemaProperty enumWithSchemaProperty) {
78231
this.enumWithSchemaProperty = enumWithSchemaProperty;
79232
}
80233

234+
@io.swagger.v3.oas.annotations.media.Schema(description = "Enum description")
81235
public enum EnumWithSchemaProperty {
82236
VALUE1,
83237
VALUE2
84238
}
85239
}
86240

241+
public static class ClassWithPlainEnum {
242+
243+
@io.swagger.v3.oas.annotations.media.Schema(description = "Plain enum property")
244+
public final PlainEnum plainEnum;
245+
246+
public ClassWithPlainEnum(PlainEnum plainEnum) {
247+
this.plainEnum = plainEnum;
248+
}
249+
250+
public enum PlainEnum {
251+
ONE,
252+
TWO,
253+
THREE
254+
}
255+
}
256+
87257
public enum Currency {
88258
USA, CANADA
89259
}

0 commit comments

Comments
 (0)