Skip to content

Commit 8a1e0ad

Browse files
committed
Multiple Superclasses Are Not Mapped To Multiple allOf If Used In Different Services. Fixes #2601.
1 parent fa35308 commit 8a1e0ad

File tree

5 files changed

+225
-6
lines changed

5 files changed

+225
-6
lines changed

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/converters/PolymorphicModelConverter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private List<Schema> findComposedSchemas(String ref, Collection<Schema> schemas)
122122
.filter(s -> s.getAllOf() != null)
123123
.filter(s -> s.getAllOf().stream().anyMatch(s2 -> ref.equals(s2.get$ref())))
124124
.map(s -> new Schema().$ref(AnnotationsUtils.COMPONENTS_REF + s.getName()))
125-
.collect(Collectors.toList());
125+
.toList();
126126

127127
List<Schema> resultSchemas = new ArrayList<>(composedSchemas);
128128

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/SpringDocAnnotationsUtils.java

+19-5
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
import java.util.Arrays;
3232
import java.util.Collections;
3333
import java.util.LinkedHashMap;
34+
import java.util.LinkedHashSet;
3435
import java.util.List;
3536
import java.util.Map;
3637
import java.util.Optional;
38+
import java.util.Set;
3739

3840
import com.fasterxml.jackson.annotation.JsonIgnore;
3941
import com.fasterxml.jackson.annotation.JsonView;
@@ -65,6 +67,7 @@
6567

