From fe9ba652c6a0c0ec0a38158fedf17cf95d1e77e1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Oct 2023 10:29:23 +0900 Subject: [PATCH 1/2] fix arraySchema in SchemaProperty. fixes #2373 --- .../core/utils/SpringDocAnnotationsUtils.java | 8 +- .../api/v30/app211/HelloController.java | 61 ++++++++++++++ .../api/v30/app211/SpringDocApp211Test.java | 36 +++++++++ .../test/resources/results/3.0.1/app211.json | 79 +++++++++++++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java create mode 100644 springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app211.json diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java index 5bc2882eb..7c35bfc8a 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java @@ -365,7 +365,13 @@ private static MediaType getMediaType(Schema schema, Components components, Json Schema oSchema = mediaType.getSchema(); for (SchemaProperty sp : annotationContent.schemaProperties()) { Class schemaImplementation = sp.schema().implementation(); - boolean isArray = isArray(annotationContent); + boolean isArray = false; + if (schemaImplementation == Void.class) { + schemaImplementation = sp.array().schema().implementation(); + if (schemaImplementation != Void.class) { + isArray = true; + } + } getSchema(sp.schema(), sp.array(), isArray, schemaImplementation, components, jsonViewAnnotation, openapi31) .ifPresent(s -> { if ("array".equals(oSchema.getType())) { diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java new file mode 100644 index 000000000..7bb8fb903 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/HelloController.java @@ -0,0 +1,61 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2023 the original author or authors. + * * * * * + * * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * * you may not use this file except in compliance with the License. + * * * * * You may obtain a copy of the License at + * * * * * + * * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * * + * * * * * Unless required by applicable law or agreed to in writing, software + * * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * * See the License for the specific language governing permissions and + * * * * * limitations under the License. + * * * * + * * * + * * + * + */ + +package test.org.springdoc.api.v30.app211; + +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.media.SchemaProperty; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + @ApiResponses(value = @ApiResponse( + responseCode = "200", + content = { + @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schemaProperties = { + @SchemaProperty( + name = "items", + array = @ArraySchema(schema = @Schema(implementation = PagedObject.class))), + @SchemaProperty(name = "paging", schema = @Schema(implementation = Paging.class)) + } + ) + } + )) + @GetMapping + public String index() { + return null; + } + + record PagedObject(long id, String name) {} + record Paging(int page, int total, int lastPage) {} +} diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java new file mode 100644 index 000000000..31b1b5ef7 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app211/SpringDocApp211Test.java @@ -0,0 +1,36 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2019-2023 the original author or authors. + * * * * * + * * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * * you may not use this file except in compliance with the License. + * * * * * You may obtain a copy of the License at + * * * * * + * * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * * + * * * * * Unless required by applicable law or agreed to in writing, software + * * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * * See the License for the specific language governing permissions and + * * * * * limitations under the License. + * * * * + * * * + * * + * + */ + +package test.org.springdoc.api.v30.app211; + +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +public class SpringDocApp211Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app211.json b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app211.json new file mode 100644 index 000000000..f2b9b4346 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app211.json @@ -0,0 +1,79 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/": { + "get": { + "tags": [ + "hello-controller" + ], + "operationId": "index_1", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PagedObject" + } + }, + "paging": { + "$ref": "#/components/schemas/Paging" + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PagedObject": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "Paging": { + "type": "object", + "properties": { + "page": { + "type": "integer", + "format": "int32" + }, + "total": { + "type": "integer", + "format": "int32" + }, + "lastPage": { + "type": "integer", + "format": "int32" + } + } + } + } + } +} From dea674b055d07422a02678cd223815391b82bb58 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 14 Oct 2023 10:36:05 +0900 Subject: [PATCH 2/2] Refactor to shallower nesting --- .../core/utils/SpringDocAnnotationsUtils.java | 103 +++++++++--------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java index 7c35bfc8a..c532a679a 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java @@ -354,64 +354,63 @@ private static void setExamples(MediaType mediaType, ExampleObject[] examples) { private static MediaType getMediaType(Schema schema, Components components, JsonView jsonViewAnnotation, io.swagger.v3.oas.annotations.media.Content annotationContent, boolean openapi31) { MediaType mediaType = new MediaType(); - if (!annotationContent.schema().hidden()) { - if (components != null) { - try { - getSchema(annotationContent, components, jsonViewAnnotation, openapi31).ifPresent(mediaType::setSchema); - if (annotationContent.schemaProperties().length > 0) { - if (mediaType.getSchema() == null) { - mediaType.schema(new Schema().type("object")); - } - Schema oSchema = mediaType.getSchema(); - for (SchemaProperty sp : annotationContent.schemaProperties()) { - Class schemaImplementation = sp.schema().implementation(); - boolean isArray = false; - if (schemaImplementation == Void.class) { - schemaImplementation = sp.array().schema().implementation(); - if (schemaImplementation != Void.class) { - isArray = true; - } - } - getSchema(sp.schema(), sp.array(), isArray, schemaImplementation, components, jsonViewAnnotation, openapi31) - .ifPresent(s -> { - if ("array".equals(oSchema.getType())) { - oSchema.getItems().addProperty(sp.name(), s); - } - else { - oSchema.addProperty(sp.name(), s); - } - }); - + if (annotationContent.schema().hidden()) { + return mediaType; + } + if (components == null) { + mediaType.setSchema(schema); + return mediaType; + } + try { + getSchema(annotationContent, components, jsonViewAnnotation, openapi31).ifPresent(mediaType::setSchema); + if (annotationContent.schemaProperties().length > 0) { + if (mediaType.getSchema() == null) { + mediaType.schema(new Schema().type("object")); + } + Schema oSchema = mediaType.getSchema(); + for (SchemaProperty sp : annotationContent.schemaProperties()) { + Class schemaImplementation = sp.schema().implementation(); + boolean isArray = false; + if (schemaImplementation == Void.class) { + schemaImplementation = sp.array().schema().implementation(); + if (schemaImplementation != Void.class) { + isArray = true; } } - if ( - hasSchemaAnnotation(annotationContent.additionalPropertiesSchema()) && - mediaType.getSchema() != null && - !Boolean.TRUE.equals(mediaType.getSchema().getAdditionalProperties()) && - !Boolean.FALSE.equals(mediaType.getSchema().getAdditionalProperties())) { - getSchemaFromAnnotation(annotationContent.additionalPropertiesSchema(), components, jsonViewAnnotation, openapi31) - .ifPresent(s -> { - if ("array".equals(mediaType.getSchema().getType())) { - mediaType.getSchema().getItems().additionalProperties(s); - } - else { - mediaType.getSchema().additionalProperties(s); - } - } - ); - } - } - catch (Exception e) { - if (isArray(annotationContent)) - mediaType.setSchema(new ArraySchema().items(new StringSchema())); - else - mediaType.setSchema(new StringSchema()); + getSchema(sp.schema(), sp.array(), isArray, schemaImplementation, components, jsonViewAnnotation, openapi31) + .ifPresent(s -> { + if ("array".equals(oSchema.getType())) { + oSchema.getItems().addProperty(sp.name(), s); + } + else { + oSchema.addProperty(sp.name(), s); + } + }); } } - else { - mediaType.setSchema(schema); + if ( + hasSchemaAnnotation(annotationContent.additionalPropertiesSchema()) && + mediaType.getSchema() != null && + !Boolean.TRUE.equals(mediaType.getSchema().getAdditionalProperties()) && + !Boolean.FALSE.equals(mediaType.getSchema().getAdditionalProperties())) { + getSchemaFromAnnotation(annotationContent.additionalPropertiesSchema(), components, jsonViewAnnotation, openapi31) + .ifPresent(s -> { + if ("array".equals(mediaType.getSchema().getType())) { + mediaType.getSchema().getItems().additionalProperties(s); + } + else { + mediaType.getSchema().additionalProperties(s); + } + } + ); } } + catch (Exception e) { + if (isArray(annotationContent)) + mediaType.setSchema(new ArraySchema().items(new StringSchema())); + else + mediaType.setSchema(new StringSchema()); + } return mediaType; }