Skip to content

Commit 2bb0c27

Browse files
Provide option to set allowed locales springdoc#2836
1 parent c585c23 commit 2bb0c27

File tree

13 files changed

+697
-9
lines changed

13 files changed

+697
-9
lines changed

Diff for: springdoc-openapi-starter-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ public static void setModelAndViewClass(Class<?> modelAndViewClass) {
333333
* Gets open api.
334334
*/
335335
private void getOpenApi() {
336-
this.getOpenApi(Locale.getDefault());
336+
this.getOpenApi(selectLocale(Locale.getDefault()));
337337
}
338338

339339
/**
@@ -346,7 +346,7 @@ protected OpenAPI getOpenApi(Locale locale) {
346346
this.reentrantLock.lock();
347347
try {
348348
final OpenAPI openAPI;
349-
final Locale finalLocale = locale == null ? Locale.getDefault() : locale;
349+
final Locale finalLocale = selectLocale(locale);
350350
if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) {
351351
Instant start = Instant.now();
352352
openAPI = openAPIService.build(finalLocale);
@@ -422,6 +422,20 @@ protected OpenAPI getOpenApi(Locale locale) {
422422
}
423423
}
424424

425+
private Locale selectLocale(Locale inputLocale) {
426+
List<String> allowedLocales = springDocConfigProperties.getAllowedLocales();
427+
if (!CollectionUtils.isEmpty(allowedLocales)) {
428+
Locale bestMatchingAllowedLocale = Locale.lookup(
429+
Locale.LanguageRange.parse(inputLocale.toLanguageTag()),
430+
allowedLocales.stream().map(Locale::forLanguageTag).collect(Collectors.toList())
431+
);
432+
433+
return bestMatchingAllowedLocale == null ? Locale.forLanguageTag(allowedLocales.get(0)) : bestMatchingAllowedLocale;
434+
}
435+
436+
return inputLocale == null ? Locale.getDefault() : inputLocale;
437+
}
438+
425439
/**
426440
* Indents are removed for properties that are mainly used as “explanations” using Open API.
427441
*
@@ -1361,7 +1375,7 @@ else if (existingOperation != null) {
13611375
* @param locale the locale
13621376
*/
13631377
protected void initOpenAPIBuilder(Locale locale) {
1364-
locale = locale == null ? Locale.getDefault() : locale;
1378+
locale = selectLocale(locale);
13651379
if (openAPIService.getCachedOpenAPI(locale) != null && springDocConfigProperties.isCacheDisabled()) {
13661380
openAPIService = openAPIBuilderObjectFactory.getObject();
13671381
}

Diff for: springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java

+28-6
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,23 @@
2626

2727
package org.springdoc.core.properties;
2828

29-
import java.util.HashSet;
30-
import java.util.List;
31-
import java.util.Objects;
32-
import java.util.Set;
33-
3429
import io.swagger.v3.oas.models.OpenAPI;
3530
import io.swagger.v3.oas.models.SpecVersion;
3631
import org.springdoc.core.configuration.SpringDocConfiguration;
3732
import org.springdoc.core.properties.SpringDocConfigProperties.ApiDocs.OpenApiVersion;
3833
import org.springdoc.core.utils.Constants;
39-
4034
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
4135
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4236
import org.springframework.boot.context.properties.ConfigurationProperties;
4337
import org.springframework.context.annotation.Configuration;
4438
import org.springframework.context.annotation.Lazy;
4539
import org.springframework.http.MediaType;
4640

41+
import java.util.HashSet;
42+
import java.util.List;
43+
import java.util.Objects;
44+
import java.util.Set;
45+
4746
import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL;
4847
import static org.springdoc.core.utils.Constants.SPRINGDOC_ENABLED;
4948

@@ -184,6 +183,11 @@ public class SpringDocConfigProperties {
184183
*/
185184
private boolean useManagementPort;
186185

186+
/**
187+
* Allowed locales for i18n.
188+
*/
189+
private List<String> allowedLocales;
190+
187191
/**
188192
* The Disable i18n.
189193
*/
@@ -985,6 +989,24 @@ public void setWriterWithDefaultPrettyPrinter(boolean writerWithDefaultPrettyPri
985989
this.writerWithDefaultPrettyPrinter = writerWithDefaultPrettyPrinter;
986990
}
987991

992+
/**
993+
* List of allowed locales for i18n.
994+
*
995+
* @return the allowed locales
996+
*/
997+
public List<String> getAllowedLocales() {
998+
return allowedLocales;
999+
}
1000+
1001+
/**
1002+
* Sets allowed locales for i18n.
1003+
*
1004+
* @param allowedLocales the allowed locales
1005+
*/
1006+
public void setAllowedLocales(List<String> allowedLocales) {
1007+
this.allowedLocales = allowedLocales;
1008+
}
1009+
9881010
/**
9891011
* Is disable i 18 n boolean.
9901012
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 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 test.org.springdoc.api.v30.app238;
26+
27+
import io.swagger.v3.oas.annotations.tags.Tag;
28+
import jakarta.validation.Valid;
29+
import jakarta.validation.constraints.NotBlank;
30+
31+
import org.springframework.http.HttpEntity;
32+
import org.springframework.web.bind.annotation.GetMapping;
33+
import org.springframework.web.bind.annotation.RestController;
34+
35+
@RestController
36+
@Tag(name = "greeting", description = "test")
37+
public class HelloLocaleController {
38+
39+
@GetMapping("/persons")
40+
public void persons(@Valid @NotBlank String name) {
41+
}
42+
43+
@GetMapping("/test")
44+
public HttpEntity<String> demo2() {
45+
return null;
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 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 test.org.springdoc.api.v30.app238;
26+
27+
import java.util.Locale;
28+
29+
import org.junit.jupiter.api.Test;
30+
import org.springdoc.core.customizers.OpenApiLocaleCustomizer;
31+
import org.springdoc.core.utils.Constants;
32+
import test.org.springdoc.api.v30.AbstractSpringDocV30Test;
33+
34+
import org.springframework.beans.factory.annotation.Autowired;
35+
import org.springframework.boot.autoconfigure.SpringBootApplication;
36+
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.support.ResourceBundleMessageSource;
38+
import org.springframework.http.HttpHeaders;
39+
import org.springframework.test.context.TestPropertySource;
40+
import org.springframework.test.web.servlet.MvcResult;
41+
42+
import static org.hamcrest.Matchers.is;
43+
import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
44+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
45+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
46+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
47+
48+
@TestPropertySource(properties = "springdoc.allowed-locales=en-US,fr-CA")
49+
public class SpringDocApp238Test extends AbstractSpringDocV30Test {
50+
51+
@Test
52+
@Override
53+
public void testApp() throws Exception {
54+
testApp(Locale.US);
55+
testApp(Locale.CANADA_FRENCH);
56+
// resolves to en-US as Chinese locale is not allowed in properties
57+
testApp(Locale.SIMPLIFIED_CHINESE);
58+
}
59+
60+
private void testApp(Locale locale) throws Exception {
61+
className = getClass().getSimpleName();
62+
String testNumber = className.replaceAll("[^0-9]", "");
63+
MvcResult mockMvcResult =
64+
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL).locale(locale).header(HttpHeaders.ACCEPT_LANGUAGE, locale.toLanguageTag())).andExpect(status().isOk())
65+
.andExpect(jsonPath("$.openapi", is("3.0.1"))).andReturn();
66+
String result = mockMvcResult.getResponse().getContentAsString();
67+
String expected = getContent("results/3.0.1/app" + testNumber + "-" + locale.toLanguageTag() + ".json");
68+
assertEquals(expected, result, true);
69+
}
70+
71+
@SpringBootApplication
72+
static class SpringDocTestApp {
73+
74+
@Autowired
75+
ResourceBundleMessageSource resourceBundleMessageSource;
76+
77+
@Bean
78+
public OpenApiLocaleCustomizer openApiLocaleCustomizer() {
79+
return (openAPI, locale)
80+
-> openAPI.getInfo().title(resourceBundleMessageSource.getMessage("test", null, locale));
81+
}
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 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 test.org.springdoc.api.v31.app238;
26+
27+
import io.swagger.v3.oas.annotations.tags.Tag;
28+
import jakarta.validation.Valid;
29+
import jakarta.validation.constraints.NotBlank;
30+
31+
import org.springframework.http.HttpEntity;
32+
import org.springframework.web.bind.annotation.GetMapping;
33+
import org.springframework.web.bind.annotation.RestController;
34+
35+
@RestController
36+
@Tag(name = "greeting", description = "test")
37+
public class HelloLocaleController {
38+
39+
@GetMapping("/persons")
40+
public void persons(@Valid @NotBlank String name) {
41+
}
42+
43+
@GetMapping("/test")
44+
public HttpEntity<String> demo2() {
45+
return null;
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * *
6+
* * * * * Copyright 2019-2024 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 test.org.springdoc.api.v31.app238;
26+
27+
import java.util.Locale;
28+
29+
import org.junit.jupiter.api.Test;
30+
import org.springdoc.core.customizers.OpenApiLocaleCustomizer;
31+
import org.springdoc.core.utils.Constants;
32+
import test.org.springdoc.api.v31.AbstractSpringDocTest;
33+
34+
import org.springframework.beans.factory.annotation.Autowired;
35+
import org.springframework.boot.autoconfigure.SpringBootApplication;
36+
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.support.ResourceBundleMessageSource;
38+
import org.springframework.http.HttpHeaders;
39+
import org.springframework.test.context.TestPropertySource;
40+
import org.springframework.test.web.servlet.MvcResult;
41+
42+
import static org.hamcrest.Matchers.is;
43+
import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
44+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
45+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
46+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
47+
48+
@TestPropertySource(properties = "springdoc.allowed-locales=en-US,fr-CA")
49+
public class SpringDocApp238Test extends AbstractSpringDocTest {
50+
51+
@Test
52+
@Override
53+
public void testApp() throws Exception {
54+
testApp(Locale.US);
55+
testApp(Locale.CANADA_FRENCH);
56+
// resolves to en-US as Chinese locale is not allowed in properties
57+
testApp(Locale.SIMPLIFIED_CHINESE);
58+
}
59+
60+
private void testApp(Locale locale) throws Exception {
61+
className = getClass().getSimpleName();
62+
String testNumber = className.replaceAll("[^0-9]", "");
63+
MvcResult mockMvcResult =
64+
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL).locale(locale).header(HttpHeaders.ACCEPT_LANGUAGE, locale.toLanguageTag())).andExpect(status().isOk())
65+
.andExpect(jsonPath("$.openapi", is("3.1.0"))).andReturn();
66+
String result = mockMvcResult.getResponse().getContentAsString();
67+
String expected = getContent("results/3.1.0/app" + testNumber + "-" + locale.toLanguageTag() + ".json");
68+
assertEquals(expected, result, true);
69+
}
70+
71+
@SpringBootApplication
72+
static class SpringDocTestApp {
73+
74+
@Autowired
75+
ResourceBundleMessageSource resourceBundleMessageSource;
76+
77+
@Bean
78+
public OpenApiLocaleCustomizer openApiLocaleCustomizer() {
79+
return (openAPI, locale)
80+
-> openAPI.getInfo().title(resourceBundleMessageSource.getMessage("test", null, locale));
81+
}
82+
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test=This is a test message[ZH]

0 commit comments

Comments
 (0)