Skip to content

Commit e2eec8b

Browse files
committed
Add SpEL support for default values. Fixes #1317
1 parent 54fdaf8 commit e2eec8b

File tree

6 files changed

+231
-12
lines changed

6 files changed

+231
-12
lines changed

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

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@
5151
import org.slf4j.LoggerFactory;
5252
import org.springdoc.core.customizers.DelegatingMethodParameterCustomizer;
5353

54+
import org.springframework.beans.factory.config.BeanExpressionContext;
55+
import org.springframework.beans.factory.config.BeanExpressionResolver;
56+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
5457
import org.springframework.core.MethodParameter;
5558
import org.springframework.core.ResolvableType;
5659
import org.springframework.core.io.Resource;
60+
import org.springframework.web.context.request.RequestScope;
5761
import org.springframework.web.multipart.MultipartFile;
5862

5963
/**
@@ -93,6 +97,16 @@ public class GenericParameterService {
9397
*/
9498
private final PropertyResolverUtils propertyResolverUtils;
9599

100+
/**
101+
* The Expression context.
102+
*/
103+
private BeanExpressionContext expressionContext;
104+
105+
/**
106+
* The Configurable bean factory.
107+
*/
108+
private ConfigurableBeanFactory configurableBeanFactory;
109+
96110
/**
97111
* Instantiates a new Generic parameter builder.
98112
* @param propertyResolverUtils the property resolver utils
@@ -103,6 +117,8 @@ public GenericParameterService(PropertyResolverUtils propertyResolverUtils, Opti
103117
this.propertyResolverUtils = propertyResolverUtils;
104118
this.optionalDelegatingMethodParameterCustomizer = optionalDelegatingMethodParameterCustomizer;
105119
this.optionalWebConversionServiceProvider = optionalWebConversionServiceProvider;
120+
this.configurableBeanFactory = propertyResolverUtils.getFactory();
121+
this.expressionContext = (configurableBeanFactory != null ? new BeanExpressionContext(configurableBeanFactory, new RequestScope()) : null);
106122
}
107123

108124
/**
@@ -206,9 +222,9 @@ public Parameter buildParameterFromDoc(io.swagger.v3.oas.annotations.Parameter p
206222
Components components, JsonView jsonView, Locale locale) {
207223
Parameter parameter = new Parameter();
208224
if (StringUtils.isNotBlank(parameterDoc.description()))
209-
parameter.setDescription(propertyResolverUtils.resolve(parameterDoc.description(),locale));
225+
parameter.setDescription(propertyResolverUtils.resolve(parameterDoc.description(), locale));
210226
if (StringUtils.isNotBlank(parameterDoc.name()))
211-
parameter.setName(propertyResolverUtils.resolve(parameterDoc.name(),locale));
227+
parameter.setName(propertyResolverUtils.resolve(parameterDoc.name(), locale));
212228
if (StringUtils.isNotBlank(parameterDoc.in().toString()))
213229
parameter.setIn(parameterDoc.in().toString());
214230
if (StringUtils.isNotBlank(parameterDoc.example())) {
@@ -272,7 +288,7 @@ private void setSchema(io.swagger.v3.oas.annotations.Parameter parameterDoc, Com
272288
LOGGER.warn(Constants.GRACEFUL_EXCEPTION_OCCURRED, e);
273289
}
274290
if (schema == null) {
275-
schema =AnnotationsUtils.getSchema(parameterDoc.schema(), parameterDoc.array(), true, parameterDoc.array().schema().implementation(), components, jsonView).orElse(null);
291+
schema = AnnotationsUtils.getSchema(parameterDoc.schema(), parameterDoc.array(), true, parameterDoc.array().schema().implementation(), components, jsonView).orElse(null);
276292
// default value not set by swagger-core for array !
277293
if (schema != null) {
278294
Object defaultValue = SpringDocAnnotationsUtils.resolveDefaultValue(parameterDoc.array().arraySchema().defaultValue());
@@ -345,7 +361,7 @@ else if (parameterInfo.isRequestPart() || schemaN instanceof FileSchema || schem
345361
else
346362
requestBodyInfo.addProperties(paramName, schemaN);
347363

348-
if(requestBodyInfo.getMergedSchema() !=null && parameterInfo.isRequired())
364+
if (requestBodyInfo.getMergedSchema() != null && parameterInfo.isRequired())
349365
requestBodyInfo.getMergedSchema().addRequiredItem(parameterInfo.getpName());
350366

351367
return schemaN;
@@ -488,7 +504,28 @@ public PropertyResolverUtils getPropertyResolverUtils() {
488504
return propertyResolverUtils;
489505
}
490506

507+
/**
508+
* Gets optional web conversion service provider.
509+
*
510+
* @return the optional web conversion service provider
511+
*/
491512
public Optional<WebConversionServiceProvider> getOptionalWebConversionServiceProvider() {
492513
return optionalWebConversionServiceProvider;
493514
}
515+
516+
/**
517+
* Resolve the given annotation-specified value,
518+
* potentially containing placeholders and expressions.
519+
*/
520+
public Object resolveEmbeddedValuesAndExpressions(String value) {
521+
if (this.configurableBeanFactory == null || this.expressionContext == null) {
522+
return value;
523+
}
524+
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
525+
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
526+
if (exprResolver == null) {
527+
return value;
528+
}
529+
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
530+
}
494531
}

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,31 @@ public class ParameterInfo {
7777
*/
7878
private boolean requestPart;
7979

80-
private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInfo.class);
80+
/**
81+
* The Generic parameter service.
82+
*/
83+
private GenericParameterService genericParameterService;
8184

