Skip to content

Commit b043739

Browse files
committed
Merge branch 'tkachenkoas-specification-string-properties-customizer'
2 parents 4673b6a + 22c8eff commit b043739

File tree

12 files changed

+644
-2
lines changed

12 files changed

+644
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
package org.springdoc.core.conditions;
25+
26+
import org.springframework.context.annotation.Condition;
27+
import org.springframework.context.annotation.ConditionContext;
28+
import org.springframework.core.env.ConfigurableEnvironment;
29+
import org.springframework.core.env.EnumerablePropertySource;
30+
import org.springframework.core.env.MutablePropertySources;
31+
import org.springframework.core.env.PropertySource;
32+
import org.springframework.core.type.AnnotatedTypeMetadata;
33+
34+
import static org.springdoc.core.utils.Constants.SPRINGDOC_SPEC_PROPERTIES_PREFIX;
35+
36+
/**
37+
* The type Spec properties condition.
38+
*
39+
* @author bnasslahsen
40+
*/
41+
public class SpecPropertiesCondition implements Condition {
42+
43+
@Override
44+
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
45+
MutablePropertySources propertySources = ((ConfigurableEnvironment) context.getEnvironment())
46+
.getPropertySources();
47+
for (PropertySource<?> propertySource : propertySources) {
48+
if (propertySource instanceof EnumerablePropertySource<?>) {
49+
String[] propertyNames = ((EnumerablePropertySource<?>) propertySource).getPropertyNames();
50+
for (String name : propertyNames) {
51+
if (name.startsWith(SPRINGDOC_SPEC_PROPERTIES_PREFIX)) {
52+
return true;
53+
}
54+
}
55+
}
56+
}
57+
return false;
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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 org.springdoc.core.configuration;
26+
27+
import java.util.List;
28+
29+
import org.springdoc.core.conditions.SpecPropertiesCondition;
30+
import org.springdoc.core.customizers.SpecPropertiesCustomizer;
31+
import org.springdoc.core.models.GroupedOpenApi;
32+
33+
import org.springframework.beans.factory.config.BeanPostProcessor;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
36+
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.annotation.Conditional;
38+
import org.springframework.context.annotation.Configuration;
39+
import org.springframework.context.annotation.Lazy;
40+
import org.springframework.core.env.PropertyResolver;
41+
42+
/**
43+
* The type Spring doc specification string properties configuration.
44+
*
45+
* @author Anton Tkachenko [email protected]
46+
* @author bnasslahsen
47+
*/
48+
@Lazy(false)
49+
@Configuration(proxyBeanMethods = false)
50+
@Conditional(SpecPropertiesCondition.class)
51+
@ConditionalOnBean(SpringDocConfiguration.class)
52+
public class SpringDocSpecPropertiesConfiguration {
53+
54+
/**
55+
* Springdoc customizer that takes care of the specification string properties customization.
56+
* Will be applied to general openapi schema.
57+
*
58+
* @return the springdoc customizer
59+
*/
60+
@Bean
61+
@ConditionalOnMissingBean
62+
@Lazy(false)
63+
SpecPropertiesCustomizer specificationStringPropertiesCustomizer(
64+
PropertyResolver propertyResolverUtils
65+
) {
66+
return new SpecPropertiesCustomizer(propertyResolverUtils);
67+
}
68+
69+
/**
70+
* Bean post processor that applies the specification string properties customization to
71+
* grouped openapi schemas by using group name as a prefix for properties.
72+
*
73+
* @return the bean post processor
74+
*/
75+
@Bean
76+
@ConditionalOnMissingBean
77+
@Lazy(false)
78+
SpecificationStringPropertiesCustomizerBeanPostProcessor specificationStringPropertiesCustomizerBeanPostProcessor(
79+
PropertyResolver propertyResolverUtils
80+
) {
81+
return new SpecificationStringPropertiesCustomizerBeanPostProcessor(propertyResolverUtils);
82+
}
83+
84+
85+
/**
86+
* The type Specification string properties customizer bean post processor.
87+
*/
88+
private static class SpecificationStringPropertiesCustomizerBeanPostProcessor implements BeanPostProcessor {
89+
90+
private final PropertyResolver propertyResolverUtils;
91+
92+
public SpecificationStringPropertiesCustomizerBeanPostProcessor(
93+
PropertyResolver propertyResolverUtils
94+
) {
95+
this.propertyResolverUtils = propertyResolverUtils;
96+
}
97+
98+
@Override
99+
public Object postProcessAfterInitialization(Object bean, String beanName) {
100+
if (bean instanceof GroupedOpenApi groupedOpenApi) {
101+
groupedOpenApi.addAllOpenApiCustomizer(List.of(new SpecPropertiesCustomizer(
102+
propertyResolverUtils, groupedOpenApi.getGroup()
103+
)));
104+
}
105+
return bean;
106+
}
107+
}
108+
109+
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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 org.springdoc.core.customizers;
26+
27+
import io.swagger.v3.oas.models.*;
28+
import io.swagger.v3.oas.models.info.Info;
29+
import io.swagger.v3.oas.models.media.Schema;
30+
import org.apache.commons.lang3.StringUtils;
31+
import org.springframework.core.env.PropertyResolver;
32+
import org.springframework.util.CollectionUtils;
33+
34+
import java.text.MessageFormat;
35+
import java.util.List;
36+
import java.util.Map;
37+
import java.util.function.Consumer;
38+
39+
import static org.springdoc.core.utils.Constants.SPRINGDOC_SPEC_PROPERTIES_PREFIX;
40+
41+
/**
42+
* Allows externalizing strings in generated openapi schema via properties that follow
43+
* conventional naming similar or identical to <a href="https://swagger.io/docs/specification/basic-structure/">openapi schema</a>
44+
* <p>
45+
* To set value of a string in schema, define an application property that matches the target node
46+
* with springdoc.spec-properties prefix.
47+
* <p>
48+
* Sample supported properties for api-info customization:
49+
* <ul>
50+
* <li>springdoc.spec-properties.info.title - to set title of api-info</li>
51+
* <li>springdoc.spec-properties.info.description - to set description of api-info</li>
52+
* <li>springdoc.spec-properties.info.version - to set version of api-info</li>
53+
* <li>springdoc.spec-properties.info.termsOfService - to set terms of service of api-info</li>
54+
* </ul>
55+
* <p>
56+
* Sample supported properties for components customization:
57+
* <ul>
58+
* <li>springdoc.spec-properties.components.User.description - to set description of User model</li>
59+
* <li>springdoc.spec-properties.components.User.properties.name.description - to set description of 'name' property</li>
60+
* <li>springdoc.spec-properties.components.User.properties.name.example - to set example of 'name' property</li>
61+
* </ul>
62+
* <p>
63+
* Sample supported properties for paths/operationIds customization:
64+
* <ul>
65+
* <li>springdoc.spec-properties.paths.{operationId}.description - to set description of {operationId}</li>
66+
* <li>springdoc.spec-properties.paths.{operationId}.summary - to set summary of {operationId}</li>
67+
* </ul>
68+
* <p>
69+
* Support for groped openapi customization is similar to the above, but with a group name prefix.
70+
* E.g.
71+
* <ul>
72+
* <li>springdoc.spec-properties.{group-name}.info.title - to set title of api-info</li>
73+
* <li>springdoc.spec-properties.{group-name}.components.User.description - to set description of User model</li>
74+
* <li>springdoc.spec-properties.{group-name}.paths.{operationId}.description - to set description of {operationId}</li>
75+
* </ul>
76+
*
77+
* @author Anton Tkachenko [email protected]
78+
* @author bnasslahsen
79+
*/
80+
public class SpecPropertiesCustomizer implements GlobalOpenApiCustomizer {
81+
82+
/**
83+
* The Property resolver.
84+
*/
85+
private final PropertyResolver propertyResolver;
86+
87+
/**
88+
* The Property prefix.
89+
*/
90+
private final String propertyPrefix;
91+
92+
/**
93+
* Instantiates a new Spec properties customizer.
94+
*
95+
* @param resolverUtils the resolver utils
96+
*/
97+
public SpecPropertiesCustomizer(PropertyResolver resolverUtils) {
98+
this.propertyResolver = resolverUtils;
99+
this.propertyPrefix = SPRINGDOC_SPEC_PROPERTIES_PREFIX;
100+
}
101+
102+
/**
103+
* Instantiates a new Spec properties customizer.
104+
*
105+
* @param propertyResolver the property resolver
106+
* @param groupName the group name
107+
*/
108+
public SpecPropertiesCustomizer(PropertyResolver propertyResolver, String groupName) {
109+
this.propertyResolver = propertyResolver;
110+
this.propertyPrefix = SPRINGDOC_SPEC_PROPERTIES_PREFIX + groupName + ".";
111+
}
112+
113+
@Override
114+
public void customise(OpenAPI openApi) {
115+
setOperationInfoProperties(openApi);
116+
setComponentsProperties(openApi);
117+
setPathsProperties(openApi);
118+
}
119+
120+
/**
121+
* Sets operation info properties.
122+
*
123+
* @param openApi the open api
124+
*/
125+
private void setOperationInfoProperties(OpenAPI openApi) {
126+
if (openApi.getInfo() == null) {
127+
openApi.setInfo(new Info());
128+
}
129+
Info info = openApi.getInfo();
130+
resolveString(info::setTitle, "info.title");
131+
resolveString(info::setDescription, "info.description");
132+
resolveString(info::setVersion, "info.version");
133+
resolveString(info::setTermsOfService, "info.termsOfService");
134+
}
135+
136+
/**
137+
* Sets paths properties.
138+
*
139+
* @param openApi the open api
140+
*/
141+
private void setPathsProperties(OpenAPI openApi) {
142+
Paths paths = openApi.getPaths();
143+
if (CollectionUtils.isEmpty(paths.values())) {
144+
return;
145+
}
146+
for (PathItem pathItem : paths.values()) {
147+
List<Operation> operations = pathItem.readOperations();
148+
for (Operation operation : operations) {
149+
String operationId = operation.getOperationId();
150+
String operationNode = MessageFormat.format("paths.{0}", operationId);
151+
resolveString(operation::setDescription, operationNode + ".description");
152+
153+
resolveString(operation::setSummary, operationNode + ".summary");
154+
}
155+
}
156+
}
157+
158+
/**
159+
* Sets components properties.
160+
*
161+
* @param openApi the open api
162+
*/
163+
private void setComponentsProperties(OpenAPI openApi) {
164+
Components components = openApi.getComponents();
165+
if (components == null || CollectionUtils.isEmpty(components.getSchemas())) {
166+
return;
167+
}
168+
169+
for (Schema componentSchema : components.getSchemas().values()) {
170+
// set component description
171+
String schemaPropertyPrefix = MessageFormat.format("components.schemas.{0}", componentSchema.getName());
172+
resolveString(componentSchema::setDescription, schemaPropertyPrefix + ".description");
173+
Map<String, Schema> properties = componentSchema.getProperties();
174+
175+
if (CollectionUtils.isEmpty(properties)) {
176+
continue;
177+
}
178+
179+
for (Schema propSchema : properties.values()) {
180+
String propertyNode = MessageFormat.format("components.schemas.{0}.properties.{1}",
181+
componentSchema.getName(), propSchema.getName());
182+
183+
resolveString(propSchema::setDescription, propertyNode + ".description");
184+
resolveString(propSchema::setExample, propertyNode + ".example");
185+
}
186+
}
187+
}
188+
189+
/**
190+
* Resolve string.
191+
*
192+
* @param setter the setter
193+
* @param node the node
194+
*/
195+
private void resolveString(
196+
Consumer<String> setter, String node
197+
) {
198+
String nodeWithPrefix = propertyPrefix + node;
199+
String value = propertyResolver.getProperty(nodeWithPrefix);
200+
if (StringUtils.isNotBlank(value)) {
201+
setter.accept(value);
202+
}
203+
}
204+
205+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public final class Constants {
104104
*/
105105
public static final String SPRINGDOC_SCHEMA_RESOLVE_PROPERTIES = "springdoc.api-docs.resolve-schema-properties";
106106

107+
/**
108+
* The constant SPRINGDOC_SPEC_PROPERTIES_PREFIX.
109+
*/
110+
public static final String SPRINGDOC_SPEC_PROPERTIES_PREFIX = "springdoc.spec-properties.";
111+
107112
/**
108113
* The constant SPRINGDOC_SHOW_LOGIN_ENDPOINT.
109114
*/

springdoc-openapi-starter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ org.springdoc.core.configuration.SpringDocFunctionCatalogConfiguration
77
org.springdoc.core.configuration.SpringDocHateoasConfiguration
88
org.springdoc.core.configuration.SpringDocPageableConfiguration
99
org.springdoc.core.configuration.SpringDocSortConfiguration
10+
org.springdoc.core.configuration.SpringDocSpecPropertiesConfiguration
1011
org.springdoc.core.configuration.SpringDocDataRestConfiguration
1112
org.springdoc.core.configuration.SpringDocKotlinConfiguration
1213
org.springdoc.core.configuration.SpringDocKotlinxConfiguration

0 commit comments

Comments
 (0)