Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class AnnotatedType {
private boolean schemaProperty;
private Annotation[] ctxAnnotations;
private boolean resolveAsRef;
private boolean resolveEnumAsRef;
private JsonView jsonViewAnnotation;
private boolean includePropertiesWithoutJSONView = true;
private boolean skipSchemaName;
Expand Down Expand Up @@ -88,6 +89,19 @@ public AnnotatedType resolveAsRef(boolean resolveAsRef) {
return this;
}

public boolean isResolveEnumAsRef() {
return resolveEnumAsRef;
}

public void setResolveEnumAsRef(boolean resolveEnumAsRef) {
this.resolveEnumAsRef = resolveEnumAsRef;
}

public AnnotatedType resolveEnumAsRef(boolean resolveEnumAsRef) {
this.resolveEnumAsRef = resolveEnumAsRef;
return this;
}

public boolean isSchemaProperty() {
return schemaProperty;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
schema.setItems(model);
return schema;
}
if (type.isEnumType() && shouldResolveEnumAsRef(resolvedSchemaAnnotation)) {
if (type.isEnumType() && shouldResolveEnumAsRef(resolvedSchemaAnnotation, annotatedType.isResolveEnumAsRef())) {
// Store off the ref and add the enum as a top-level model
context.defineModel(name, model, annotatedType, null);
// Return the model as a ref only property
Expand Down Expand Up @@ -540,6 +540,7 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
.propertyName(annotatedType.getPropertyName())
.jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
.components(annotatedType.getComponents())
.resolveEnumAsRef(annotatedType.isResolveEnumAsRef())
.parent(annotatedType.getParent()));

if (items == null) {
Expand Down Expand Up @@ -794,7 +795,8 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context
.skipSchemaName(true)
.schemaProperty(true)
.components(annotatedType.getComponents())
.propertyName(propName);
.propertyName(propName)
.resolveEnumAsRef(AnnotationsUtils.computeEnumAsRef(ctxSchema, ctxArraySchema));
if (
Schema.SchemaResolution.ALL_OF.equals(resolvedSchemaResolution) ||
Schema.SchemaResolution.ALL_OF_REF.equals(resolvedSchemaResolution) ||
Expand Down Expand Up @@ -1239,8 +1241,8 @@ private Stream<Annotation> getGenericTypeArgumentAnnotations(java.lang.reflect.A
.orElseGet(Stream::of);
}

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

protected Type findJsonValueType(final BeanDescription beanDesc) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2907,4 +2907,13 @@ public static Schema.SchemaResolution resolveSchemaResolution(Schema.SchemaResol
}
return globalSchemaResolution;
}

public static boolean computeEnumAsRef(io.swagger.v3.oas.annotations.media.Schema ctxSchema, io.swagger.v3.oas.annotations.media.ArraySchema ctxArraySchema) {
if (ctxSchema != null && ctxSchema.enumAsRef()) {
return ctxSchema.enumAsRef();
} else if(ctxArraySchema != null && ctxArraySchema.schema() != null && ctxArraySchema.schema().enumAsRef()) {
return ctxArraySchema.schema().enumAsRef();
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
import io.swagger.v3.oas.models.media.StringSchema;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.*;
import java.util.stream.Collectors;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.*;
import static org.testng.AssertJUnit.assertFalse;

public class EnumTest extends SwaggerTestBase {

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


final Schema property = context.resolve(new AnnotatedType().type(Currency.class).schemaProperty(true));
assertNotNull(property);
assertTrue(property instanceof StringSchema);
Expand All @@ -50,10 +47,262 @@ public void testEnumGenerics() {
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve((new AnnotatedType().type(Contract.class)));
assertBasicModelStructure(model, "Contract");
assertPropertyExists(model, "type");
}

@Test
public void testEnumPropertyWithSchemaAnnotation() {
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumAsRefProperty.class));
assertBasicModelStructure(model, "ClassWithEnumAsRefProperty");
assertPropertyExists(model, "enumWithSchemaProperty");

final Schema enumPropertySchema = (Schema) model.getProperties().get("enumWithSchemaProperty");
assertEnumAsRefProperty(enumPropertySchema, "#/components/schemas/EnumWithSchemaProperty");
assertEquals(enumPropertySchema.getDescription(), "Property description");
}

@Test
public void testEnumPropertyWithGlobalSwitchOnlyOpenApi31() {
ModelResolver.enumsAsRef = true;
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
assertBasicModelStructure(model, "ClassWithPlainEnum");
assertPropertyExists(model, "plainEnum");

final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
assertNotNull(enumPropertySchema.get$ref());
assertNull(enumPropertySchema.getEnum());
assertEquals(enumPropertySchema.getDescription(), "Plain enum property");

assertEnumComponentExists(context, ClassWithPlainEnum.PlainEnum.values(), null);

// Reset the static field
ModelResolver.enumsAsRef = false;
}

@Test
public void testArrayOfEnumWithSchemaAnnotationOpenApi31() {
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumArray.class));
assertBasicModelStructure(model, "ClassWithEnumArray");
assertPropertyExists(model, "enumArray");

final Schema arrayPropertySchema = (Schema) model.getProperties().get("enumArray");
assertArrayWithEnumRef(arrayPropertySchema);

assertEnumComponentExists(context, ClassWithEnumArray.ArrayEnum.values(), "Enum description");
}

@Test
public void testArrayOfEnumWithSchemaAnnotationOpenApi30() {
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumArray.class));
assertBasicModelStructure(model, "ClassWithEnumArray");
assertPropertyExists(model, "enumArray");

final Schema arrayPropertySchema = (Schema) model.getProperties().get("enumArray");
assertArrayWithEnumRef(arrayPropertySchema);

assertEnumComponentExists(context, ClassWithEnumArray.ArrayEnum.values(), "Enum description");
}

