From 33b9b9524928a5d5a651d5d83008af205e088cfa Mon Sep 17 00:00:00 2001 From: frantuma Date: Sun, 17 Sep 2023 21:04:18 +0200 Subject: [PATCH] refs #3080 - support array in additionalProperties annotation member --- .../v3/oas/annotations/media/Content.java | 14 ++- .../v3/core/util/AnnotationsUtils.java | 18 +++- .../java/io/swagger/v3/jaxrs2/ReaderTest.java | 70 +++++++++++++ .../SchemaAdditionalPropertiesResource.java | 98 +++++++++++++++++++ 4 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/SchemaAdditionalPropertiesResource.java diff --git a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Content.java b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Content.java index da4032e8ee..f4532f9c4a 100644 --- a/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Content.java +++ b/modules/swagger-annotations/src/main/java/io/swagger/v3/oas/annotations/media/Content.java @@ -56,13 +56,23 @@ SchemaProperty[] schemaProperties() default {}; /** - * The schema properties defined for schema provided in @Schema + * The additionalProperties schema defined for schema provided in @Schema + * If the additionalProperties schema is an array, use additionalPropertiesArraySchema * * @since 2.2.0 - * @return the schema properties + * @return the additionalProperties schema */ Schema additionalPropertiesSchema() default @Schema(); + /** + * The additionalProperties array schema defined for schema provided in @Schema + * If the additionalProperties schema is not an array, use additionalPropertiesSchema + * + * @since 2.2.16 + * @return the additionalProperties array schema + */ + ArraySchema additionalPropertiesArraySchema() default @ArraySchema(); + /** * The schema of the array that defines the type used for the content. * diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java index 679a07c864..9e9af0ab83 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java @@ -489,6 +489,9 @@ public static Optional getArraySchema(io.swagger.v3.oas.annotations return Optional.empty(); } public static Optional getArraySchema(io.swagger.v3.oas.annotations.media.ArraySchema arraySchema, Components components, JsonView jsonViewAnnotation, boolean openapi31, Schema existingSchema) { + return getArraySchema(arraySchema, components, jsonViewAnnotation, openapi31, existingSchema, false); + } + public static Optional getArraySchema(io.swagger.v3.oas.annotations.media.ArraySchema arraySchema, Components components, JsonView jsonViewAnnotation, boolean openapi31, Schema existingSchema, boolean processSchemaImplementation) { if (arraySchema == null || !hasArrayAnnotation(arraySchema)) { if (existingSchema == null) { return Optional.empty(); @@ -546,7 +549,9 @@ public static Optional getArraySchema(io.swagger.v3.oas.annotations.medi if (arraySchema.schema() != null) { if (arraySchema.schema().implementation().equals(Void.class)) { getSchemaFromAnnotation(arraySchema.schema(), components, jsonViewAnnotation, openapi31).ifPresent(arraySchemaObject::setItems); - } // if present, schema implementation handled upstream + } else if (processSchemaImplementation) { + getSchema(arraySchema.schema(), arraySchema, false, arraySchema.schema().implementation(), components, jsonViewAnnotation, openapi31).ifPresent(arraySchemaObject::setItems); + } } return Optional.of(arraySchemaObject); @@ -1500,7 +1505,15 @@ public static Optional getContent(io.swagger.v3.oas.annotations.media.C } } - if ( + Optional arraySchemaResult = getArraySchema(annotationContent.additionalPropertiesArraySchema(), components, jsonViewAnnotation, openapi31, null, true); + if (arraySchemaResult.isPresent()) { + if ("array".equals(mediaType.getSchema().getType())) { + mediaType.getSchema().getItems().additionalProperties(arraySchemaResult.get()); + } else { + mediaType.getSchema().additionalProperties(arraySchemaResult.get()); + } + } else { + if ( hasSchemaAnnotation(annotationContent.additionalPropertiesSchema()) && mediaType.getSchema() != null && !Boolean.TRUE.equals(mediaType.getSchema().getAdditionalProperties()) && @@ -1514,6 +1527,7 @@ public static Optional getContent(io.swagger.v3.oas.annotations.media.C } } ); + } } } else { mediaType.setSchema(schema); diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java index 7cd24d81c6..e136021158 100644 --- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java @@ -18,6 +18,7 @@ import io.swagger.v3.jaxrs2.resources.Misc31Resource; import io.swagger.v3.jaxrs2.resources.ParameterMaximumValueResource; import io.swagger.v3.jaxrs2.resources.ResponseReturnTypeResource; +import io.swagger.v3.jaxrs2.resources.SchemaAdditionalPropertiesResource; import io.swagger.v3.jaxrs2.resources.SchemaPropertiesResource; import io.swagger.v3.jaxrs2.resources.SiblingPropResource; import io.swagger.v3.jaxrs2.resources.SiblingsResource; @@ -3011,6 +3012,75 @@ public void testSchemaProperties() { SerializationMatchers.assertEqualsToYaml(openAPI, yaml); } + @Test(description = "Test Schema AdditionalProperties annotations") + public void testSchemaAdditionalProperties() { + Reader reader = new Reader(new OpenAPI()); + + OpenAPI openAPI = reader.read(SchemaAdditionalPropertiesResource.class); + String yaml = "openapi: 3.0.1\n" + + "paths:\n" + + " /arraySchemaImpl:\n" + + " get:\n" + + " operationId: arraySchemaImpl\n" + + " responses:\n" + + " \"200\":\n" + + " description: voila!\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " type: object\n" + + " additionalProperties:\n" + + " type: array\n" + + " items:\n" + + " $ref: '#/components/schemas/Pet'\n" + + " /fromtResponseType:\n" + + " get:\n" + + " operationId: fromtResponseType\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " type: object\n" + + " additionalProperties:\n" + + " type: array\n" + + " items:\n" + + " $ref: '#/components/schemas/Pet'\n" + + " /schemaImpl:\n" + + " get:\n" + + " operationId: schemaImpl\n" + + " responses:\n" + + " \"200\":\n" + + " description: voila!\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " type: object\n" + + " additionalProperties:\n" + + " $ref: '#/components/schemas/Pet'\n" + + " /schemaNotImpl:\n" + + " get:\n" + + " operationId: schemaNotImpl\n" + + " responses:\n" + + " \"200\":\n" + + " description: voila!\n" + + " content:\n" + + " application/json:\n" + + " schema:\n" + + " type: object\n" + + " additionalProperties:\n" + + " $ref: '#/components/schemas/Pet'\n" + + "components:\n" + + " schemas:\n" + + " Pet:\n" + + " type: object\n" + + " properties:\n" + + " foo:\n" + + " type: string\n"; + SerializationMatchers.assertEqualsToYaml(openAPI, yaml); + } + @Test(description = "Responses schema resolved from return type") public void testResponseReturnType() { Reader reader = new Reader(new OpenAPI()); diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/SchemaAdditionalPropertiesResource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/SchemaAdditionalPropertiesResource.java new file mode 100644 index 0000000000..a2c22b53b8 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/resources/SchemaAdditionalPropertiesResource.java @@ -0,0 +1,98 @@ +package io.swagger.v3.jaxrs2.resources; + +import io.swagger.v3.oas.annotations.Operation; +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 javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; +import java.util.List; +import java.util.Map; + +public class SchemaAdditionalPropertiesResource { + + static class Pet { + public String foo; + } + @GET + @Path("/fromtResponseType") + public Map> fromtResponseType() { + return null; + } + + @GET + @Path("/schemaNotImpl") + @Operation( + operationId = "schemaNotImpl", + responses = { + @ApiResponse( + responseCode = "200", + description = "voila!", + content = @Content( + mediaType = "application/json", + schema = @Schema( + type = "object" + ), + additionalPropertiesSchema = @Schema( + ref = "#/components/schemas/Pet" + ) + ) + ) + } + ) + public Response schemaNotImpl() { + return null; + } + + @GET + @Path("/schemaImpl") + @Operation( + operationId = "schemaImpl", + responses = { + @ApiResponse( + responseCode = "200", + description = "voila!", + content = @Content( + mediaType = "application/json", + schema = @Schema( + type = "object", + additionalPropertiesSchema = Pet.class + ) + ) + ) + } + ) + public Response schemaImpl() { + return null; + } + + @GET + @Path("/arraySchemaImpl") + @Operation( + operationId = "arraySchemaImpl", + responses = { + @ApiResponse( + responseCode = "200", + description = "voila!", + content = @Content( + mediaType = "application/json", + additionalPropertiesArraySchema = @ArraySchema( + schema = @Schema( + implementation = Pet.class + ) + ), + schema = @Schema( + type = "object" + ) + ) + ) + } + ) + public Response arraySchemaImpl() { + return null; + } + +}