Skip to content

Commit

Permalink
Add support JsonFormat on enum (#1746)
Browse files Browse the repository at this point in the history
  • Loading branch information
altro3 authored Sep 5, 2024
1 parent fc503bd commit ce296f8
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import static io.micronaut.openapi.OpenApiUtils.CONVERT_JSON_MAPPER;
import static io.micronaut.openapi.OpenApiUtils.JSON_MAPPER;
import static io.micronaut.openapi.visitor.ContextUtils.warn;
import static io.micronaut.openapi.visitor.ElementUtils.isEnum;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_BEARER_FORMAT;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_CONTENT;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_FLOWS;
Expand Down Expand Up @@ -596,7 +597,7 @@ public static SecurityRequirement mapToSecurityRequirement(AnnotationValue<io.sw
public static void setDefaultValueObject(Schema<?> schema, String defaultValue, @Nullable Element element, @Nullable String schemaType, @Nullable String schemaFormat, boolean isMicronautFormat, VisitorContext context) {
try {
Pair<String, String> typeAndFormat;
if (element instanceof EnumElement enumEl) {
if (element instanceof EnumElement enumEl && isEnum(enumEl)) {
typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, schemaType, schemaFormat);
} else {
typeAndFormat = Pair.of(schemaType, schemaFormat);
Expand Down Expand Up @@ -649,7 +650,7 @@ public static Pair<String, String> checkEnumJsonValueType(VisitorContext context
Pair<String, String> result = null;
if (firstMethod != null) {
ClassElement returnType = firstMethod.getReturnType();
if (returnType.isEnum()) {
if (isEnum(returnType)) {
return checkEnumJsonValueType(context, (EnumElement) returnType, null, null);
}
result = ConvertUtils.getTypeAndFormatByClass(returnType.getName(), returnType.isArray(), returnType);
Expand All @@ -661,7 +662,7 @@ public static Pair<String, String> checkEnumJsonValueType(VisitorContext context
if (CollectionUtils.isNotEmpty(fields)) {
var firstField = fields.get(0);
ClassElement fieldType = firstField.getType();
if (fieldType.isEnum()) {
if (isEnum(fieldType)) {
return checkEnumJsonValueType(context, (EnumElement) fieldType, null, null);
}
result = ConvertUtils.getTypeAndFormatByClass(fieldType.getName(), fieldType.isArray(), fieldType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.micronaut.openapi.visitor;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
Expand Down Expand Up @@ -470,4 +471,17 @@ public static boolean isTypeWithGenericNullable(ClassElement type) {
|| type.isAssignable(AtomicReference.class)
;
}

public static boolean isEnum(ClassElement classElement) {

var isEnum = classElement.isEnum();

var jsonFormatAnn = classElement.getAnnotation(JsonFormat.class);
if (jsonFormatAnn == null) {
return isEnum;
}

var jsonShape = jsonFormatAnn.get("shape", JsonFormat.Shape.class).orElse(JsonFormat.Shape.ANY);
return jsonShape != JsonFormat.Shape.OBJECT && isEnum;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
import static io.micronaut.openapi.visitor.ElementUtils.getAnnotation;
import static io.micronaut.openapi.visitor.ElementUtils.getAnnotationMetadata;
import static io.micronaut.openapi.visitor.ElementUtils.isAnnotationPresent;
import static io.micronaut.openapi.visitor.ElementUtils.isEnum;
import static io.micronaut.openapi.visitor.ElementUtils.isFileUpload;
import static io.micronaut.openapi.visitor.ElementUtils.isNotNullable;
import static io.micronaut.openapi.visitor.ElementUtils.isNullable;
Expand Down Expand Up @@ -274,7 +275,7 @@ public static Schema<?> readSchema(AnnotationValue<io.swagger.v3.oas.annotations
String elFormat = schema.getFormat();
if (elType == null && type instanceof ClassElement classEl) {
Pair<String, String> typeAndFormat;
if (classEl instanceof EnumElement enumEl) {
if (classEl instanceof EnumElement enumEl && isEnum(enumEl)) {
typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat);
} else {
typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classEl.getName(), classEl.isArray(), classEl);
Expand All @@ -287,7 +288,7 @@ public static Schema<?> readSchema(AnnotationValue<io.swagger.v3.oas.annotations
}
}

if (type instanceof EnumElement enumEl) {
if (type instanceof EnumElement enumEl && isEnum(enumEl)) {
if (CollectionUtils.isEmpty(schema.getEnum())) {
schema.setEnum(getEnumValues(enumEl, schema.getType(), schema.getFormat(), context));
}
Expand Down Expand Up @@ -354,7 +355,7 @@ public static Schema<?> getSchemaDefinition(OpenAPI openAPI,
JavadocDescription javadoc = Utils.getJavadocParser().parse(type.getDocumentation().orElse(null));
if (schema == null) {

if (type instanceof EnumElement enumEl) {
if (type instanceof EnumElement enumEl && isEnum(enumEl)) {
schema = setSpecVersion(new Schema<>());
schema.setName(schemaName);
processJacksonDescription(enumEl, schema);
Expand Down Expand Up @@ -677,7 +678,7 @@ public static Schema<?> resolveSchema(OpenAPI openApi, @Nullable Element definin

Schema<?> schema = null;

if (type instanceof EnumElement enumEl) {
if (type instanceof EnumElement enumEl && isEnum(enumEl)) {
schema = getSchemaDefinition(openApi, context, enumEl, typeArgs, definingElement, mediaTypes, jsonViewClass);
if (isArray != null && isArray) {
schema = SchemaUtils.arraySchema(schema);
Expand Down Expand Up @@ -1408,7 +1409,7 @@ public static Schema<?> bindSchemaAnnotationValue(VisitorContext context, TypedE
Pair<String, String> typeAndFormat;
if (classEl.isIterable()) {
typeAndFormat = Pair.of(TYPE_ARRAY, null);
} else if (classEl instanceof EnumElement enumEl) {
} else if (classEl instanceof EnumElement enumEl && isEnum(enumEl)) {
typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null);
} else {
typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classEl.getName(), classEl.isArray(), classEl);
Expand Down Expand Up @@ -1515,7 +1516,7 @@ public static void processSchemaProperty(VisitorContext context, TypedElement el
String elFormat = schemaAnn.stringValue(PROP_ONE_FORMAT).orElse(null);
if (elType == null && elementType != null) {
Pair<String, String> typeAndFormat;
if (elementType instanceof EnumElement enumEl) {
if (elementType instanceof EnumElement enumEl && isEnum(enumEl)) {
typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat);
} else {
typeAndFormat = ConvertUtils.getTypeAndFormatByClass(elementType.getName(), elementType.isArray(), elementType);
Expand Down Expand Up @@ -1636,6 +1637,7 @@ private static Schema<?> processSuperTypes(Schema<?> schema,
} else {
var superType = classElement.getSuperType().orElse(null);
if (superType != null
&& !Enum.class.getName().equals(superType.getName())
// check protobuf generated classes
&& !isProtobufMessageClass(superType)) {
superTypes.add(superType);
Expand Down Expand Up @@ -1896,7 +1898,7 @@ private static void processSchemaAnn(Schema schemaToBind, VisitorContext context
if (element instanceof ClassElement classElement) {
if (classElement.isIterable()) {
typeAndFormat = Pair.of(TYPE_ARRAY, null);
} else if (element instanceof EnumElement enumEl) {
} else if (element instanceof EnumElement enumEl && isEnum(enumEl)) {
typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null);
} else {
typeAndFormat = ConvertUtils.getTypeAndFormatByClass(classElement.getName(), classElement.isArray(), classElement);
Expand Down Expand Up @@ -2401,7 +2403,7 @@ private static Schema<?> processMapSchema(ClassElement type, Map<String, ClassEl
// Case, when map key is enumeration
ClassElement keyType = typeArgs.get("K");
ClassElement valueType = typeArgs.get("V");
if (keyType.isEnum()) {
if (isEnum(keyType)) {
var enumSchema = getSchemaDefinition(openApi, context, keyType, keyType.getTypeArguments(), null, mediaTypes, null);
if (enumSchema != null && enumSchema.get$ref() != null) {
enumSchema = getSchemaByRef(enumSchema, openApi);
Expand Down Expand Up @@ -2566,7 +2568,7 @@ private static Schema<?> doBindSchemaAnnotationValue(VisitorContext context, Typ
if (elType == null && element != null) {
ClassElement typeEl = element.getType();
Pair<String, String> typeAndFormat;
if (typeEl instanceof EnumElement enumEl) {
if (typeEl instanceof EnumElement enumEl && isEnum(enumEl)) {
typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, elFormat);
} else {
typeAndFormat = ConvertUtils.getTypeAndFormatByClass(typeEl.getName(), typeEl.isArray(), typeEl);
Expand Down Expand Up @@ -2842,6 +2844,9 @@ private static void handleUnwrapped(VisitorContext context, Element element, Cla

private static boolean doesParamExistsMandatoryInConstructor(Element element, @Nullable Element classElement) {
if (classElement instanceof ClassElement classEl) {
if (classEl.isEnum()) {
return true;
}
return classEl.getPrimaryConstructor().flatMap(methodElement -> Arrays.stream(methodElement.getParameters())
.filter(parameterElement -> parameterElement.getName().equals(element.getName()))
.map(parameterElement -> !parameterElement.isNullable())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1341,4 +1341,65 @@ class MyBean {}
schemas.ResourceSearchVO.properties.type2.default == "VAL2"
schemas.ResourceSearchVO.properties.type3.$ref == '#/components/schemas/ResourceEnum'
}

void "test JsonFormat on enum"() {

when:
buildBeanDefinition('test.MyBean', '''
package test;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;
@Controller
class HelloController {
@Get("/foo")
User getFoo(@QueryValue @Nullable User user) {
return null;
}
}
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
enum User {
FOO(1, "Foo"),
BAR(2, "Bar");
@JsonProperty("id")
public final int id;
@JsonProperty("name")
public final String name;
User(int id, String name) {
this.id = id;
this.name = name;
}
}
@jakarta.inject.Singleton
class MyBean {}
''')
then: "the state is correct"
Utils.testReference != null

when: "The OpenAPI is retrieved"
def openApi = Utils.testReference
def schemas = openApi.components.schemas

then: "the components are valid"
schemas.User
schemas.User.type == 'object'
schemas.User.required
schemas.User.required.size() == 2
schemas.User.required[0] == 'id'
schemas.User.required[1] == 'name'
schemas.User.properties.id.type == "integer"
schemas.User.properties.id.format == "int32"
schemas.User.properties.name.type == "string"
}
}

0 comments on commit ce296f8

Please sign in to comment.