@Test
public void testControlTestNoRefOpenApi31() {
ModelResolver.enumsAsRef = false;
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(true);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
assertBasicModelStructure(model, "ClassWithPlainEnum");
assertPropertyExists(model, "plainEnum");

final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
assertInlineEnumProperty(enumPropertySchema);

// Apply broad assertions - verify no components are created for inline enums
Map<String, Schema> components = context.getDefinedModels();
if (components != null && !components.isEmpty()) {
Set<String> expected = Arrays.stream(ClassWithPlainEnum.PlainEnum.values())
.map(Enum::name)
.collect(Collectors.toSet());
Schema enumComponent = findEnumComponent(components, expected);
assertNull(enumComponent, "No enum component should exist for inline enums");
}
}

@Test
public void testControlTestNoRefOpenApi30() {
ModelResolver.enumsAsRef = false;
final ModelResolver modelResolver = new ModelResolver(mapper()).openapi31(false);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve(new AnnotatedType().type(ClassWithPlainEnum.class));
assertBasicModelStructure(model, "ClassWithPlainEnum");
assertPropertyExists(model, "plainEnum");

final Schema enumPropertySchema = (Schema) model.getProperties().get("plainEnum");
assertInlineEnumProperty(enumPropertySchema);

// Apply broad assertions - verify no components are created for inline enums
Map<String, Schema> components = context.getDefinedModels();
if (components != null && !components.isEmpty()) {
Set<String> expected = Arrays.stream(ClassWithPlainEnum.PlainEnum.values())
.map(Enum::name)
.collect(Collectors.toSet());
Schema enumComponent = findEnumComponent(components, expected);
assertNull(enumComponent, "No enum component should exist for inline enums");
}
}

@Test
public void testEnumWithAllOfSchemaResolutionOpenApi30() {
final ModelResolver modelResolver = new ModelResolver(mapper())
.openapi31(false)
.schemaResolution(Schema.SchemaResolution.ALL_OF);
final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

final Schema model = context.resolve(new AnnotatedType().type(ClassWithEnumAsRefProperty.class));
assertBasicModelStructure(model, "ClassWithEnumAsRefProperty");
assertPropertyExists(model, "enumWithSchemaProperty");

final Schema enumPropertySchema = (Schema) model.getProperties().get("enumWithSchemaProperty");

boolean hasEnumRef = false;
for (Object allOfItem : enumPropertySchema.getAllOf()) {
if (allOfItem instanceof Schema) {
Schema allOfSchema = (Schema) allOfItem;
if ("#/components/schemas/EnumWithSchemaProperty".equals(allOfSchema.get$ref())) {
hasEnumRef = true;
break;
}
}
}
assertTrue(hasEnumRef, "AllOf should contain reference to enum component");
assertEnumComponentExists(context, ClassWithEnumAsRefProperty.EnumWithSchemaProperty.values(), "Enum description");
}

