Skip to content

Commit

Permalink
Fix process jackson annotations together with swagger annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
altro3 committed Sep 4, 2024
1 parent 16b07b2 commit c74dd8f
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@ public interface OpenApiModelProp {
String PROP_FLOWS = "flows";
String PROP_OPEN_ID_CONNECT_URL = "openIdConnectUrl";
String PROP_BEARER_FORMAT = "bearerFormat";

String PROP_ACCESS = "access";
}
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ private static void unwrapAllOff(Schema<?> schema) {

for (var entry : innerSchemas.entrySet()) {
var innerSchema = entry.getValue();
innerSchema.setName(null);
if (StringUtils.isNotEmpty(innerSchema.getTitle())) {
schema.setTitle(innerSchema.getTitle());
innerSchema.setTitle(null);
Expand All @@ -544,6 +545,22 @@ private static void unwrapAllOff(Schema<?> schema) {
}
innerSchema.setNullable(null);
}
if (innerSchema.getDefault() != null) {
schema.setDefault(innerSchema.getDefault());
innerSchema.setDefault(null);
}
if (innerSchema.getAnyOf() != null && schema.getAnyOf() == null) {
schema.setAnyOf(innerSchema.getAnyOf());
innerSchema.setAnyOf(null);
}
if (innerSchema.getOneOf() != null && schema.getOneOf() == null) {
schema.setOneOf(innerSchema.getOneOf());
innerSchema.setOneOf(null);
}
if (innerSchema.getNot() != null && schema.getNot() == null) {
schema.setNot(innerSchema.getNot());
innerSchema.setNot(null);
}
if (CollectionUtils.isNotEmpty(innerSchema.getRequired())) {
schema.setRequired(innerSchema.getRequired());
innerSchema.setRequired(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanMap;
import io.micronaut.core.bind.annotation.Bindable;
Expand Down Expand Up @@ -117,6 +116,7 @@
import java.util.OptionalLong;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Function;

Expand Down Expand Up @@ -147,6 +147,7 @@
import static io.micronaut.openapi.visitor.OpenApiApplicationVisitor.resolvePlaceholders;
import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL;
import static io.micronaut.openapi.visitor.OpenApiModelProp.DISCRIMINATOR;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ACCESS;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ACCESS_MODE;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ADDITIONAL_PROPERTIES;
import static io.micronaut.openapi.visitor.OpenApiModelProp.PROP_ALLOWABLE_VALUES;
Expand Down Expand Up @@ -356,7 +357,8 @@ public static Schema<?> getSchemaDefinition(OpenAPI openAPI,
if (type instanceof EnumElement enumEl) {
schema = setSpecVersion(new Schema<>());
schema.setName(schemaName);
if (javadoc != null && StringUtils.hasText(javadoc.getMethodDescription())) {
processJacksonDescription(enumEl, schema);
if (schema.getDescription() == null && javadoc != null && StringUtils.hasText(javadoc.getMethodDescription())) {
schema.setDescription(javadoc.getMethodDescription());
}
schemas.put(schemaName, schema);
Expand All @@ -372,7 +374,8 @@ public static Schema<?> getSchemaDefinition(OpenAPI openAPI,
if (schemaWithSuperTypes != null) {
schema = schemaWithSuperTypes;
}
if (schema != null && javadoc != null && StringUtils.hasText(javadoc.getMethodDescription())) {
processJacksonDescription(type, schema);
if (schema != null && schema.getDescription() == null && javadoc != null && StringUtils.hasText(javadoc.getMethodDescription())) {
schema.setDescription(javadoc.getMethodDescription());
}

Expand Down Expand Up @@ -432,7 +435,7 @@ public static Schema<?> getSchemaDefinition(OpenAPI openAPI,
if (externalDocs != null) {
schema.setExternalDocs(externalDocs);
}
setSchemaDocumentation(type, schema);
setSchemaDescription(type, schema);
var schemaRef = setSpecVersion(new Schema<>());
schemaRef.set$ref(SchemaUtils.schemaRef(schema.getName()));
if (definingElement instanceof ClassElement classEl && classEl.isIterable()) {
Expand Down Expand Up @@ -838,6 +841,8 @@ public static Schema<?> resolveSchema(OpenAPI openApi, @Nullable Element definin
processSchemaAnn(schema, context, definingElement, type, schemaAnnotationValue);
}

processJacksonDescription(definingElement, schema);

if (definingElement != null && StringUtils.isEmpty(schema.getDescription())) {
if (fieldJavadoc != null) {
if (StringUtils.hasText(fieldJavadoc.getMethodDescription())) {
Expand Down Expand Up @@ -928,7 +933,7 @@ public static Schema<?> bindSchemaForElement(VisitorContext context, TypedElemen
}

boolean notOnlyRef = false;
setSchemaDocumentation(element, topLevelSchema);
setSchemaDescription(element, topLevelSchema);
if (StringUtils.isNotEmpty(topLevelSchema.getDescription())) {
notOnlyRef = true;
}
Expand All @@ -952,11 +957,14 @@ && isProtobufGenerated(propertyEl.getOwningType())
SchemaUtils.setNullable(topLevelSchema);
notOnlyRef = true;
}
final String defaultJacksonValue = stringValue(element, JsonProperty.class, PROP_DEFAULT_VALUE).orElse(null);
if (defaultJacksonValue != null && schemaToBind.getDefault() == null) {
setDefaultValueObject(topLevelSchema, defaultJacksonValue, elementType, schemaToBind.getType(), schemaToBind.getFormat(), false, context);
if (processJacksonPropertyAnn(element, elementType, topLevelSchema, schemaAnn, context)) {
notOnlyRef = true;
}
// final String defaultJacksonValue = stringValue(element, JsonProperty.class, PROP_DEFAULT_VALUE).orElse(null);
// if (defaultJacksonValue != null && schemaToBind.getDefault() == null) {
// setDefaultValueObject(topLevelSchema, defaultJacksonValue, elementType, schemaToBind.getType(), schemaToBind.getFormat(), false, context);
// notOnlyRef = true;
// }

boolean addSchemaToBind = !SchemaUtils.isEmptySchema(schemaToBind);

Expand Down Expand Up @@ -1731,37 +1739,59 @@ private static void checkAllOf(Schema<Object> composedSchema) {
composedSchema.addAllOfItem(propSchema);
}

private static void setSchemaDocumentation(Element element, Schema<?> schemaToBind) {
if (StringUtils.isEmpty(schemaToBind.getDescription())) {
// First, find getter method javadoc
String doc = element.getDocumentation().orElse(null);
if (StringUtils.isEmpty(doc)) {
// next, find field javadoc
if (element instanceof MemberElement memberEl) {
List<FieldElement> fields = memberEl.getDeclaringType().getFields();
if (CollectionUtils.isNotEmpty(fields)) {
for (FieldElement field : fields) {
if (field.getName().equals(element.getName())) {
doc = field.getDocumentation().orElse(null);
break;
}
private static void processJacksonDescription(@Nullable Element element, @Nullable Schema<?> schemaToBind) {
if (element == null || schemaToBind == null || StringUtils.isNotEmpty(schemaToBind.getDescription())) {
return;
}
findAnnotation(element, element instanceof ClassElement
? "com.fasterxml.jackson.annotation.JsonClassDescription"
: "com.fasterxml.jackson.annotation.JsonPropertyDescription"
)
.flatMap(ann -> ann.stringValue(PROP_VALUE))
.ifPresent(schemaToBind::setDescription);
}

private static void setSchemaDescription(Element element, Schema<?> schemaToBind) {
if (StringUtils.isNotEmpty(schemaToBind.getDescription())) {
return;
}

processJacksonDescription(element, schemaToBind);
if (StringUtils.isNotEmpty(schemaToBind.getDescription())) {
return;
}

// First, find getter method javadoc
String doc = element.getDocumentation().orElse(null);
if (StringUtils.isEmpty(doc)) {
// next, find field javadoc
if (element instanceof MemberElement memberEl) {
List<FieldElement> fields = memberEl.getDeclaringType().getFields();
if (CollectionUtils.isNotEmpty(fields)) {
for (FieldElement field : fields) {
if (field.getName().equals(element.getName())) {
doc = field.getDocumentation().orElse(null);
break;
}
}
}
}
if (doc != null) {
JavadocDescription desc = Utils.getJavadocParser().parse(doc);
if (StringUtils.hasText(desc.getMethodDescription())) {
schemaToBind.setDescription(desc.getMethodDescription());
}
}
if (doc != null) {
JavadocDescription desc = Utils.getJavadocParser().parse(doc);
if (StringUtils.hasText(desc.getMethodDescription())) {
schemaToBind.setDescription(desc.getMethodDescription());
}
}
}

private static void processSchemaAnn(Schema schemaToBind, VisitorContext context, Element element,
@Nullable ClassElement classEl,
@NonNull AnnotationValue<io.swagger.v3.oas.annotations.media.Schema> schemaAnn) {
@Nullable AnnotationValue<io.swagger.v3.oas.annotations.media.Schema> schemaAnn) {

if (schemaAnn == null) {
return;
}
Map<CharSequence, Object> annValues = schemaAnn.getValues();
if (annValues.containsKey(PROP_NAME)) {
schemaToBind.setName((String) annValues.get(PROP_NAME));
Expand Down Expand Up @@ -2136,6 +2166,59 @@ private static void processArgTypeAnnotations(ClassElement type, @Nullable Schem
processJakartaValidationAnnotations(type, type, schema);
}

private static boolean processJacksonPropertyAnn(Element element, ClassElement elType, Schema<?> schemaToBind,
@Nullable AnnotationValue<io.swagger.v3.oas.annotations.media.Schema> schemaAnn,
VisitorContext context) {

var swaggerAccessMode = schemaAnn != null ? schemaAnn.stringValue(PROP_ACCESS_MODE).orElse(null) : null;
var swaggerDefaultValue = schemaAnn != null ? schemaAnn.stringValue(PROP_DEFAULT_VALUE).orElse(null) : null;

var reference = new AtomicReference<>(false);
findAnnotation(element, "com.fasterxml.jackson.annotation.JsonProperty")
.ifPresent(ann -> {
if (swaggerAccessMode == null) {
ann.get(PROP_ACCESS, JsonProperty.Access.class).ifPresent(access -> {
switch (access) {
case READ_ONLY:
schemaToBind.setWriteOnly(null);
schemaToBind.setReadOnly(true);
reference.set(true);
break;
case WRITE_ONLY:
schemaToBind.setWriteOnly(true);
schemaToBind.setReadOnly(null);
reference.set(true);
break;
case READ_WRITE:
schemaToBind.setWriteOnly(null);
schemaToBind.setReadOnly(null);
break;
default:
break;
}
});
}
if (swaggerDefaultValue == null) {
ann.stringValue(PROP_DEFAULT_VALUE).ifPresent(defaultValue -> {
Pair<String, String> typeAndFormat;
if (elType.isIterable()) {
typeAndFormat = Pair.of(TYPE_ARRAY, null);
} else if (elType instanceof EnumElement enumEl) {
typeAndFormat = ConvertUtils.checkEnumJsonValueType(context, enumEl, null, null);
} else {
typeAndFormat = ConvertUtils.getTypeAndFormatByClass(elType.getName(), elType.isArray());
}
setDefaultValueObject(schemaToBind, defaultValue, elType, typeAndFormat.getFirst(), typeAndFormat.getSecond(), false, context);
if (schemaToBind.getDefault() != null) {
reference.set(true);
}
});
}
});

return reference.get();
}

private static void processJakartaValidationAnnotations(Element element, ClassElement elementType, Schema<?> schemaToBind) {

final boolean isIterableOrMap = elementType.isIterable() || elementType.isAssignable(Map.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1434,14 +1434,14 @@ public class MyBean {}
schema.properties.uuid.type == 'string'
schema.properties.uuid.format == 'uuid'

// TODO: need to add support custom format for DateTime
schema.properties.date.default == OffsetDateTime.parse('2007-12-03T10:15:30+01:00')
schema.properties.date.type == 'string'
schema.properties.date.format == 'date-time'

schema.properties.mySubObject.allOf.get(1).default == 'myDefault3'
schema.properties.mySubObject.allOf.get(1).type == null
schema.properties.mySubObject.allOf.get(1).format == null
schema.properties.mySubObject.default == 'myDefault3'
!schema.properties.mySubObject.type
!schema.properties.mySubObject.format
schema.properties.mySubObject.allOf[0].$ref == "#/components/schemas/MySubObject"
}

@Issue("https://github.com/micronaut-projects/micronaut-openapi/issues/947")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1095,9 +1095,9 @@ class MyBean {}
then:
operation
parametersSchema
parametersSchema.properties.stampAlign.default == 'RIGHT'
parametersSchema.properties.stampAlign.allOf
parametersSchema.properties.stampAlign.allOf[0].$ref == '#/components/schemas/ParagraphAlignment'
parametersSchema.properties.stampAlign.allOf[1].default == 'RIGHT'
}

void "test enum in map key"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class MyBean {}
Schema woopsieRef = testImpl1.properties."woopsie-id"

woopsieRef.description == "woopsie doopsie"
woopsieRef.allOf.size() == 2
woopsieRef.allOf.size() == 1
woopsieRef.allOf[0].$ref == "#/components/schemas/TestInterface"
Schema woopsie = schemas.TestInterface
woopsie
Expand Down
Loading

0 comments on commit c74dd8f

Please sign in to comment.