Skip to content

Commit 8f98a39

Browse files
committedFeb 16, 2025
PR review
2 parents a329910 + 3ae7763 commit 8f98a39

File tree

10 files changed

+771
-1
lines changed

10 files changed

+771
-1
lines changed
 

‎springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ SpringDocProviders springDocProviders(Optional<ActuatorProvider> actuatorProvide
424424
Optional<RepositoryRestResourceProvider> repositoryRestResourceProvider, Optional<RouterFunctionProvider> routerFunctionProvider,
425425
Optional<SpringWebProvider> springWebProvider,
426426
ObjectMapperProvider objectMapperProvider) {
427-
objectMapperProvider.jsonMapper().registerModule(new SpringDocRequiredModule());
427+
objectMapperProvider.jsonMapper().registerModules(new SpringDocRequiredModule(), new SpringDocSealedClassModule());
428428
return new SpringDocProviders(actuatorProvider, springCloudFunctionProvider, springSecurityOAuth2Provider, repositoryRestResourceProvider, routerFunctionProvider, springWebProvider, objectMapperProvider);
429429
}
430430

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * * Copyright 2025 the original author or authors.
6+
* * * *
7+
* * * * Licensed under the Apache License, Version 2.0 (the "License");
8+
* * * * you may not use this file except in compliance with the License.
9+
* * * * You may obtain a copy of the License at
10+
* * * *
11+
* * * * https://www.apache.org/licenses/LICENSE-2.0
12+
* * * *
13+
* * * * Unless required by applicable law or agreed to in writing, software
14+
* * * * distributed under the License is distributed on an "AS IS" BASIS,
15+
* * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* * * * See the License for the specific language governing permissions and
17+
* * * * limitations under the License.
18+
* * *
19+
* *
20+
*
21+
*/
22+
23+
package org.springdoc.core.configuration;
24+
25+
import com.fasterxml.jackson.databind.introspect.Annotated;
26+
import com.fasterxml.jackson.databind.jsontype.NamedType;
27+
import com.fasterxml.jackson.databind.module.SimpleModule;
28+
import io.swagger.v3.core.jackson.SwaggerAnnotationIntrospector;
29+
import java.util.ArrayList;
30+
import java.util.Arrays;
31+
import java.util.List;
32+
33+
/**
34+
* The type Spring doc sealed class module.
35+
*
36+
* @author sahil-ramagiri
37+
*/
38+
public class SpringDocSealedClassModule extends SimpleModule {
39+
40+
@Override
41+
public void setupModule(SetupContext context) {
42+
context.insertAnnotationIntrospector(new RespectSealedClassAnnotationIntrospector());
43+
}
44+
45+
/**
46+
* The type sealed class annotation introspector.
47+
*/
48+
private static class RespectSealedClassAnnotationIntrospector extends SwaggerAnnotationIntrospector {
49+
50+
@Override
51+
public List<NamedType> findSubtypes(Annotated annotated) {
52+
ArrayList<NamedType> subTypes = new ArrayList<>();
53+
54+
if (annotated.getAnnotated() instanceof Class<?> clazz && clazz.isSealed()) {
55+
Class<?>[] permittedSubClasses = clazz.getPermittedSubclasses();
56+
if (permittedSubClasses.length > 0) {
57+
Arrays.stream(permittedSubClasses).map(NamedType::new).forEach(subTypes::add);
58+
}
59+
}
60+
61+
return subTypes;
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2025 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app240;
26+
27+
28+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
29+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
30+
31+
@JsonTypeInfo(use = Id.NAME, property = "type")
32+
public abstract sealed class AbstractParent {
33+
private int id;
34+
35+
public int getId() {
36+
return id;
37+
}
38+
39+
public void setId(int id) {
40+
this.id = id;
41+
}
42+
}
43+
44+
final class ChildOfAbstract1 extends AbstractParent {
45+
private String abstrachChild1Param;
46+
47+
public String getAbstrachChild1Param() {
48+
return abstrachChild1Param;
49+
}
50+
51+
public void setAbstrachChild1Param(String abstrachChild1Param) {
52+
this.abstrachChild1Param = abstrachChild1Param;
53+
}
54+
}
55+
56+
final class ChildOfAbstract2 extends AbstractParent {
57+
private String abstractChild2Param;
58+
59+
public String getAbstractChild2Param() {
60+
return abstractChild2Param;
61+
}
62+
63+
public void setAbstractChild2Param(String abstractChild2Param) {
64+
this.abstractChild2Param = abstractChild2Param;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2025 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app240;
26+
27+
28+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
29+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
30+
31+
@JsonTypeInfo(use = Id.NAME, property = "type")
32+
public sealed class ConcreteParent {
33+
private int id;
34+
35+
public int getId() {
36+
return id;
37+
}
38+
39+
public void setId(int id) {
40+
this.id = id;
41+
}
42+
}
43+
44+
final class ChildOfConcrete1 extends ConcreteParent {
45+
private String concreteChild1Param;
46+
47+
public String getConcreteChild1Param() {
48+
return concreteChild1Param;
49+
}
50+
51+
public void setConcreteChild1Param(String concreteChild1Param) {
52+
this.concreteChild1Param = concreteChild1Param;
53+
}
54+
}
55+
56+
final class ChildOfConcrete2 extends ConcreteParent {
57+
private String concreteChild2Param;
58+
59+
public String getConcreteChild2Param() {
60+
return concreteChild2Param;
61+
}
62+
63+
public void setConcreteChild2Param(String concreteChild2Param) {
64+
this.concreteChild2Param = concreteChild2Param;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2025 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app240;
26+
27+
import java.util.List;
28+
29+
import org.springframework.web.bind.annotation.PostMapping;
30+
import org.springframework.web.bind.annotation.RequestBody;
31+
import org.springframework.web.bind.annotation.RequestMapping;
32+
import org.springframework.web.bind.annotation.RestController;
33+
34+
@RestController
35+
@RequestMapping("class-hierarchy")
36+
public class Controller {
37+
@PostMapping("abstract-parent")
38+
public Response abstractParent(@RequestBody AbstractParent payload) {
39+
return null;
40+
}
41+
42+
@PostMapping("concrete-parent")
43+
public Response concreteParent(@RequestBody ConcreteParent payload) {
44+
return null;
45+
}
46+
}
47+
48+
class Response {
49+
AbstractParent abstractParent;
50+
51+
List<ConcreteParent> concreteParents;
52+
53+
public AbstractParent getAbstractParent() {
54+
return abstractParent;
55+
}
56+
57+
public void setAbstractParent(AbstractParent abstractParent) {
58+
this.abstractParent = abstractParent;
59+
}
60+
61+
public List<ConcreteParent> getConcreteParents() {
62+
return concreteParents;
63+
}
64+
65+
public void setConcreteParents(List<ConcreteParent> concreteParents) {
66+
this.concreteParents = concreteParents;
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app240;
26+
27+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
28+
29+
import org.springframework.boot.autoconfigure.SpringBootApplication;
30+
31+
public class SpringDocApp240Test extends AbstractSpringDocV30Test {
32+
33+
@SpringBootApplication
34+
static class SpringDocTestApp {}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * *
7+
* * * * * * Copyright 2019-2025 the original author or authors.
8+
* * * * * *
9+
* * * * * * Licensed under the Apache License, Version 2.0 (the "License");
10+
* * * * * * you may not use this file except in compliance with the License.
11+
* * * * * * You may obtain a copy of the License at
12+
* * * * * *
13+
* * * * * * https://www.apache.org/licenses/LICENSE-2.0
14+
* * * * * *
15+
* * * * * * Unless required by applicable law or agreed to in writing, software
16+
* * * * * * distributed under the License is distributed on an "AS IS" BASIS,
17+
* * * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* * * * * * See the License for the specific language governing permissions and
19+
* * * * * * limitations under the License.
20+
* * * * *
21+
* * * *
22+
* * *
23+
* *
24+
*
25+
*/
26+
27+
package test.org.springdoc.api.v30.app241;
28+
29+
30+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
31+
import io.swagger.v3.oas.annotations.media.Schema;
32+
33+
import org.springframework.web.bind.annotation.PostMapping;
34+
import org.springframework.web.bind.annotation.RequestBody;
35+
import org.springframework.web.bind.annotation.RestController;
36+
37+
@RestController
38+
public class HelloController {
39+
40+
@PostMapping("/parent")
41+
public void parentEndpoint(@RequestBody Superclass parent) {
42+
43+
}
44+
45+
}
46+
47+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
48+
sealed class Superclass permits IntermediateClass {
49+
50+
public Superclass() {}
51+
}
52+
53+
@Schema(name = IntermediateClass.SCHEMA_NAME)
54+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
55+
sealed class IntermediateClass extends Superclass permits FirstChildClass, SecondChildClass {
56+
57+
public static final String SCHEMA_NAME = "IntermediateClass";
58+
}
59+
60+
@Schema(name = FirstChildClass.SCHEMA_NAME)
61+
final class FirstChildClass extends IntermediateClass {
62+
63+
public static final String SCHEMA_NAME = "Image";
64+
}
65+
66+
@Schema(name = SecondChildClass.SCHEMA_NAME)
67+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "@type")
68+
sealed class SecondChildClass extends IntermediateClass {
69+
70+
public static final String SCHEMA_NAME = "Mail";
71+
}
72+
73+
@Schema(name = ThirdChildClass.SCHEMA_NAME)
74+
final class ThirdChildClass extends SecondChildClass {
75+
76+
public static final String SCHEMA_NAME = "Home";
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 the original author or authors.
7+
* * * * *
8+
* * * * * Licensed under the Apache License, Version 2.0 (the "License");
9+
* * * * * you may not use this file except in compliance with the License.
10+
* * * * * You may obtain a copy of the License at
11+
* * * * *
12+
* * * * * https://www.apache.org/licenses/LICENSE-2.0
13+
* * * * *
14+
* * * * * Unless required by applicable law or agreed to in writing, software
15+
* * * * * distributed under the License is distributed on an "AS IS" BASIS,
16+
* * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* * * * * See the License for the specific language governing permissions and
18+
* * * * * limitations under the License.
19+
* * * *
20+
* * *
21+
* *
22+
*
23+
*/
24+
25+
package test.org.springdoc.api.v30.app241;
26+
27+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
28+
29+
import org.springframework.boot.autoconfigure.SpringBootApplication;
30+
31+
public class SpringDocApp241Test extends AbstractSpringDocV30Test {
32+
33+
@SpringBootApplication
34+
static class SpringDocTestApp {}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/class-hierarchy/concrete-parent": {
15+
"post": {
16+
"tags": [
17+
"controller"
18+
],
19+
"operationId": "concreteParent",
20+
"requestBody": {
21+
"content": {
22+
"application/json": {
23+
"schema": {
24+
"oneOf": [
25+
{
26+
"$ref": "#/components/schemas/ConcreteParent"
27+
},
28+
{
29+
"$ref": "#/components/schemas/ChildOfConcrete1"
30+
},
31+
{
32+
"$ref": "#/components/schemas/ChildOfConcrete2"
33+
}
34+
]
35+
}
36+
}
37+
},
38+
"required": true
39+
},
40+
"responses": {
41+
"200": {
42+
"description": "OK",
43+
"content": {
44+
"*/*": {
45+
"schema": {
46+
"$ref": "#/components/schemas/Response"
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
},
54+
"/class-hierarchy/abstract-parent": {
55+
"post": {
56+
"tags": [
57+
"controller"
58+
],
59+
"operationId": "abstractParent",
60+
"requestBody": {
61+
"content": {
62+
"application/json": {
63+
"schema": {
64+
"oneOf": [
65+
{
66+
"$ref": "#/components/schemas/ChildOfAbstract1"
67+
},
68+
{
69+
"$ref": "#/components/schemas/ChildOfAbstract2"
70+
}
71+
]
72+
}
73+
}
74+
},
75+
"required": true
76+
},
77+
"responses": {
78+
"200": {
79+
"description": "OK",
80+
"content": {
81+
"*/*": {
82+
"schema": {
83+
"$ref": "#/components/schemas/Response"
84+
}
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
},
92+
"components": {
93+
"schemas": {
94+
"ChildOfConcrete1": {
95+
"type": "object",
96+
"allOf": [
97+
{
98+
"$ref": "#/components/schemas/ConcreteParent"
99+
},
100+
{
101+
"type": "object",
102+
"properties": {
103+
"concreteChild1Param": {
104+
"type": "string"
105+
}
106+
}
107+
}
108+
]
109+
},
110+
"ChildOfConcrete2": {
111+
"type": "object",
112+
"allOf": [
113+
{
114+
"$ref": "#/components/schemas/ConcreteParent"
115+
},
116+
{
117+
"type": "object",
118+
"properties": {
119+
"concreteChild2Param": {
120+
"type": "string"
121+
}
122+
}
123+
}
124+
]
125+
},
126+
"ConcreteParent": {
127+
"required": [
128+
"type"
129+
],
130+
"type": "object",
131+
"properties": {
132+
"id": {
133+
"type": "integer",
134+
"format": "int32"
135+
},
136+
"type": {
137+
"type": "string"
138+
}
139+
},
140+
"discriminator": {
141+
"propertyName": "type"
142+
}
143+
},
144+
"AbstractParent": {
145+
"required": [
146+
"type"
147+
],
148+
"type": "object",
149+
"properties": {
150+
"id": {
151+
"type": "integer",
152+
"format": "int32"
153+
},
154+
"type": {
155+
"type": "string"
156+
}
157+
},
158+
"discriminator": {
159+
"propertyName": "type"
160+
}
161+
},
162+
"ChildOfAbstract1": {
163+
"type": "object",
164+
"allOf": [
165+
{
166+
"$ref": "#/components/schemas/AbstractParent"
167+
},
168+
{
169+
"type": "object",
170+
"properties": {
171+
"abstrachChild1Param": {
172+
"type": "string"
173+
}
174+
}
175+
}
176+
]
177+
},
178+
"ChildOfAbstract2": {
179+
"type": "object",
180+
"allOf": [
181+
{
182+
"$ref": "#/components/schemas/AbstractParent"
183+
},
184+
{
185+
"type": "object",
186+
"properties": {
187+
"abstractChild2Param": {
188+
"type": "string"
189+
}
190+
}
191+
}
192+
]
193+
},
194+
"Response": {
195+
"type": "object",
196+
"properties": {
197+
"abstractParent": {
198+
"oneOf": [
199+
{
200+
"$ref": "#/components/schemas/ChildOfAbstract1"
201+
},
202+
{
203+
"$ref": "#/components/schemas/ChildOfAbstract2"
204+
}
205+
]
206+
},
207+
"concreteParents": {
208+
"type": "array",
209+
"items": {
210+
"oneOf": [
211+
{
212+
"$ref": "#/components/schemas/ConcreteParent"
213+
},
214+
{
215+
"$ref": "#/components/schemas/ChildOfConcrete1"
216+
},
217+
{
218+
"$ref": "#/components/schemas/ChildOfConcrete2"
219+
}
220+
]
221+
}
222+
}
223+
}
224+
}
225+
}
226+
}
227+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/parent": {
15+
"post": {
16+
"tags": [
17+
"hello-controller"
18+
],
19+
"operationId": "parentEndpoint",
20+
"requestBody": {
21+
"content": {
22+
"application/json": {
23+
"schema": {
24+
"oneOf": [
25+
{
26+
"$ref": "#/components/schemas/Superclass"
27+
},
28+
{
29+
"$ref": "#/components/schemas/IntermediateClass"
30+
},
31+
{
32+
"$ref": "#/components/schemas/Image"
33+
},
34+
{
35+
"$ref": "#/components/schemas/Mail"
36+
},
37+
{
38+
"$ref": "#/components/schemas/Home"
39+
}
40+
]
41+
}
42+
}
43+
},
44+
"required": true
45+
},
46+
"responses": {
47+
"200": {
48+
"description": "OK"
49+
}
50+
}
51+
}
52+
}
53+
},
54+
"components": {
55+
"schemas": {
56+
"Home": {
57+
"type": "object",
58+
"allOf": [
59+
{
60+
"$ref": "#/components/schemas/Mail"
61+
}
62+
]
63+
},
64+
"Image": {
65+
"type": "object",
66+
"allOf": [
67+
{
68+
"$ref": "#/components/schemas/IntermediateClass"
69+
}
70+
]
71+
},
72+
"IntermediateClass": {
73+
"required": [
74+
"@type"
75+
],
76+
"type": "object",
77+
"discriminator": {
78+
"propertyName": "@type"
79+
},
80+
"allOf": [
81+
{
82+
"$ref": "#/components/schemas/Superclass"
83+
},
84+
{
85+
"type": "object",
86+
"properties": {
87+
"@type": {
88+
"type": "string"
89+
}
90+
}
91+
}
92+
]
93+
},
94+
"Mail": {
95+
"required": [
96+
"@type"
97+
],
98+
"type": "object",
99+
"discriminator": {
100+
"propertyName": "@type"
101+
},
102+
"allOf": [
103+
{
104+
"$ref": "#/components/schemas/IntermediateClass"
105+
},
106+
{
107+
"type": "object",
108+
"properties": {
109+
"@type": {
110+
"type": "string"
111+
}
112+
}
113+
}
114+
]
115+
},
116+
"Superclass": {
117+
"required": [
118+
"@type"
119+
],
120+
"type": "object",
121+
"properties": {
122+
"@type": {
123+
"type": "string"
124+
}
125+
},
126+
"discriminator": {
127+
"propertyName": "@type"
128+
}
129+
}
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)
Please sign in to comment.