private void assertBasicModelStructure(Schema model, String expectedName) {
assertNotNull(model);
assertEquals(model.getName(), "Contract");
assertTrue(model.getProperties().containsKey("type"));
assertNotNull(model.getProperties().get("type"));
assertEquals(model.getName(), expectedName);
}

private void assertPropertyExists(Schema model, String propertyName) {
assertTrue(model.getProperties().containsKey(propertyName));
assertNotNull(model.getProperties().get(propertyName));
}

private void assertEnumComponentExists(ModelConverterContextImpl context, Enum<?>[] enumValues, String expectedDescription) {
Map<String, Schema> components = context.getDefinedModels();
assertNotNull(components);
assertFalse(components.isEmpty());

Set<String> expected = Arrays.stream(enumValues)
.map(Enum::name)
.collect(Collectors.toCollection(LinkedHashSet::new));

Schema enumComponent = findEnumComponent(components, expected);
assertNotNull(enumComponent);
assertEquals(enumComponent.getDescription(), expectedDescription);
}

private void assertEnumComponentExistsWithDefault(ModelConverterContextImpl context, Enum<?>[] enumValues, String expectedDescription, String expectedDefault) {
assertEnumComponentExists(context, enumValues, expectedDescription);

Map<String, Schema> components = context.getDefinedModels();
Set<String> expected = Arrays.stream(enumValues)
.map(Enum::name)
.collect(Collectors.toCollection(LinkedHashSet::new));

Schema enumComponent = findEnumComponent(components, expected);
assertEquals(enumComponent.getDefault(), expectedDefault);
}

private Schema findEnumComponent(Map<String, Schema> components, Set<String> expectedValues) {
return components.values().stream()
.filter(Objects::nonNull)
.filter(s -> s.getEnum() != null)
.filter(s -> {
List<?> ev = s.getEnum();
Set<String> vals = ev.stream().map(Object::toString).collect(Collectors.toSet());
return vals.containsAll(expectedValues) && expectedValues.containsAll(vals);
})
.findFirst()
.orElse(null);
}

private void assertEnumAsRefProperty(Schema propertySchema, String expectedRef) {
assertEquals(propertySchema.get$ref(), expectedRef);
assertNull(propertySchema.getEnum());
}

private void assertInlineEnumProperty(Schema propertySchema) {
assertNotNull(propertySchema.getEnum());
assertNull(propertySchema.get$ref());
}

private void assertArrayWithEnumRef(Schema arrayPropertySchema) {
assertNotNull(arrayPropertySchema.getItems());
assertNotNull(arrayPropertySchema.getItems().get$ref());
}


public static class ClassWithEnumAsRefProperty {

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

public ClassWithEnumAsRefProperty(EnumWithSchemaProperty enumWithSchemaProperty) {
this.enumWithSchemaProperty = enumWithSchemaProperty;
}

@io.swagger.v3.oas.annotations.media.Schema(description = "Enum description")
public enum EnumWithSchemaProperty {
VALUE1,
VALUE2
}
}

public static class ClassWithEnumArray {

@io.swagger.v3.oas.annotations.media.ArraySchema(schema = @io.swagger.v3.oas.annotations.media.Schema(enumAsRef = true, description = "Property Description"))
public final ArrayEnum[] enumArray;

public ClassWithEnumArray(ArrayEnum[] enumArray) {
this.enumArray = enumArray;
}

@io.swagger.v3.oas.annotations.media.Schema(description = "Enum description")
public enum ArrayEnum {
FIRST,
SECOND,
THIRD
}
}

public static class ClassWithPlainEnum {

@io.swagger.v3.oas.annotations.media.Schema(description = "Plain enum property")
public final PlainEnum plainEnum;

public ClassWithPlainEnum(PlainEnum plainEnum) {
this.plainEnum = plainEnum;
}

public enum PlainEnum {
ONE,
TWO,
THREE
}
}

public enum Currency {
Expand Down
Loading