85+
/**
86+
* The constant LOGGER.
87+
*/
88+
private static final Logger LOGGER = LoggerFactory.getLogger(ParameterInfo.class);
8289

8390
/**
8491
* Instantiates a new Parameter info.
8592
* @param pName the parameter name
8693
* @param methodParameter the method parameter
87-
* @param parameterBuilder the parameter builder
94+
* @param genericParameterService the parameter builder
8895
* @param locale the locale
8996
*/
90-
public ParameterInfo(String pName, MethodParameter methodParameter, GenericParameterService parameterBuilder, Locale locale) {
91-
PropertyResolverUtils propertyResolverUtils = parameterBuilder.getPropertyResolverUtils();
97+
public ParameterInfo(String pName, MethodParameter methodParameter, GenericParameterService genericParameterService, Locale locale) {
98+
this.genericParameterService = genericParameterService;
99+
PropertyResolverUtils propertyResolverUtils = genericParameterService.getPropertyResolverUtils();
92100
RequestHeader requestHeader = methodParameter.getParameterAnnotation(RequestHeader.class);
93101
RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
94102
PathVariable pathVar = methodParameter.getParameterAnnotation(PathVariable.class);
95103
CookieValue cookieValue = methodParameter.getParameterAnnotation(CookieValue.class);
96-
boolean isFile = parameterBuilder.isFile(methodParameter);
104+
boolean isFile = genericParameterService.isFile(methodParameter);
97105

98106
this.methodParameter = methodParameter;
99107
this.pName = pName;
@@ -108,10 +116,10 @@ else if (cookieValue != null)
108116
calculateParams(cookieValue);
109117

110118
if (StringUtils.isNotBlank(this.pName))
111-
this.pName = propertyResolverUtils.resolve(this.pName, locale);
119+
this.pName = genericParameterService.resolveEmbeddedValuesAndExpressions(this.pName).toString();
112120
if (this.defaultValue != null && !ValueConstants.DEFAULT_NONE.equals(this.defaultValue.toString())) {
113-
this.defaultValue = propertyResolverUtils.resolve(this.defaultValue.toString(), locale);
114-
parameterBuilder.getOptionalWebConversionServiceProvider()
121+
this.defaultValue = genericParameterService.resolveEmbeddedValuesAndExpressions(this.defaultValue.toString());
122+
genericParameterService.getOptionalWebConversionServiceProvider()
115123
.ifPresent(conversionService -> {
116124
try {
117125
this.defaultValue = conversionService.convert(this.defaultValue, new TypeDescriptor(methodParameter));

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,13 @@ public String resolve(String parameterProperty, Locale locale) {
8383
}
8484
return parameterProperty;
8585
}
86+
87+
/**
88+
* Gets factory.
89+
*
90+
* @return the factory
91+
*/
92+
public ConfigurableBeanFactory getFactory() {
93+
return factory;
94+
}
8695
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package test.org.springdoc.api.app165;
2+
3+
import java.util.List;
4+
5+
import io.swagger.v3.oas.annotations.Operation;
6+
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.PathVariable;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
/**
15+
* @author bnasslahsen
16+
*/
17+
@RestController
18+
@RequestMapping("/api")
19+
public class HelloController {
20+
21+
@Operation(description = "I want here some custom config")
22+
@GetMapping("/sample1/{springdoc}")
23+
public ResponseEntity sample1(@PathVariable(name = "#{T(org.springdoc.core.Constants).SPRINGDOC_PREFIX}") String id) {
24+
throw new UnsupportedOperationException("the body is not relevant now");
25+
}
26+
27+
@Operation(description = "I want here another some custom config")
28+
@GetMapping("/sample2")
29+
public ResponseEntity sample2(@RequestParam(defaultValue = "#{{}}") List<String> value) {
30+
throw new UnsupportedOperationException("the body is not relevant now");
31+
}
32+
33+
@Operation(description = "I want here another some custom config")
34+
@GetMapping("/sample3")
35+
public ResponseEntity sample3(@RequestParam(defaultValue = "#{T(org.springdoc.core.Constants).DEFAULT_SWAGGER_UI_PATH}") String id) {
36+
throw new UnsupportedOperationException("the body is not relevant now");
37+
}
38+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package test.org.springdoc.api.app165;
2+
3+
import test.org.springdoc.api.AbstractSpringDocTest;
4+
5+
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
7+
public class SpringDocApp165Test extends AbstractSpringDocTest {
8+
9+
@SpringBootApplication
10+
static class SpringDocTestApp {}
11+
12+
13+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
"/api/sample3": {
15+
"get": {
16+
"tags": [
17+
"hello-controller"
18+
],
19+
"description": "I want here another some custom config",
20+
"operationId": "sample3",
21+
"parameters": [
22+
{
23+
"name": "id",
24+
"in": "query",
25+
"required": false,
26+
"schema": {
27+
"type": "string",
28+
"default": "/swagger-ui.html"
29+
}
30+
}
31+
],
32+
"responses": {
33+
"200": {
34+
"description": "OK",
35+
"content": {
36+
"*/*": {
37+
"schema": {
38+
"type": "string"
39+
}
40+
}
41+
}
42+
}
43+
}
44+
}
45+
},
46+
"/api/sample2": {
47+
"get": {
48+
"tags": [
49+
"hello-controller"
50+
],
51+
"description": "I want here another some custom config",
52+
"operationId": "sample2",
53+
"parameters": [
54+
{
55+
"name": "value",
56+
"in": "query",
57+
"required": false,
58+
"schema": {
59+
"type": "array",
60+
"items": {
61+
"type": "string"
62+
},
63+
"default": []
64+
}
65+
}
66+
],
67+
"responses": {
68+
"200": {
69+
"description": "OK",
70+
"content": {
71+
"*/*": {
72+
"schema": {
73+
"type": "string"
74+
}
75+
}
76+
}
77+
}
78+
}
79+
}
80+
},
81+
"/api/sample1/{springdoc}": {
82+
"get": {
83+
"tags": [
84+
"hello-controller"
85+
],
86+
"description": "I want here some custom config",
87+
"operationId": "sample1",
88+
"parameters": [
89+
{
90+
"name": "springdoc",
91+
"in": "path",
92+
"required": true,
93+
"schema": {
94+
"type": "string"
95+
}
96+
}
97+
],
98+
"responses": {
99+
"200": {
100+
"description": "OK",
101+
"content": {
102+
"*/*": {
103+
"schema": {
104+
"type": "string"
105+
}
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
},
113+
"components": {}
114+
}

0 commit comments

Comments
 (0)