diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java index d0b27ad81..bac371559 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocSealedClassModule.java @@ -26,10 +26,12 @@ import java.util.Arrays; import java.util.List; +import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.module.SimpleModule; import io.swagger.v3.core.jackson.SwaggerAnnotationIntrospector; +import io.swagger.v3.oas.annotations.media.Schema; /** * The type Spring doc sealed class module. @@ -56,6 +58,18 @@ public List findSubtypes(Annotated annotated) { && clazz.isSealed() && !clazz.getPackage().getName().startsWith("java") ) { + + Schema schema = clazz.getAnnotation(Schema.class); + if (schema != null && schema.oneOf().length > 0) { + return new ArrayList<>(); + } + + JsonSubTypes jsonSubTypes = clazz.getAnnotation(JsonSubTypes.class); + if (jsonSubTypes != null && jsonSubTypes.value().length > 0) { + return new ArrayList<>(); + } + + Class[] permittedSubClasses = clazz.getPermittedSubclasses(); if (permittedSubClasses.length > 0) { Arrays.stream(permittedSubClasses).map(NamedType::new).forEach(subTypes::add); diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app243/HelloController.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app243/HelloController.java new file mode 100644 index 000000000..021fdf4ee --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app243/HelloController.java @@ -0,0 +1,93 @@ +/* + * + * * + * * * + * * * * + * * * * * + * * * * * * Copyright 2019-2025 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.app243; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; +import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @PostMapping("/parent") + public void parentEndpoint(@RequestBody SuperClass parent) { + + } + +} + +@Schema(name = SuperClass.SCHEMA_NAME, + discriminatorProperty = "type", + oneOf = { + FirstChildClass.class, + SecondChildClass.class + }, + discriminatorMapping = { + @DiscriminatorMapping(value = FirstChildClass.SCHEMA_NAME, schema = FirstChildClass.class), + @DiscriminatorMapping(value = SecondChildClass.SCHEMA_NAME, schema = SecondChildClass.class) + } +) +sealed class SuperClass { + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + public String getType() { + return type; + } + + public String type; + + public static final String SCHEMA_NAME = "SuperClass"; +} + +@Schema(name = FirstChildClass.SCHEMA_NAME) +final class FirstChildClass extends SuperClass { + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + public String getType() { + return type; + } + + public String type; + + public static final String SCHEMA_NAME = "Image"; +} + +@Schema(name = SecondChildClass.SCHEMA_NAME) +final class SecondChildClass extends SuperClass { + + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + public String getType() { + return type; + } + + public String type; + + public static final String SCHEMA_NAME = "Mail"; +} diff --git a/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app243/SpringDocApp243Test.java b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app243/SpringDocApp243Test.java new file mode 100644 index 000000000..f6ceacf4d --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/java/test/org/springdoc/api/v30/app243/SpringDocApp243Test.java @@ -0,0 +1,34 @@ +/* + * + * * + * * * + * * * * + * * * * * Copyright 2025 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.app243; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import test.org.springdoc.api.v30.AbstractSpringDocV30Test; + +public class SpringDocApp243Test extends AbstractSpringDocV30Test { + + @SpringBootApplication + static class SpringDocTestApp {} +} \ No newline at end of file diff --git a/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app243.json b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app243.json new file mode 100644 index 000000000..edb12ee9d --- /dev/null +++ b/springdoc-openapi-starter-webmvc-api/src/test/resources/results/3.0.1/app243.json @@ -0,0 +1,90 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "paths": { + "/parent": { + "post": { + "tags": [ + "hello-controller" + ], + "operationId": "parentEndpoint", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuperClass" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": { + "schemas": { + "Image": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "Mail": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "SuperClass": { + "required": [ + "type" + ], + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "discriminator": { + "propertyName": "type", + "mapping": { + "Image": "#/components/schemas/Image", + "Mail": "#/components/schemas/Mail" + } + }, + "oneOf": [ + { + "$ref": "#/components/schemas/Image" + }, + { + "$ref": "#/components/schemas/Mail" + } + ] + } + } + } +} \ No newline at end of file