Skip to content

Commit 7b4f210

Browse files
committed
Changes report: Support custom Spring type converters. Fixes #1534.
1 parent 1405109 commit 7b4f210

File tree

10 files changed

+244
-31
lines changed

10 files changed

+244
-31
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
import org.springframework.context.annotation.Configuration;
9797
import org.springframework.context.annotation.Lazy;
9898
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
99+
import org.springframework.core.convert.support.GenericConversionService;
99100
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
100101
import org.springframework.http.HttpStatus;
101102
import org.springframework.http.ResponseEntity;
@@ -481,13 +482,13 @@ static class WebConversionServiceConfiguration {
481482
/**
482483
* Web conversion service provider web conversion service provider.
483484
*
484-
* @param webConversionServiceOptional the web conversion service optional
485+
* @param mvcConversionService the web conversion service optional
485486
* @return the web conversion service provider
486487
*/
487488
@Bean
488489
@Lazy(false)
489-
WebConversionServiceProvider webConversionServiceProvider(Optional<WebConversionService> webConversionServiceOptional) {
490-
return new WebConversionServiceProvider(webConversionServiceOptional);
490+
WebConversionServiceProvider webConversionServiceProvider(Optional<GenericConversionService> mvcConversionService) {
491+
return new WebConversionServiceProvider(mvcConversionService);
491492
}
492493
}
493494

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/extractor/MethodParameterPojoExtractor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ static boolean isSimpleType(Class<?> clazz) {
226226
* @param clazz the clazz
227227
* @return the boolean
228228
*/
229-
private static boolean isSwaggerPrimitiveType(Class<?> clazz) {
229+
public static boolean isSwaggerPrimitiveType(Class<?> clazz) {
230230
PrimitiveType primitiveType = PrimitiveType.fromType(clazz);
231231
return primitiveType != null;
232232
}
Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,17 @@
1-
/*
2-
*
3-
* *
4-
* * *
5-
* * * * Copyright 2019-2022 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-
231
package org.springdoc.core.providers;
242

3+
import java.lang.reflect.Field;
4+
import java.util.Map;
255
import java.util.Optional;
266

27-
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
7+
import org.apache.commons.lang3.reflect.FieldUtils;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
2811
import org.springframework.core.convert.TypeDescriptor;
12+
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
13+
import org.springframework.core.convert.support.GenericConversionService;
2914
import org.springframework.format.support.DefaultFormattingConversionService;
30-
import org.springframework.format.support.FormattingConversionService;
3115
import org.springframework.lang.Nullable;
3216

3317
/**
@@ -36,17 +20,23 @@
3620
*/
3721
public class WebConversionServiceProvider {
3822

23+
private static final String CONVERTERS = "converters";
24+
25+
/**
26+
* The constant LOGGER.
27+
*/
28+
private static final Logger LOGGER = LoggerFactory.getLogger(WebConversionServiceProvider.class);
3929
/**
4030
* The Formatting conversion service.
4131
*/
42-
private final FormattingConversionService formattingConversionService;
32+
private final GenericConversionService formattingConversionService;
4333

4434
/**
4535
* Instantiates a new Web conversion service provider.
4636
*
4737
* @param webConversionServiceOptional the web conversion service optional
4838
*/
49-
public WebConversionServiceProvider(Optional<WebConversionService> webConversionServiceOptional) {
39+
public WebConversionServiceProvider(Optional<GenericConversionService> webConversionServiceOptional) {
5040
if (webConversionServiceOptional.isPresent())
5141
this.formattingConversionService = webConversionServiceOptional.get();
5242
else
@@ -62,6 +52,32 @@ public WebConversionServiceProvider(Optional<WebConversionService> webConversion
6252
*/
6353
@Nullable
6454
public Object convert(@Nullable Object source, TypeDescriptor targetTypeDescriptor) {
65-
return formattingConversionService.convert(source, targetTypeDescriptor);
55+
return formattingConversionService.convert(source, targetTypeDescriptor);
56+
}
57+
58+
/**
59+
* Gets spring converted type.
60+
*
61+
* @param clazz the clazz
62+
* @return the spring converted type
63+
*/
64+
public Class<?> getSpringConvertedType(Class<?> clazz) {
65+
Class<?> result = clazz;
66+
Field convertersField = FieldUtils.getDeclaredField(GenericConversionService.class, CONVERTERS, true);
67+
Object converters;
68+
try {
69+
converters = convertersField.get(formattingConversionService);
70+
convertersField = FieldUtils.getDeclaredField(converters.getClass(), CONVERTERS, true);
71+
Map<ConvertiblePair, Object> springConverters = (Map) convertersField.get(converters);
72+
Optional<ConvertiblePair> convertiblePairOptional = springConverters.keySet().stream().filter(convertiblePair -> convertiblePair.getTargetType().equals(clazz)).findAny();
73+
if (convertiblePairOptional.isPresent()) {
74+
ConvertiblePair convertiblePair = convertiblePairOptional.get();
75+
result = convertiblePair.getSourceType();
76+
}
77+
}
78+
catch (IllegalAccessException e) {
79+
LOGGER.warn(e.getMessage());
80+
}
81+
return result;
6682
}
6783
}

springdoc-openapi-starter-common/src/main/java/org/springdoc/core/service/GenericParameterService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.slf4j.Logger;
5353
import org.slf4j.LoggerFactory;
5454
import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;
55+
import org.springdoc.core.extractor.MethodParameterPojoExtractor;
5556
import org.springdoc.core.models.ParameterInfo;
5657
import org.springdoc.core.models.RequestBodyInfo;
5758
import org.springdoc.core.parsers.ReturnTypeParser;
@@ -326,6 +327,11 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque
326327

327328
if (parameterInfo.getParameterModel() == null || parameterInfo.getParameterModel().getSchema() == null) {
328329
Type type = ReturnTypeParser.getType(methodParameter);
330+
if(type instanceof Class && optionalWebConversionServiceProvider.isPresent()){
331+
WebConversionServiceProvider webConversionServiceProvider = optionalWebConversionServiceProvider.get();
332+
if (!MethodParameterPojoExtractor.isSwaggerPrimitiveType((Class) type))
333+
type = webConversionServiceProvider.getSpringConvertedType(methodParameter.getParameterType());
334+
}
329335
schemaN = SpringDocAnnotationsUtils.extractSchema(components, type, jsonView, methodParameter.getParameterAnnotations());
330336
}
331337
else
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package test.org.springdoc.api.app183;
2+
3+
import io.swagger.v3.oas.annotations.tags.Tag;
4+
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.PathVariable;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
/**
10+
* The type Hello controller.
11+
*/
12+
@RestController
13+
@Tag(name = "NetworkServices", description = "the NetworkServices API")
14+
public class HelloController {
15+
16+
17+
18+
19+
@GetMapping("/{userId}")
20+
public User doSomething(@PathVariable("userId") User user) {
21+
return new User(user.getId(), "tototot");
22+
}
23+
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package test.org.springdoc.api.app183;
20+
21+
import test.org.springdoc.api.AbstractSpringDocTest;
22+
23+
import org.springframework.boot.autoconfigure.SpringBootApplication;
24+
25+
public class SpringDocApp183Test extends AbstractSpringDocTest {
26+
27+
@SpringBootApplication
28+
static class SpringDocTestApp {}
29+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package test.org.springdoc.api.app183;
2+
3+
public class User {
4+
5+
String id;
6+
7+
String toto;
8+
9+
10+
public User() {
11+
}
12+
13+
public User(String id, String toto) {
14+
this.id = id;
15+
this.toto = toto;
16+
}
17+
18+
public String getId() {
19+
return id;
20+
}
21+
22+
public void setId(String id) {
23+
this.id = id;
24+
}
25+
26+
public String getToto() {
27+
return toto;
28+
}
29+
30+
public void setToto(String toto) {
31+
this.toto = toto;
32+
}
33+
34+
@Override
35+
public String toString() {
36+
return "User{" +
37+
"id='" + id + '\'' +
38+
", toto='" + toto + '\'' +
39+
'}';
40+
}
41+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package test.org.springdoc.api.app183;
2+
3+
import org.springframework.core.convert.converter.Converter;
4+
5+
public class UserConverter implements Converter<String, User> {
6+
7+
@Override
8+
public User convert(String userId) {
9+
// Fetch from repository
10+
User user = new User();
11+
user.setId(userId);
12+
return user;
13+
}
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package test.org.springdoc.api.app183;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.format.FormatterRegistry;
5+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
6+
7+
@Configuration
8+
public class WebConfig implements WebMvcConfigurer {
9+
10+
@Override
11+
public void addFormatters(FormatterRegistry registry) {
12+
registry.addConverter(new UserConverter());
13+
}
14+
15+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
"tags": [
14+
{
15+
"name": "NetworkServices",
16+
"description": "the NetworkServices API"
17+
}
18+
],
19+
"paths": {
20+
"/{userId}": {
21+
"get": {
22+
"tags": [
23+
"NetworkServices"
24+
],
25+
"operationId": "doSomething",
26+
"parameters": [
27+
{
28+
"name": "userId",
29+
"in": "path",
30+
"required": true,
31+
"schema": {
32+
"type": "string"
33+
}
34+
}
35+
],
36+
"responses": {
37+
"200": {
38+
"description": "OK",
39+
"content": {
40+
"*/*": {
41+
"schema": {
42+
"$ref": "#/components/schemas/User"
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
},
51+
"components": {
52+
"schemas": {
53+
"User": {
54+
"type": "object",
55+
"properties": {
56+
"id": {
57+
"type": "string"
58+
},
59+
"toto": {
60+
"type": "string"
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)