Skip to content

Commit cf86b23

Browse files
committed
Support custom Spring type converters. Fixes #1534.
1 parent 0d4bbb9 commit cf86b23

File tree

10 files changed

+243
-9
lines changed

10 files changed

+243
-9
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,11 @@ Schema calculateSchema(Components components, ParameterInfo parameterInfo, Reque
318318

319319
if (parameterInfo.getParameterModel() == null || parameterInfo.getParameterModel().getSchema() == null) {
320320
Type type = ReturnTypeParser.getType(methodParameter);
321+
if(type instanceof Class && optionalWebConversionServiceProvider.isPresent()){
322+
WebConversionServiceProvider webConversionServiceProvider = optionalWebConversionServiceProvider.get();
323+
if (!MethodParameterPojoExtractor.isSwaggerPrimitiveType((Class) type))
324+
type = webConversionServiceProvider.getSpringConvertedType(methodParameter.getParameterType());
325+
}
321326
schemaN = SpringDocAnnotationsUtils.extractSchema(components, type, jsonView, methodParameter.getParameterAnnotations());
322327
}
323328
else

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ static boolean isSimpleType(Class<?> clazz) {
215215
* @param clazz the clazz
216216
* @return the boolean
217217
*/
218-
private static boolean isSwaggerPrimitiveType(Class<?> clazz) {
218+
public static boolean isSwaggerPrimitiveType(Class<?> clazz) {
219219
PrimitiveType primitiveType = PrimitiveType.fromType(clazz);
220220
return primitiveType != null;
221221
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import org.springframework.context.annotation.Configuration;
8686
import org.springframework.context.annotation.Lazy;
8787
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
88+
import org.springframework.core.convert.support.GenericConversionService;
8889
import org.springframework.data.domain.Pageable;
8990
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
9091
import org.springframework.http.HttpStatus;
@@ -473,13 +474,13 @@ static class WebConversionServiceConfiguration {
473474
/**
474475
* Web conversion service provider web conversion service provider.
475476
*
476-
* @param webConversionServiceOptional the web conversion service optional
477+
* @param mvcConversionService the web conversion service optional
477478
* @return the web conversion service provider
478479
*/
479480
@Bean
480481
@Lazy(false)
481-
WebConversionServiceProvider webConversionServiceProvider(Optional<WebConversionService> webConversionServiceOptional) {
482-
return new WebConversionServiceProvider(webConversionServiceOptional);
482+
WebConversionServiceProvider webConversionServiceProvider(Optional<GenericConversionService> mvcConversionService) {
483+
return new WebConversionServiceProvider(mvcConversionService);
483484
}
484485
}
485486

springdoc-openapi-common/src/main/java/org/springdoc/core/providers/WebConversionServiceProvider.java

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package org.springdoc.core.providers;
22

3+
import java.lang.reflect.Field;
4+
import java.util.Map;
35
import java.util.Optional;
46

5-
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+
611
import org.springframework.core.convert.TypeDescriptor;
12+
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
13+
import org.springframework.core.convert.support.GenericConversionService;
714
import org.springframework.format.support.DefaultFormattingConversionService;
8-
import org.springframework.format.support.FormattingConversionService;
915
import org.springframework.lang.Nullable;
1016

1117
/**
@@ -14,17 +20,23 @@
1420
*/
1521
public class WebConversionServiceProvider {
1622

23+
private static final String CONVERTERS = "converters";
24+
25+
/**
26+
* The constant LOGGER.
27+
*/
28+
private static final Logger LOGGER = LoggerFactory.getLogger(WebConversionServiceProvider.class);
1729
/**
1830
* The Formatting conversion service.
1931
*/
20-
private final FormattingConversionService formattingConversionService;
32+
private final GenericConversionService formattingConversionService;
2133

2234
/**
2335
* Instantiates a new Web conversion service provider.
2436
*
2537
* @param webConversionServiceOptional the web conversion service optional
2638
*/
27-
public WebConversionServiceProvider(Optional<WebConversionService> webConversionServiceOptional) {
39+
public WebConversionServiceProvider(Optional<GenericConversionService> webConversionServiceOptional) {
2840
if (webConversionServiceOptional.isPresent())
2941
this.formattingConversionService = webConversionServiceOptional.get();
3042
else
@@ -40,6 +52,32 @@ public WebConversionServiceProvider(Optional<WebConversionService> webConversion
4052
*/
4153
@Nullable
4254
public Object convert(@Nullable Object source, TypeDescriptor targetTypeDescriptor) {
43-
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;
4482
}
4583
}
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+
}
Lines changed: 29 additions & 0 deletions
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)