6668
/**
6769
* The type Spring doc annotations utils.
70+
*
6871
* @author bnasslahsen
6972
*/
7073
@SuppressWarnings({ "rawtypes" })
@@ -142,10 +145,21 @@ public static Schema extractSchema(Components components, Type returnType, JsonV
142145
for (Map.Entry<String, Schema> entry : schemaMap.entrySet()) {
143146
// If we've seen this schema before but find later it should be polymorphic,
144147
// replace the existing schema with this richer version.
148+
Schema existingSchema = componentSchemas.get(entry.getKey());
145149
if (!componentSchemas.containsKey(entry.getKey()) ||
146-
(!entry.getValue().getClass().equals(componentSchemas.get(entry.getKey()).getClass()) && entry.getValue().getAllOf() != null)) {
150+
(!entry.getValue().getClass().equals(existingSchema.getClass()) && entry.getValue().getAllOf() != null)) {
147151
componentSchemas.put(entry.getKey(), entry.getValue());
148152
}
153+
else if (componentSchemas.containsKey(entry.getKey()) && schemaMap.containsKey(entry.getKey())) {
154+
// Check to merge polymorphic types
155+
Set<Schema> existingAllOf = new LinkedHashSet<>();
156+
if(existingSchema.getAllOf() != null)
157+
existingAllOf.addAll(existingSchema.getAllOf());
158+
if (schemaMap.get(entry.getKey()).getAllOf() != null){
159+
existingAllOf.addAll(schemaMap.get(entry.getKey()).getAllOf());
160+
existingSchema.setAllOf(new ArrayList<>(existingAllOf));
161+
}
162+
}
149163
}
150164
components.setSchemas(componentSchemas);
151165
}
@@ -207,8 +221,8 @@ public static Optional<Content> getContent(io.swagger.v3.oas.annotations.media.C
207221
* Merge schema.
208222
*
209223
* @param existingContent the existing content
210-
* @param schemaN the schema n
211-
* @param mediaTypeStr the media type str
224+
* @param schemaN the schema n
225+
* @param mediaTypeStr the media type str
212226
*/
213227
public static void mergeSchema(Content existingContent, Schema<?> schemaN, String mediaTypeStr) {
214228
if (existingContent.containsKey(mediaTypeStr)) {
@@ -322,7 +336,7 @@ private static void addExtension(io.swagger.v3.oas.annotations.media.Content ann
322336
* Sets examples.
323337
*
324338
* @param mediaType the media type
325-
* @param examples the examples
339+
* @param examples the examples
326340
*/
327341
private static void setExamples(MediaType mediaType, ExampleObject[] examples) {
328342
if (examples.length == 1 && StringUtils.isBlank(examples[0].name())) {
@@ -436,7 +450,7 @@ private static boolean isArray(io.swagger.v3.oas.annotations.media.Content annot
436450
* Resolve default value object.
437451
*
438452
* @param defaultValueStr the default value str
439-
* @param objectMapper the object mapper
453+
* @param objectMapper the object mapper
440454
* @return the object
441455
*/
442456
public static Object resolveDefaultValue(String defaultValueStr, ObjectMapper objectMapper) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package test.org.springdoc.api.v30.app222;
2+
3+
4+
5+
import test.org.springdoc.api.v30.app222.SpringDocApp222Test.FirstHierarchyUser;
6+
import test.org.springdoc.api.v30.app222.SpringDocApp222Test.SecondHierarchyUser;
7+
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
/**
12+
* @author bnasslahsen
13+
*/
14+
15+
@RestController
16+
class HelloController {
17+
18+
@GetMapping("/hello1")
19+
public FirstHierarchyUser getItems1() {
20+
return null;
21+
}
22+
23+
@GetMapping("/hello2")
24+
public SecondHierarchyUser getItems2() {
25+
return null;
26+
}
27+
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2022 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.app222;
26+
27+
import com.fasterxml.jackson.annotation.JsonSubTypes;
28+
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
29+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
30+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
31+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
32+
33+
import org.springframework.boot.autoconfigure.SpringBootApplication;
34+
35+
public class SpringDocApp222Test extends AbstractSpringDocV30Test {
36+
37+
@SpringBootApplication
38+
static class SpringDocTestApp {}
39+
40+
@JsonTypeInfo(use = Id.NAME, property = "@type")
41+
@JsonSubTypes(@Type(CommonImplementor.class))
42+
interface FirstHierarchy {}
43+
44+
@JsonTypeInfo(use = Id.NAME, property = "@type")
45+
@JsonSubTypes(@Type(CommonImplementor.class))
46+
interface SecondHierarchy {}
47+
48+
class CommonImplementor implements FirstHierarchy, SecondHierarchy {}
49+
50+
record CommonImplementorUser(FirstHierarchy firstHierarchy, SecondHierarchy secondHierarchy) {}
51+
52+
record FirstHierarchyUser(FirstHierarchy firstHierarchy) {}
53+
54+
record SecondHierarchyUser(SecondHierarchy secondHierarchy) {}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
"/hello2": {
15+
"get": {
16+
"tags": [
17+
"hello-controller"
18+
],
19+
"operationId": "getItems2",
20+
"responses": {
21+
"200": {
22+
"description": "OK",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"$ref": "#/components/schemas/SecondHierarchyUser"
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
33+
},
34+
"/hello1": {
35+
"get": {
36+
"tags": [
37+
"hello-controller"
38+
],
39+
"operationId": "getItems1",
40+
"responses": {
41+
"200": {
42+
"description": "OK",
43+
"content": {
44+
"*/*": {
45+
"schema": {
46+
"$ref": "#/components/schemas/FirstHierarchyUser"
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
},
55+
"components": {
56+
"schemas": {
57+
"CommonImplementor": {
58+
"type": "object",
59+
"allOf": [
60+
{
61+
"$ref": "#/components/schemas/SecondHierarchy"
62+
},
63+
{
64+
"$ref": "#/components/schemas/FirstHierarchy"
65+
}
66+
]
67+
},
68+
"SecondHierarchy": {
69+
"required": [
70+
"@type"
71+
],
72+
"type": "object",
73+
"properties": {
74+
"@type": {
75+
"type": "string"
76+
}
77+
},
78+
"discriminator": {
79+
"propertyName": "@type"
80+
}
81+
},
82+
"SecondHierarchyUser": {
83+
"type": "object",
84+
"properties": {
85+
"secondHierarchy": {
86+
"oneOf": [
87+
{
88+
"$ref": "#/components/schemas/CommonImplementor"
89+
}
90+
]
91+
}
92+
}
93+
},
94+
"FirstHierarchy": {
95+
"required": [
96+
"@type"
97+
],
98+
"type": "object",
99+
"properties": {
100+
"@type": {
101+
"type": "string"
102+
}
103+
},
104+
"discriminator": {
105+
"propertyName": "@type"
106+
}
107+
},
108+
"FirstHierarchyUser": {
109+
"type": "object",
110+
"properties": {
111+
"firstHierarchy": {
112+
"oneOf": [
113+
{
114+
"$ref": "#/components/schemas/CommonImplementor"
115+
}
116+
]
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}

0 commit comments

Comments
 (0)