diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java index d91a53b335..522aa86f87 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiModelProp.java @@ -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"; } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java index 32216eddfa..e6b2a54934 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiNormalizeUtils.java @@ -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); @@ -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); diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java index a2bb4f46b8..c4881a0f6a 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java @@ -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; @@ -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; @@ -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; @@ -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); @@ -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()); } @@ -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()) { @@ -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())) { @@ -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; } @@ -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); @@ -1731,37 +1739,59 @@ private static void checkAllOf(Schema 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 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 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 schemaAnn) { + @Nullable AnnotationValue schemaAnn) { + if (schemaAnn == null) { + return; + } Map annValues = schemaAnn.getValues(); if (annValues.containsKey(PROP_NAME)) { schemaToBind.setName((String) annValues.get(PROP_NAME)); @@ -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 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 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(), elType); + } + 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); diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy index 4d4d618d3b..8baae9c20b 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy @@ -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") diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy index be1920544a..1123ee4493 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy @@ -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"() { diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecursionSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecursionSpec.groovy index 7ddd379a0f..9a60a7f8cd 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecursionSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiRecursionSpec.groovy @@ -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 diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy index 14426cb52d..e20b91c0dc 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy @@ -121,7 +121,7 @@ class MyBean {} parametersSchema instanceof Schema when: - Operation operation = openAPI.paths.get("/path").post + Operation operation = openAPI.paths."/path".post then: operation @@ -143,27 +143,27 @@ class MyBean {} dtoSchema.properties.test.example.stampWidth == 220 dtoSchema.properties.test.example.stampHeight == 85 dtoSchema.properties.test.example.pageNumber == 1 + dtoSchema.properties.test.default + dtoSchema.properties.test.default.stampWidth == 100 dtoSchema.properties.test.allOf dtoSchema.properties.test.allOf.size() == 2 - dtoSchema.properties.test.allOf.get(0).$ref == "#/components/schemas/Parameters" - dtoSchema.properties.test.allOf.get(1).default - dtoSchema.properties.test.allOf.get(1).default.stampWidth == 100 - dtoSchema.properties.test.allOf.get(1).format == 'binary' - dtoSchema.properties.test.allOf.get(1).exclusiveMinimum - dtoSchema.properties.test.allOf.get(1).exclusiveMaximum - dtoSchema.properties.test.allOf.get(1).maximum == 100 - dtoSchema.properties.test.allOf.get(1).minimum == 10 - dtoSchema.properties.test.allOf.get(1).maximum == 100 - dtoSchema.properties.test.allOf.get(1).minLength == 10 - dtoSchema.properties.test.allOf.get(1).maxLength == 100 - dtoSchema.properties.test.allOf.get(1).minProperties == 10 - dtoSchema.properties.test.allOf.get(1).maxProperties == 100 - dtoSchema.properties.test.allOf.get(1).multipleOf == 1.5 - dtoSchema.properties.test.allOf.get(1).pattern == "ppp" - dtoSchema.properties.test.allOf.get(1).additionalProperties == true + dtoSchema.properties.test.allOf[0].$ref == "#/components/schemas/Parameters" + dtoSchema.properties.test.allOf[1].format == 'binary' + dtoSchema.properties.test.allOf[1].exclusiveMinimum + dtoSchema.properties.test.allOf[1].exclusiveMaximum + dtoSchema.properties.test.allOf[1].maximum == 100 + dtoSchema.properties.test.allOf[1].minimum == 10 + dtoSchema.properties.test.allOf[1].maximum == 100 + dtoSchema.properties.test.allOf[1].minLength == 10 + dtoSchema.properties.test.allOf[1].maxLength == 100 + dtoSchema.properties.test.allOf[1].minProperties == 10 + dtoSchema.properties.test.allOf[1].maxProperties == 100 + dtoSchema.properties.test.allOf[1].multipleOf == 1.5 + dtoSchema.properties.test.allOf[1].pattern == "ppp" + dtoSchema.properties.test.allOf[1].additionalProperties == true dtoSchema.properties.test.nullable dtoSchema.required.size() == 1 - dtoSchema.required.get(0) == 'test' + dtoSchema.required[0] == 'test' } void "test schema example in class schema"() { @@ -271,7 +271,7 @@ class MyBean {} parametersSchema when: - Operation operation = openAPI.paths.get("/path").post + Operation operation = openAPI.paths."/path".post then: operation @@ -395,7 +395,7 @@ class MyBean {} parametersSchema instanceof Schema when: - Operation operation = openAPI.paths.get("/path").post + Operation operation = openAPI.paths."/path".post then: operation @@ -411,7 +411,7 @@ class MyBean {} dtoSchema.properties.test.description == 'this is description' dtoSchema.properties.test.allOf dtoSchema.properties.test.allOf.size() == 1 - dtoSchema.properties.test.allOf.get(0).$ref == "#/components/schemas/Parameters" + dtoSchema.properties.test.allOf[0].$ref == "#/components/schemas/Parameters" !dtoSchema.properties.test.default !dtoSchema.properties.test.example !dtoSchema.properties.test.deprecated @@ -553,7 +553,7 @@ class MyBean {} globalParamsSchema when: - Operation operation = openAPI.paths.get("/path").post + Operation operation = openAPI.paths."/path".post then: operation @@ -567,20 +567,20 @@ class MyBean {} ((Schema) operation.requestBody.content."application/json".schema).$ref == "#/components/schemas/MyDto" dtoSchema.properties.test instanceof ComposedSchema - dtoSchema.properties.test.allOf.get(0).$ref == '#/components/schemas/GlobalParams' - dtoSchema.properties.test.allOf.get(1).not - dtoSchema.properties.test.allOf.get(1).not.$ref == '#/components/schemas/LocalParams' - dtoSchema.properties.test.allOf.get(1).allOf.get(0).$ref == '#/components/schemas/LocalParams' - dtoSchema.properties.test.allOf.get(1).oneOf.get(0).$ref == '#/components/schemas/LocalParams' - dtoSchema.properties.test.allOf.get(1).anyOf.get(0).$ref == '#/components/schemas/LocalParams' + dtoSchema.properties.test.allOf[0].$ref == '#/components/schemas/GlobalParams' + dtoSchema.properties.test.allOf[1].allOf[0].$ref == '#/components/schemas/LocalParams' + dtoSchema.properties.test.not + dtoSchema.properties.test.not.$ref == '#/components/schemas/LocalParams' + dtoSchema.properties.test.oneOf[0].$ref == '#/components/schemas/LocalParams' + dtoSchema.properties.test.anyOf[0].$ref == '#/components/schemas/LocalParams' globalParamsSchema.properties.globalStampWidth.type == 'integer' globalParamsSchema.properties.globalStampWidth.format == 'int32' localParamsSchema.allOf.size() == 2 - localParamsSchema.allOf.get(0).$ref == '#/components/schemas/GlobalParams' - localParamsSchema.allOf.get(1).properties.stampWidth.type == 'integer' - localParamsSchema.allOf.get(1).properties.stampWidth.format == 'int32' + localParamsSchema.allOf[0].$ref == '#/components/schemas/GlobalParams' + localParamsSchema.allOf[1].properties.stampWidth.type == 'integer' + localParamsSchema.allOf[1].properties.stampWidth.format == 'int32' } void "test schema on property level with implementation"() { @@ -698,7 +698,7 @@ class MyBean {} globalParamsSchema when: - Operation operation = openAPI.paths.get("/path").post + Operation operation = openAPI.paths."/path".post then: operation @@ -712,13 +712,13 @@ class MyBean {} ((Schema) operation.requestBody.content."application/json".schema).$ref == "#/components/schemas/MyDto" localParamsSchema.allOf.size() == 2 - localParamsSchema.allOf.get(0).$ref == '#/components/schemas/GlobalParams' - localParamsSchema.allOf.get(1).properties.stampWidth.type == 'integer' - localParamsSchema.allOf.get(1).properties.stampWidth.format == 'int32' + localParamsSchema.allOf[0].$ref == '#/components/schemas/GlobalParams' + localParamsSchema.allOf[1].properties.stampWidth.type == 'integer' + localParamsSchema.allOf[1].properties.stampWidth.format == 'int32' dtoSchema.properties.test.description == 'this is description' dtoSchema.properties.test.allOf.size() == 1 - dtoSchema.properties.test.allOf.get(0).$ref == '#/components/schemas/LocalParams' + dtoSchema.properties.test.allOf[0].$ref == '#/components/schemas/LocalParams' } void "test schema on class level with not/allOf/anyOf/oneOf"() { @@ -839,7 +839,7 @@ class MyBean {} globalParamsSchema when: - Operation operation = openAPI.paths.get("/path").post + Operation operation = openAPI.paths."/path".post then: operation @@ -855,18 +855,18 @@ class MyBean {} dtoSchema.description == 'this is description' dtoSchema.not dtoSchema.not.$ref == '#/components/schemas/LocalParams' - dtoSchema.allOf.get(0).$ref == '#/components/schemas/LocalParams' - dtoSchema.allOf.get(1).properties.parameters.$ref == '#/components/schemas/GlobalParams' - dtoSchema.oneOf.get(0).$ref == '#/components/schemas/LocalParams' - dtoSchema.anyOf.get(0).$ref == '#/components/schemas/LocalParams' + dtoSchema.allOf[0].$ref == '#/components/schemas/LocalParams' + dtoSchema.allOf[1].properties.parameters.$ref == '#/components/schemas/GlobalParams' + dtoSchema.oneOf[0].$ref == '#/components/schemas/LocalParams' + dtoSchema.anyOf[0].$ref == '#/components/schemas/LocalParams' globalParamsSchema.properties.globalStampWidth.type == 'integer' globalParamsSchema.properties.globalStampWidth.format == 'int32' localParamsSchema.allOf.size() == 2 - localParamsSchema.allOf.get(0).$ref == '#/components/schemas/GlobalParams' - localParamsSchema.allOf.get(1).properties.stampWidth.type == 'integer' - localParamsSchema.allOf.get(1).properties.stampWidth.format == 'int32' + localParamsSchema.allOf[0].$ref == '#/components/schemas/GlobalParams' + localParamsSchema.allOf[1].properties.stampWidth.type == 'integer' + localParamsSchema.allOf[1].properties.stampWidth.format == 'int32' } void "test schema on property level with type"() { @@ -1256,4 +1256,89 @@ class MyBean {} schemas.AuditSearchCriteria.additionalProperties != null schemas.AuditSearchCriteria.additionalProperties == false } + + void "test jackson and swagger annotations together"() { + + when: + buildBeanDefinition('test.MyBean', ''' +package test; + +import com.fasterxml.jackson.annotation.JsonClassDescription;import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription;import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get;import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; + +@Controller +class HelloController { + + @Get + ResourceSearchVO sendModelWithDiscriminator() { + return null; + } +} + +@JsonClassDescription("Jackson schema description") +class ResourceSearchVO implements Serializable { + + /** + * Must be swagger description (javadoc) + */ + @Schema(description = "Must be swagger description (swagger)") + @JsonPropertyDescription("Must be swagger description (jackson)") + public String swaggerDesc; + /** + * Must be jackson description (javadoc) + */ + @JsonPropertyDescription("Must be jackson description (jackson)") + public String jacksonDesc; + /** + * Must be javadoc description (javadoc) + */ + public String javadocDesc; + + @Schema(defaultValue = "VAL1") + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY, defaultValue = "VAL2") + public ResourceEnum type; + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY, defaultValue = "VAL2") + public ResourceEnum type1; + @Schema(accessMode = Schema.AccessMode.READ_ONLY, defaultValue = "VAL2") + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY, defaultValue = "VAL1") + public ResourceEnum type2; + public ResourceEnum type3; +} + +enum ResourceEnum { + VAL1, + VAL2 +} + +@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.ResourceSearchVO + schemas.ResourceSearchVO.description == 'Jackson schema description' + schemas.ResourceSearchVO.properties.swaggerDesc.description == "Must be swagger description (swagger)" + schemas.ResourceSearchVO.properties.jacksonDesc.description == 'Must be jackson description (jackson)' + schemas.ResourceSearchVO.properties.javadocDesc.description == 'Must be javadoc description (javadoc)' + schemas.ResourceSearchVO.properties.type.allOf[0].$ref == '#/components/schemas/ResourceEnum' + schemas.ResourceSearchVO.properties.type.writeOnly == true + schemas.ResourceSearchVO.properties.type.default == "VAL1" + schemas.ResourceSearchVO.properties.type1.allOf[0].$ref == '#/components/schemas/ResourceEnum' + schemas.ResourceSearchVO.properties.type1.writeOnly == true + schemas.ResourceSearchVO.properties.type1.default == "VAL2" + schemas.ResourceSearchVO.properties.type2.allOf[0].$ref == '#/components/schemas/ResourceEnum' + schemas.ResourceSearchVO.properties.type2.readOnly == true + !schemas.ResourceSearchVO.properties.type2.writeOnly + schemas.ResourceSearchVO.properties.type2.default == "VAL2" + schemas.ResourceSearchVO.properties.type3.$ref == '#/components/schemas/ResourceEnum' + } } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy index 0dceef65d9..4f0181fda2 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaInheritanceSpec.groovy @@ -263,8 +263,8 @@ class MyBean {} Schema vehicleRef = owner.properties.vehicle vehicleRef.description == "Vehicle of the owner. Here a car or bike with a name" vehicleRef.allOf[0].$ref == "#/components/schemas/Vehicle" - vehicleRef.allOf[1].oneOf[0].$ref == '#/components/schemas/Car' - vehicleRef.allOf[1].oneOf[1].$ref == '#/components/schemas/Bike' + vehicleRef.oneOf[0].$ref == '#/components/schemas/Car' + vehicleRef.oneOf[1].$ref == '#/components/schemas/Bike' Schema vehicle = schemas["Vehicle"] vehicle.type == 'object' }