Skip to content

Commit 935b984

Browse files
committed
Polymorphic fields on polymorphic parents don't get correct oneOf docs generated. Fixes #2597
1 parent 8a1e0ad commit 935b984

File tree

10 files changed

+666
-202
lines changed

10 files changed

+666
-202
lines changed

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

+32-7
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
import java.lang.reflect.Modifier;
2828
import java.util.ArrayList;
2929
import java.util.Collection;
30+
import java.util.Collections;
3031
import java.util.Iterator;
3132
import java.util.List;
32-
import java.util.stream.Collectors;
3333

3434
import com.fasterxml.jackson.databind.JavaType;
3535
import io.swagger.v3.core.converter.AnnotatedType;
@@ -43,6 +43,7 @@
4343

4444
/**
4545
* The type Polymorphic model converter.
46+
*
4647
* @author bnasslahsen
4748
*/
4849
public class PolymorphicModelConverter implements ModelConverter {
@@ -52,6 +53,17 @@ public class PolymorphicModelConverter implements ModelConverter {
5253
*/
5354
private final ObjectMapperProvider springDocObjectMapper;
5455

56+
/**
57+
* The constant PARENT_TYPES_TO_IGNORE.
58+
*/
59+
private static final List<String> PARENT_TYPES_TO_IGNORE = Collections.synchronizedList(new ArrayList<>());
60+
61+
static {
62+
PARENT_TYPES_TO_IGNORE.add("JsonSchema");
63+
PARENT_TYPES_TO_IGNORE.add("Pageable");
64+
PARENT_TYPES_TO_IGNORE.add("EntityModel");
65+
}
66+
5567
/**
5668
* Instantiates a new Polymorphic model converter.
5769
*
@@ -61,12 +73,21 @@ public PolymorphicModelConverter(ObjectMapperProvider springDocObjectMapper) {
6173
this.springDocObjectMapper = springDocObjectMapper;
6274
}
6375

64-
private static Schema<?> getResolvedSchema(JavaType javaType, Schema<?> resolvedSchema) {
76+
/**
77+
* Add parent type.
78+
*
79+
* @param parentTypes the parent types
80+
*/
81+
public static void addParentType(String... parentTypes) {
82+
PARENT_TYPES_TO_IGNORE.addAll(List.of(parentTypes));
83+
}
84+
85+
private Schema<?> getResolvedSchema(JavaType javaType, Schema<?> resolvedSchema) {
6586
if (resolvedSchema instanceof ObjectSchema && resolvedSchema.getProperties() != null) {
66-
if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getName())){
87+
if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getName())) {
6788
resolvedSchema = resolvedSchema.getProperties().get(javaType.getRawClass().getName());
6889
}
69-
else if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getSimpleName())){
90+
else if (resolvedSchema.getProperties().containsKey(javaType.getRawClass().getSimpleName())) {
7091
resolvedSchema = resolvedSchema.getProperties().get(javaType.getRawClass().getSimpleName());
7192
}
7293
}
@@ -78,6 +99,9 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
7899
JavaType javaType = springDocObjectMapper.jsonMapper().constructType(type.getType());
79100
if (javaType != null) {
80101
if (chain.hasNext()) {
102+
if (!type.isResolveAsRef() && type.getParent() != null
103+
&& PARENT_TYPES_TO_IGNORE.stream().noneMatch(ignore -> type.getParent().getName().startsWith(ignore)))
104+
type.resolveAsRef(true);
81105
Schema<?> resolvedSchema = chain.next().resolve(type, context, chain);
82106
resolvedSchema = getResolvedSchema(javaType, resolvedSchema);
83107
if (resolvedSchema == null || resolvedSchema.get$ref() == null)
@@ -91,8 +115,8 @@ public Schema resolve(AnnotatedType type, ModelConverterContext context, Iterato
91115
/**
92116
* Compose polymorphic schema.
93117
*
94-
* @param type the type
95-
* @param schema the schema
118+
* @param type the type
119+
* @param schema the schema
96120
* @param schemas the schemas
97121
* @return the schema
98122
*/
@@ -111,7 +135,7 @@ private Schema composePolymorphicSchema(AnnotatedType type, Schema schema, Colle
111135
/**
112136
* Find composed schemas recursively.
113137
*
114-
* @param ref the reference of the schema
138+
* @param ref the reference of the schema
115139
* @param schemas the collection of schemas to search in
116140
* @return the list of composed schemas
117141
*/
@@ -133,6 +157,7 @@ private List<Schema> findComposedSchemas(String ref, Collection<Schema> schemas)
133157

134158
return resultSchemas;
135159
}
160+
136161
/**
137162
* Is concrete class boolean.
138163
*

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

+12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springdoc.api.AbstractOpenApiResource;
3333
import org.springdoc.core.converters.AdditionalModelsConverter;
3434
import org.springdoc.core.converters.ConverterUtils;
35+
import org.springdoc.core.converters.PolymorphicModelConverter;
3536
import org.springdoc.core.converters.SchemaPropertyDeprecatingConverter;
3637
import org.springdoc.core.extractor.MethodParameterPojoExtractor;
3738
import org.springdoc.core.service.AbstractRequestService;
@@ -388,5 +389,16 @@ public static boolean isValidPath(String path) {
388389
return true;
389390
return false;
390391
}
392+
393+
/**
394+
* Add parent type spring doc utils.
395+
*
396+
* @param parentTypes the parent types
397+
* @return the spring doc utils
398+
*/
399+
public SpringDocUtils addParentType(String ...parentTypes) {
400+
PolymorphicModelConverter.addParentType(parentTypes);
401+
return this;
402+
}
391403
}
392404

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package test.org.springdoc.api.v30.app223;
2+
3+
4+
import test.org.springdoc.api.v30.app223.apiobjects.AbstractChild;
5+
import test.org.springdoc.api.v30.app223.apiobjects.AbstractParent;
6+
import test.org.springdoc.api.v30.app223.apiobjects.Response;
7+
8+
import org.springframework.web.bind.annotation.PostMapping;
9+
import org.springframework.web.bind.annotation.RequestBody;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
public class ARestController {
14+
@PostMapping("/parent")
15+
public Response parentEndpoint(@RequestBody AbstractParent parent) {
16+
return null;
17+
}
18+
19+
@PostMapping("/child")
20+
public Response childEndpoint(@RequestBody AbstractChild child) {
21+
return null;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.app223;
26+
27+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
28+
29+
import org.springframework.boot.autoconfigure.SpringBootApplication;
30+
31+
public class SpringDocApp223Test extends AbstractSpringDocV30Test {
32+
33+
@SpringBootApplication
34+
static class SpringDocTestApp {}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package test.org.springdoc.api.v30.app223.apiobjects;
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
7+
8+
@JsonTypeInfo(use = Id.NAME, property = "type")
9+
@JsonSubTypes({
10+
@Type(ChildType1.class),
11+
@Type(ChildType2.class)
12+
})
13+
public abstract class AbstractChild {
14+
private int id;
15+
16+
public AbstractChild(int id) {
17+
this.id = id;
18+
}
19+
20+
public int getId() {
21+
return id;
22+
}
23+
24+
public void setId(int id) {
25+
this.id = id;
26+
}
27+
}
28+
29+
class ChildType1 extends AbstractChild {
30+
private String childType1Param;
31+
32+
public ChildType1(int id, String childType1Param) {
33+
super(id);
34+
this.childType1Param = childType1Param;
35+
}
36+
37+
public String getChildType1Param() {
38+
return childType1Param;
39+
}
40+
41+
public void setChildType1Param(String childType1Param) {
42+
this.childType1Param = childType1Param;
43+
}
44+
}
45+
46+
class ChildType2 extends AbstractChild {
47+
private String childType2Param;
48+
49+
public ChildType2(int id, String childType2Param) {
50+
super(id);
51+
this.childType2Param = childType2Param;
52+
}
53+
54+
public String getChildType2Param() {
55+
return childType2Param;
56+
}
57+
58+
public void setChildType2Param(String childType2Param) {
59+
this.childType2Param = childType2Param;
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package test.org.springdoc.api.v30.app223.apiobjects;
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
7+
8+
9+
@JsonTypeInfo(use = Id.NAME, property = "type")
10+
@JsonSubTypes({
11+
@Type(ParentType1.class),
12+
@Type(ParentType2.class)
13+
})
14+
public abstract class AbstractParent {
15+
private int id;
16+
17+
public AbstractParent(int id) {
18+
this.id = id;
19+
}
20+
21+
public int getId() {
22+
return id;
23+
}
24+
25+
public void setId(int id) {
26+
this.id = id;
27+
}
28+
}
29+
30+
class ParentType1 extends AbstractParent {
31+
private String parentType1Param;
32+
private AbstractChild abstractChild;
33+
34+
public ParentType1(int id, String parentType1Param, AbstractChild abstractChild) {
35+
super(id);
36+
this.parentType1Param = parentType1Param;
37+
this.abstractChild = abstractChild;
38+
}
39+
40+
public String getParentType1Param() {
41+
return parentType1Param;
42+
}
43+
44+
public void setParentType1Param(String parentType1Param) {
45+
this.parentType1Param = parentType1Param;
46+
}
47+
48+
public AbstractChild getAbstractChild() {
49+
return abstractChild;
50+
}
51+
52+
public void setAbstractChild(AbstractChild abstractChild) {
53+
this.abstractChild = abstractChild;
54+
}
55+
}
56+
57+
class ParentType2 extends AbstractParent {
58+
private String parentType2Param;
59+
60+
public ParentType2(int id, String parentType2Param) {
61+
super(id);
62+
this.parentType2Param = parentType2Param;
63+
}
64+
65+
public String getParentType2Param() {
66+
return parentType2Param;
67+
}
68+
69+
public void setParentType2Param(String parentType2Param) {
70+
this.parentType2Param = parentType2Param;
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package test.org.springdoc.api.v30.app223.apiobjects;
2+
3+
public record Response(AbstractParent abstractParent, AbstractChild abstractChild) {
4+
}

0 commit comments

Comments
 (0)