Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support JsonFormat on enum #1746

Merged
merged 1 commit into from
Sep 5, 2024
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 @@ -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"
}
}
Loading