diff --git a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java index 5b4ddba90..f01b66e79 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/scanner/dataobject/TypeResolver.java @@ -375,6 +375,17 @@ private boolean isExposedByDefault() { * @return resolved type (if found) */ private Type getResolvedType(Type fieldType) { + return getResolvedType(fieldType, resolutionStack); + } + + /** + * Resolve a type against the given resolution stack + * + * @param fieldType type to resolve + * @param resolutionStack stack of types for resolution + * @return resolved type (if found) + */ + private static Type getResolvedType(Type fieldType, Deque> resolutionStack) { Type current = TypeUtil.resolveWildcard(fieldType); for (Map map : resolutionStack) { @@ -571,7 +582,7 @@ private static List methods(AnnotationScannerContext context, ClassI if (context.getConfig().sortedPropertiesEnable()) { return currentClass.methods(); } - return currentClass.unsortedMethods(); + return currentClass.methodsInDeclarationOrder(); } private static boolean acceptMethod(MethodInfo method) { @@ -785,7 +796,7 @@ private static void scanField(AnnotationScannerContext context, Map descendants) { final String propertyName = field.name(); - final Type fieldType = field.type(); + final Type fieldType = getResolvedType(field.type(), stack); final ClassInfo fieldClass = context.getAugmentedIndex().getClass(fieldType); final boolean unwrapped; final TypeResolver resolver; @@ -878,7 +889,7 @@ private static void scanMethod(AnnotationScannerContext context, Map> stack, AnnotationTarget reference, List descendants) { - Type returnType = method.returnType(); + Type returnType = getResolvedType(method.returnType(), stack); Type propertyType = null; if (isAccessor(context, method)) { diff --git a/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java b/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java index 08f193b3a..0a3a564a2 100644 --- a/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java +++ b/core/src/test/java/io/smallrye/openapi/runtime/scanner/StandaloneSchemaScanTest.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Target; +import java.time.Instant; import java.time.LocalTime; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -331,43 +332,73 @@ void testJakartaJaxbElementUnwrapped() throws IOException, JSONException { @Test void testJacksonJsonUnwrapped() throws IOException, JSONException { assertJsonEquals("components.schemas-jackson-jsonunwrapped.json", - JacksonJsonPerson.class, JacksonJsonPersonWithPrefixedAddress.class, - JacksonJsonPersonWithSuffixedAddress.class, JacksonJsonAddress.class); + JacksonJsonUnwrapped.JacksonJsonPerson.class, + JacksonJsonUnwrapped.JacksonJsonPersonWithPrefixedAddress.class, + JacksonJsonUnwrapped.JacksonJsonPersonWithSuffixedAddress.class, + JacksonJsonUnwrapped.JacksonJsonAddress.class, + JacksonJsonUnwrapped.TimestampedEntity.class, + JacksonJsonUnwrapped.Alternative.class, + JacksonJsonUnwrapped.Greeting.class, + JacksonJsonUnwrapped.LanguageAlternatives.class); } - @Schema - static class JacksonJsonPerson { - protected String name; - @JsonUnwrapped - protected JacksonJsonAddress address; + static class JacksonJsonUnwrapped { + @Schema + static class JacksonJsonPerson { + protected String name; + @JsonUnwrapped + protected JacksonJsonAddress address; + protected TimestampedEntity> greeting; - @Schema(description = "Ignored since address is unwrapped") - public JacksonJsonAddress getAddress() { - return address; + @Schema(description = "Ignored since address is unwrapped") + public JacksonJsonAddress getAddress() { + return address; + } } - } - @Schema - static class JacksonJsonPersonWithPrefixedAddress { - protected String name; - @JsonUnwrapped(prefix = "addr-") - protected JacksonJsonAddress address; - } + @Schema + static class JacksonJsonPersonWithPrefixedAddress { + protected String name; + @JsonUnwrapped(prefix = "addr-") + protected JacksonJsonAddress address; + } - @Schema - static class JacksonJsonPersonWithSuffixedAddress { - protected String name; - @JsonUnwrapped(suffix = "-addr") - protected JacksonJsonAddress address; - } + @Schema + static class JacksonJsonPersonWithSuffixedAddress { + protected String name; + @JsonUnwrapped(suffix = "-addr") + protected JacksonJsonAddress address; + } - @Schema - static class JacksonJsonAddress { - protected int streetNumber; - protected String streetName; - protected String city; - protected String state; - protected String postalCode; + @Schema + static class JacksonJsonAddress { + protected int streetNumber; + protected String streetName; + protected String city; + protected String state; + protected String postalCode; + } + + static class TimestampedEntity { + @JsonUnwrapped + T entity; + Instant timestamp; + } + + interface Alternative { + } + + static class Greeting { + String message; + T alternatives; + } + + static class LanguageAlternatives implements Alternative { + @Schema(required = true, example = "Hola") + String spanish; + @Schema(required = true, example = "Hallo") + String german; + } } /****************************************************************/ diff --git a/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas-jackson-jsonunwrapped.json b/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas-jackson-jsonunwrapped.json index 37c0bc95e..e99d1745f 100644 --- a/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas-jackson-jsonunwrapped.json +++ b/core/src/test/resources/io/smallrye/openapi/runtime/scanner/components.schemas-jackson-jsonunwrapped.json @@ -9,8 +9,8 @@ "type": "string" }, "streetNumber": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "city": { "type": "string" @@ -23,6 +23,9 @@ }, "name": { "type": "string" + }, + "greeting": { + "$ref": "#/components/schemas/TimestampedEntityGreetingLanguageAlternatives" } } }, @@ -42,8 +45,8 @@ "type": "string" }, "addr-streetNumber": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "name": { "type": "string" @@ -63,8 +66,8 @@ "type": "string" }, "streetNumber-addr": { - "format": "int32", - "type": "integer" + "type": "integer", + "format": "int32" }, "state-addr": { "type": "string" @@ -77,21 +80,60 @@ "JacksonJsonAddress": { "type": "object", "properties": { - "city": { + "streetNumber": { + "type": "integer", + "format": "int32" + }, + "streetName": { "type": "string" }, - "postalCode": { + "city": { "type": "string" }, "state": { "type": "string" }, - "streetName": { + "postalCode": { + "type": "string" + } + } + }, + "LanguageAlternatives": { + "type": "object", + "required": [ + "spanish", + "german" + ], + "properties": { + "spanish": { + "type": "string", + "examples": [ + "Hola" + ] + }, + "german": { + "type": "string", + "examples": [ + "Hallo" + ] + } + } + }, + "TimestampedEntityGreetingLanguageAlternatives": { + "type": "object", + "properties": { + "alternatives": { + "$ref": "#/components/schemas/LanguageAlternatives" + }, + "message": { "type": "string" }, - "streetNumber": { - "format": "int32", - "type": "integer" + "timestamp": { + "type": "string", + "format": "date-time", + "examples": [ + "2022-03-10T16:15:50Z" + ] } } }