diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java index eec62138f4..a20bac762f 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java @@ -414,8 +414,29 @@ private Optional parseJsonString(Object object) { return Optional.empty(); } + private void processAnnotationValue(VisitorContext context, AnnotationValue annotationValue, Map arraySchemaMap, List filters, Class type) { + Map values = annotationValue.getValues().entrySet().stream() + .filter(entry -> filters == null || ! filters.contains(entry.getKey())) + .collect(toMap( + e -> e.getKey().equals("requiredProperties") ? "required" : e.getKey(), Map.Entry::getValue)); + JsonNode schemaJson = toJson(values, context); + try { + T schema = treeToValue(schemaJson, type); + if (schema != null) { + schemaToValueMap(arraySchemaMap, schema); + } + } catch (JsonProcessingException e) { + context.warn("Error reading Swagger Schema: " + e.getMessage(), null); + } + } + private Map resolveArraySchemaAnnotationValues(VisitorContext context, AnnotationValue av) { final Map arraySchemaMap = new HashMap<>(10); + // properties + av.get("arraySchema", AnnotationValue.class).ifPresent(annotationValue -> { + processAnnotationValue(context, (AnnotationValue) annotationValue, arraySchemaMap, Arrays.asList("ref", "implementation"), Schema.class); + }); + // items av.get("schema", AnnotationValue.class).ifPresent(annotationValue -> { Optional impl = ((AnnotationValue) annotationValue).get("implementation", String.class); Optional type = ((AnnotationValue) annotationValue).get("type", String.class); @@ -446,9 +467,11 @@ private Map resolveArraySchemaAnnotationValues(VisitorCont schemaToValueMap(arraySchemaMap, schema); } } else { - arraySchemaMap.putAll(resolveAnnotationValues(context, av)); + arraySchemaMap.putAll(resolveAnnotationValues(context, annotationValue)); } }); + // other properties (minItems,...) + processAnnotationValue(context, av, arraySchemaMap, Arrays.asList("schema", "arraySchema"), ArraySchema.class); return arraySchemaMap; } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy index bc0a7a97a9..3a39409d7f 100755 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiArraySchemaSpec.groovy @@ -16,6 +16,13 @@ package io.micronaut.openapi.visitor import io.micronaut.annotation.processing.test.AbstractTypeElementSpec +import io.micronaut.http.MediaType +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.enums.ParameterIn +import io.swagger.v3.oas.annotations.media.ArraySchema +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.Operation @@ -24,7 +31,7 @@ class OpenApiArraySchemaSpec extends AbstractTypeElementSpec { System.setProperty(AbstractOpenApiVisitor.ATTR_TEST_MODE, "true") } - void "test ArraySchema with arraySchema field"() { + void "test ArraySchema with arraySchema field in class"() { given: buildBeanDefinition('test.MyBean', ''' package test; @@ -98,4 +105,107 @@ class MyBean {} openAPI.components.schemas['Pets'].properties['primitiveIds'].items.description == 'Yes' openAPI.components.schemas['Pets'].properties['primitiveIds'].items.nullable == true } + + void "test ArraySchema with arraySchema field in Controller ApiResponse"() { + given: + buildBeanDefinition('test.MyBean', ''' +package test; + +import io.swagger.v3.oas.annotations.*; +import io.swagger.v3.oas.annotations.parameters.*; +import io.swagger.v3.oas.annotations.responses.*; +import io.swagger.v3.oas.annotations.security.*; +import io.swagger.v3.oas.annotations.media.*; +import io.swagger.v3.oas.annotations.enums.*; +import io.swagger.v3.oas.annotations.links.*; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.*; +import java.util.List; + +@Controller("/") +class MyController { + + @Get("/") + @Operation(description = "Lists the Pets.") + @ApiResponse(responseCode = "200", description = "Returns a list of _Pet_s.", content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(minItems = 2, arraySchema = @Schema(description = "A list of Pets", example = "[{'name': 'cat'}, {'name': 'dog'}]"), schema = @Schema(implementation = Pet.class)))) + public List findPets() { + return null; + } +} + +@Schema(description = "Pet") +class Pet { + @Schema(description = "The name of the pet") + public String name; +} + +@javax.inject.Singleton +class MyBean {} +''') + + OpenAPI openAPI = AbstractOpenApiVisitor.testReference + Operation operation = openAPI.paths?.get("/")?.get + + expect: + operation + operation.responses.size() == 1 + operation.responses.'200'.content.'application/json'.schema.description == 'A list of Pets' + operation.responses.'200'.content.'application/json'.schema.minItems == 2 + operation.responses.'200'.content.'application/json'.schema.items.$ref == '#/components/schemas/Pet' + + } + + void "test ArraySchema with arraySchema field in Controller Parameter"() { + given: + buildBeanDefinition('test.MyBean', ''' +package test; + +import io.swagger.v3.oas.annotations.*; +import io.swagger.v3.oas.annotations.parameters.*; +import io.swagger.v3.oas.annotations.responses.*; +import io.swagger.v3.oas.annotations.security.*; +import io.swagger.v3.oas.annotations.media.*; +import io.swagger.v3.oas.annotations.enums.*; +import io.swagger.v3.oas.annotations.links.*; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.*; +import java.util.List; + +@Controller("/") +class MyController { + + @Get("/{?names*}") + @Operation(description = "Lists the Pets.") + @ApiResponse(responseCode = "200", description = "Returns a list of _Pet_s.", content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(minItems = 2, arraySchema = @Schema(description = "A list of Pets", example = "[{'name': 'cat'}, {'name': 'dog'}]"), schema = @Schema(implementation = Pet.class)))) + public List findPets(@Parameter(in = ParameterIn.QUERY, required = true, description = "A list of names", example = "['dog', 'cat']", array = @ArraySchema(minItems = 2, arraySchema = @Schema(description = "A list of _Pet_'s name"), schema = @Schema(type = "string"))) List names) { + return null; + } +} + +@Schema(description = "Pet") +class Pet { + @Schema(description = "The name of the pet") + public String name; +} + +@javax.inject.Singleton +class MyBean {} +''') + + OpenAPI openAPI = AbstractOpenApiVisitor.testReference + Operation operation = openAPI.paths?.get("/")?.get + + expect: + operation + operation.responses.size() == 1 + operation.responses.'200'.content.'application/json'.schema.description == 'A list of Pets' + operation.responses.'200'.content.'application/json'.schema.minItems == 2 + operation.responses.'200'.content.'application/json'.schema.items.$ref == '#/components/schemas/Pet' + + operation.parameters + operation.parameters.size() == 1 + operation.parameters[0].schema.description == 'A list of _Pet_\'s name' + operation.parameters[0].schema.minItems == 2 + operation.parameters[0].schema.items.type == 'string' + } } \ No newline at end of file