Skip to content

Commit d04fef8

Browse files
author
bnasslahsen
committed
Improve management of abstract generic. Fixes #829
1 parent 8a8ae05 commit d04fef8

File tree

7 files changed

+195
-9
lines changed

7 files changed

+195
-9
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/core/ReturnTypeParser.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,47 @@ static Type resolveType(Type genericType, @Nullable Class<?> contextClass) {
5959
else if (genericType instanceof ParameterizedType) {
6060
ResolvableType resolvedType = ResolvableType.forType(genericType);
6161
if (resolvedType.hasUnresolvableGenerics()) {
62-
ParameterizedType parameterizedType = (ParameterizedType) genericType;
63-
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
64-
Type[] typeArguments = parameterizedType.getActualTypeArguments();
65-
ResolvableType contextType = ResolvableType.forClass(contextClass);
66-
findTypeForGenerics(generics, typeArguments, contextType);
67-
Class<?> rawClass = resolvedType.getRawClass();
68-
if (rawClass != null) {
69-
return ResolvableType.forClassWithGenerics(rawClass, generics).getType();
70-
}
62+
ResolvableType resolvableType = getResolvedType(resolvedType, contextClass);
63+
if (resolvableType != null)
64+
return resolvableType.getType();
65+
}
66+
else if (resolvedType.hasGenerics()) {
67+
ResolvableType[] resolvableTypes = resolvedType.getGenerics();
68+
resolveType(resolvableTypes, contextClass);
69+
return ResolvableType.forClassWithGenerics(resolvedType.getRawClass(), resolvableTypes).getType();
7170
}
7271
}
7372
}
7473
return genericType;
7574
}
7675

76+
static void resolveType(ResolvableType[] resolvableTypes, Class<?> contextClass) {
77+
for (int i = 0; i < resolvableTypes.length; i++) {
78+
if (resolvableTypes[i].hasUnresolvableGenerics() && resolvableTypes[i].getType() instanceof ParameterizedType) {
79+
ResolvableType resolvableType = getResolvedType(resolvableTypes[i], contextClass);
80+
if (resolvableType != null)
81+
resolvableTypes[i] = resolvableType;
82+
}
83+
else if (resolvableTypes[i].hasGenerics()) {
84+
resolveType(resolvableTypes[i].getGenerics(), contextClass);
85+
if (resolvableTypes[i].getRawClass() != null)
86+
resolvableTypes[i] = ResolvableType.forClassWithGenerics(resolvableTypes[i].getRawClass(), resolvableTypes[i].getGenerics());
87+
}
88+
}
89+
}
90+
91+
static ResolvableType getResolvedType(ResolvableType resolvableType, Class<?> contextClass) {
92+
ParameterizedType parameterizedType = (ParameterizedType) resolvableType.getType();
93+
Class<?>[] generics = new Class<?>[parameterizedType.getActualTypeArguments().length];
94+
Type[] typeArguments = parameterizedType.getActualTypeArguments();
95+
ResolvableType contextType = ResolvableType.forClass(contextClass);
96+
findTypeForGenerics(generics, typeArguments, contextType);
97+
Class<?> rawClass = resolvableType.getRawClass();
98+
if (rawClass != null)
99+
return ResolvableType.forClassWithGenerics(rawClass, generics);
100+
return null;
101+
}
102+
77103
/**
78104
* Find type for generics.
79105
*
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package test.org.springdoc.api.app79;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
public abstract class BaseClientModel {
6+
@JsonProperty("id")
7+
int id;
8+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package test.org.springdoc.api.app79;
2+
3+
import java.util.List;
4+
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import reactor.core.publisher.Mono;
7+
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
11+
public abstract class BaseController<TClientModel extends BaseClientModel> {
12+
@Operation
13+
@GetMapping("/test1")
14+
Mono<ResponseEntity<TClientModel>> get1() {
15+
return null;
16+
}
17+
18+
@Operation
19+
@GetMapping("/test2")
20+
Mono<ResponseEntity<List<TClientModel>>> get2() {
21+
return null;
22+
}
23+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package test.org.springdoc.api.app79;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
public class SpecificClientModel extends BaseClientModel {
7+
@JsonProperty("name")
8+
@Schema(title = "my title", pattern = "this is it", example = "this is example")
9+
String name;
10+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package test.org.springdoc.api.app79;
2+
3+
import org.springframework.web.bind.annotation.RestController;
4+
5+
@RestController
6+
public class SpecificController extends BaseController<SpecificClientModel> {}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * * Copyright 2019-2020 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+
24+
package test.org.springdoc.api.app79;
25+
26+
import test.org.springdoc.api.AbstractSpringDocTest;
27+
28+
import org.springframework.boot.autoconfigure.SpringBootApplication;
29+
import org.springframework.context.annotation.ComponentScan;
30+
31+
public class SpringDocApp79Test extends AbstractSpringDocTest {
32+
33+
@SpringBootApplication
34+
@ComponentScan(basePackages = { "org.springdoc", "test.org.springdoc.api.app79" })
35+
static class SpringDocTestApp {}
36+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/test2": {
15+
"get": {
16+
"tags": [
17+
"specific-controller"
18+
],
19+
"operationId": "get2",
20+
"responses": {
21+
"200": {
22+
"description": "OK",
23+
"content": {
24+
"*/*": {
25+
"schema": {
26+
"type": "array",
27+
"items": {
28+
"$ref": "#/components/schemas/SpecificClientModel"
29+
}
30+
}
31+
}
32+
}
33+
}
34+
}
35+
}
36+
},
37+
"/test1": {
38+
"get": {
39+
"tags": [
40+
"specific-controller"
41+
],
42+
"operationId": "get1",
43+
"responses": {
44+
"200": {
45+
"description": "OK",
46+
"content": {
47+
"*/*": {
48+
"schema": {
49+
"$ref": "#/components/schemas/SpecificClientModel"
50+
}
51+
}
52+
}
53+
}
54+
}
55+
}
56+
}
57+
},
58+
"components": {
59+
"schemas": {
60+
"SpecificClientModel": {
61+
"type": "object",
62+
"properties": {
63+
"id": {
64+
"type": "integer",
65+
"format": "int32"
66+
},
67+
"name": {
68+
"title": "my title",
69+
"pattern": "this is it",
70+
"type": "string",
71+
"example": "this is example"
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)