Relax profile naming restrictions and allow opt-out
Rework profile name validation logic so that `.` and `+` and `@` can be used in the names. Also provide an opt-out property that can be set to restore earlier Spring Boot behavior. The commit also include an update to the reference documentation. Fixes gh-45947
This commit is contained in:
parent
302a6e823f
commit
7613594b3a
|
|
@ -59,6 +59,22 @@ This means that you can specify active profiles in `application.properties` and
|
|||
|
||||
TIP: See xref:features/external-config.adoc#features.external-config.order[the "`Externalized Configuration`"] for more details on the order in which property sources are considered.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
By default, profile names in Spring Boot may contain letters, numbers, or permitted characters (`-`, `_`, `.`, `+`, `@`).
|
||||
In addition, they can only start and end with a letter or number.
|
||||
|
||||
This restriction helps to prevent common parsing issues.
|
||||
if, however, you prefer more flexible profile names you can set configprop:spring.profiles.validate[] to `false` in your `application.properties` or `application.yaml` file:
|
||||
|
||||
[configprops,yaml]
|
||||
----
|
||||
spring:
|
||||
profiles:
|
||||
validate: false
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[features.profiles.adding-active-profiles]]
|
||||
|
|
|
|||
|
|
@ -297,14 +297,16 @@ class ConfigDataEnvironment {
|
|||
ConfigurationPropertySource source = contributor.getConfigurationPropertySource();
|
||||
if (source != null && !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES)) {
|
||||
Binder binder = new Binder(Collections.singleton(source), placeholdersResolver);
|
||||
binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> {
|
||||
if (!contributor.isActive(activationContext)) {
|
||||
InactiveConfigDataAccessException.throwIfPropertyFound(contributor, Profiles.INCLUDE_PROFILES);
|
||||
InactiveConfigDataAccessException.throwIfPropertyFound(contributor,
|
||||
Profiles.INCLUDE_PROFILES.append("[0]"));
|
||||
}
|
||||
result.addAll(includes);
|
||||
});
|
||||
binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST, ProfilesValidator.get(binder))
|
||||
.ifBound((includes) -> {
|
||||
if (!contributor.isActive(activationContext)) {
|
||||
InactiveConfigDataAccessException.throwIfPropertyFound(contributor,
|
||||
Profiles.INCLUDE_PROFILES);
|
||||
InactiveConfigDataAccessException.throwIfPropertyFound(contributor,
|
||||
Profiles.INCLUDE_PROFILES.append("[0]"));
|
||||
}
|
||||
result.addAll(includes);
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -78,27 +78,35 @@ public class Profiles implements Iterable<String> {
|
|||
* @param additionalProfiles any additional active profiles
|
||||
*/
|
||||
Profiles(Environment environment, Binder binder, Collection<String> additionalProfiles) {
|
||||
this.groups = binder.bind("spring.profiles.group", STRING_STRINGS_MAP).orElseGet(LinkedMultiValueMap::new);
|
||||
this.activeProfiles = expandProfiles(getActivatedProfiles(environment, binder, additionalProfiles));
|
||||
this.defaultProfiles = expandProfiles(getDefaultProfiles(environment, binder));
|
||||
ProfilesValidator validator = ProfilesValidator.get(binder);
|
||||
if (additionalProfiles != null) {
|
||||
validator.validate(additionalProfiles, () -> "Invalid profile property value found in additional profiles");
|
||||
}
|
||||
this.groups = binder.bind("spring.profiles.group", STRING_STRINGS_MAP, validator)
|
||||
.orElseGet(LinkedMultiValueMap::new);
|
||||
this.activeProfiles = expandProfiles(getActivatedProfiles(environment, binder, validator, additionalProfiles));
|
||||
this.defaultProfiles = expandProfiles(getDefaultProfiles(environment, binder, validator));
|
||||
}
|
||||
|
||||
private List<String> getActivatedProfiles(Environment environment, Binder binder,
|
||||
private List<String> getActivatedProfiles(Environment environment, Binder binder, ProfilesValidator validator,
|
||||
Collection<String> additionalProfiles) {
|
||||
return asUniqueItemList(getProfiles(environment, binder, Type.ACTIVE), additionalProfiles);
|
||||
return asUniqueItemList(getProfiles(environment, binder, validator, Type.ACTIVE), additionalProfiles);
|
||||
}
|
||||
|
||||
private List<String> getDefaultProfiles(Environment environment, Binder binder) {
|
||||
return asUniqueItemList(getProfiles(environment, binder, Type.DEFAULT));
|
||||
private List<String> getDefaultProfiles(Environment environment, Binder binder, ProfilesValidator validator) {
|
||||
return asUniqueItemList(getProfiles(environment, binder, validator, Type.DEFAULT));
|
||||
}
|
||||
|
||||
private Collection<String> getProfiles(Environment environment, Binder binder, Type type) {
|
||||
private Collection<String> getProfiles(Environment environment, Binder binder, ProfilesValidator validator,
|
||||
Type type) {
|
||||
String environmentPropertyValue = environment.getProperty(type.getName());
|
||||
Set<String> environmentPropertyProfiles = (!StringUtils.hasLength(environmentPropertyValue))
|
||||
? Collections.emptySet()
|
||||
: StringUtils.commaDelimitedListToSet(StringUtils.trimAllWhitespace(environmentPropertyValue));
|
||||
validator.validate(environmentPropertyProfiles,
|
||||
() -> "Invalid profile property value found in Envronment under '%s'".formatted(type.getName()));
|
||||
Set<String> environmentProfiles = new LinkedHashSet<>(Arrays.asList(type.get(environment)));
|
||||
BindResult<Set<String>> boundProfiles = binder.bind(type.getName(), STRING_SET);
|
||||
BindResult<Set<String>> boundProfiles = binder.bind(type.getName(), STRING_SET, validator);
|
||||
if (hasProgrammaticallySetProfiles(type, environmentPropertyValue, environmentPropertyProfiles,
|
||||
environmentProfiles)) {
|
||||
if (!type.isMergeWithEnvironmentProfiles() || !boundProfiles.isBound()) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindContext;
|
||||
import org.springframework.boot.context.properties.bind.BindHandler;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link BindHandler} that validates profile names.
|
||||
*
|
||||
* @author Sijun Yang
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
final class ProfilesValidator implements BindHandler {
|
||||
|
||||
private static final String ALLOWED_CHARS = "-_.+@";
|
||||
|
||||
private final boolean validate;
|
||||
|
||||
private ProfilesValidator(boolean validate) {
|
||||
this.validate = validate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
|
||||
validate(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void validate(Object value, Supplier<String> wrappedExceptionMessage) {
|
||||
try {
|
||||
validate(value);
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
throw new IllegalStateException(wrappedExceptionMessage.get(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(Object value) {
|
||||
if (!this.validate) {
|
||||
return;
|
||||
}
|
||||
if (value instanceof Collection<?> list) {
|
||||
list.forEach(this::validate);
|
||||
return;
|
||||
}
|
||||
if (value instanceof Map<?, ?> map) {
|
||||
map.forEach((k, v) -> validate(v));
|
||||
return;
|
||||
}
|
||||
String profile = (value != null) ? value.toString() : null;
|
||||
Assert.state(StringUtils.hasText(profile), "Invalid empty profile");
|
||||
for (int i = 0; i < profile.length(); i++) {
|
||||
int codePoint = profile.codePointAt(i);
|
||||
boolean isAllowedChar = ALLOWED_CHARS.indexOf(codePoint) != -1;
|
||||
Assert.state(isAllowedChar || Character.isLetterOrDigit(codePoint),
|
||||
() -> "Profile '%s' must contain a letter, digit or allowed char (%s)".formatted(profile,
|
||||
Arrays.stream(ALLOWED_CHARS.split("")).collect(Collectors.joining("', '", "'", "'"))));
|
||||
Assert.state((i > 0 && i < profile.length() - 1) || Character.isLetterOrDigit(codePoint),
|
||||
() -> "Profile '%s' must start and end with a letter or digit".formatted(profile));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static ProfilesValidator get(Binder binder) {
|
||||
return new ProfilesValidator(binder.bind("spring.profiles.validate", Boolean.class).orElse(true));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -146,7 +146,6 @@ public class StandardConfigDataLocationResolver
|
|||
@Override
|
||||
public List<StandardConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
|
||||
ConfigDataLocation location, Profiles profiles) {
|
||||
validateProfiles(profiles);
|
||||
return resolve(getProfileSpecificReferences(context, location.split(), profiles));
|
||||
}
|
||||
|
||||
|
|
@ -162,27 +161,6 @@ public class StandardConfigDataLocationResolver
|
|||
return references;
|
||||
}
|
||||
|
||||
private void validateProfiles(Profiles profiles) {
|
||||
for (String profile : profiles) {
|
||||
validateProfile(profile);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProfile(String profile) {
|
||||
Assert.hasText(profile, "'profile' must contain text");
|
||||
Assert.state(!profile.startsWith("-") && !profile.startsWith("_"),
|
||||
() -> String.format("Invalid profile '%s': must not start with '-' or '_'", profile));
|
||||
Assert.state(!profile.endsWith("-") && !profile.endsWith("_"),
|
||||
() -> String.format("Invalid profile '%s': must not end with '-' or '_'", profile));
|
||||
profile.codePoints().forEach((codePoint) -> {
|
||||
if (codePoint == '-' || codePoint == '_' || Character.isLetterOrDigit(codePoint)) {
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
String.format("Invalid profile '%s': must contain only letters, digits, '-', or '_'", profile));
|
||||
});
|
||||
}
|
||||
|
||||
private String getResourceLocation(ConfigDataLocationResolverContext context,
|
||||
ConfigDataLocation configDataLocation) {
|
||||
String resourceLocation = configDataLocation.getNonPrefixedValue(PREFIX);
|
||||
|
|
|
|||
|
|
@ -614,6 +614,13 @@
|
|||
"sourceType": "org.springframework.boot.context.config.Profiles",
|
||||
"description": "Unconditionally activate the specified comma-separated list of profiles (or list of profiles if using YAML)."
|
||||
},
|
||||
{
|
||||
"name": "spring.profiles.validate",
|
||||
"type": "java.lang.Boolean",
|
||||
"sourceType": "org.springframework.boot.context.config.Profiles",
|
||||
"description": "Whether profiles should be validated to ensure sensible names are used.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "spring.reactor.debug-agent.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
|
|
|||
|
|
@ -1242,6 +1242,36 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
|
|||
assertThat(environment.getProperty("v2")).isEqualTo("root-p2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "application.properties", content = """
|
||||
spring.profiles.active=fa!l
|
||||
""")
|
||||
void invalidProfileActivePropertyThrowsException() {
|
||||
assertThatExceptionOfType(BindException.class).isThrownBy(() -> this.application.run())
|
||||
.havingCause()
|
||||
.withMessageContaining("must contain a letter");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "application.properties", content = """
|
||||
spring.profiles.include=fa!l
|
||||
""")
|
||||
void invalidProfileIncludePropertyThrowsException() {
|
||||
assertThatExceptionOfType(BindException.class).isThrownBy(() -> this.application.run())
|
||||
.havingCause()
|
||||
.withMessageContaining("must contain a letter");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithResource(name = "application.properties", content = """
|
||||
spring.profiles.active=p!1
|
||||
spring.profiles.include=p!2
|
||||
spring.profiles.validate=false
|
||||
""")
|
||||
void unvalidatedProfileProperties() {
|
||||
assertThatNoException().isThrownBy(() -> this.application.run());
|
||||
}
|
||||
|
||||
private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
|
||||
return new Condition<>("environment containing property source " + sourceName) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2021 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
@ -31,12 +32,16 @@ import org.springframework.core.env.Environment;
|
|||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link Profiles}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @author Sijun Yang
|
||||
*/
|
||||
class ProfilesTests {
|
||||
|
||||
|
|
@ -418,4 +423,59 @@ class ProfilesTests {
|
|||
assertThat(profiles.getAccepted()).containsExactly("a", "e", "x", "y", "g", "f", "b", "c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validNamesArePermitted() {
|
||||
assertValidName("spring.profiles.active", "ok");
|
||||
assertValidName("spring.profiles.default", "ok");
|
||||
assertValidName("spring.profiles.group.a", "ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidNamesAreNotPermitted() {
|
||||
assertInvalidName("spring.profiles.active", "fa!l");
|
||||
assertInvalidName("spring.profiles.default", "fa!l");
|
||||
assertInvalidName("spring.profiles.group.a", "fa!l");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidNamesWhenValidationDisabledArePermitted() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("spring.profiles.validate", "false");
|
||||
environment.setProperty("spring.profiles.active", "fa!l");
|
||||
Binder binder = Binder.get(environment);
|
||||
Profiles profiles = new Profiles(environment, binder, null);
|
||||
assertThat(profiles.getAccepted()).containsExactly("fa!l");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidNameInEnvironment() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("spring.profiles.active", "fa!l");
|
||||
Binder binder = new Binder();
|
||||
assertThatIllegalStateException().isThrownBy(() -> new Profiles(environment, binder, null))
|
||||
.withMessage("Invalid profile property value found in Envronment under 'spring.profiles.active'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidNameInActive() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
Binder binder = new Binder();
|
||||
assertThatIllegalStateException().isThrownBy(() -> new Profiles(environment, binder, Set.of("fa!l")))
|
||||
.withMessage("Invalid profile property value found in additional profiles");
|
||||
}
|
||||
|
||||
private void assertValidName(String name, String value) {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty(name, value);
|
||||
Binder binder = Binder.get(environment);
|
||||
assertThatNoException().isThrownBy(() -> new Profiles(environment, binder, null));
|
||||
}
|
||||
|
||||
private void assertInvalidName(String name, String value) {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty(name, value);
|
||||
Binder binder = Binder.get(environment);
|
||||
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> new Profiles(environment, binder, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindException;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ProfilesValidator}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sijun Yang
|
||||
*/
|
||||
class ProfilesValidatorTests {
|
||||
|
||||
private static final Bindable<String> STRING = Bindable.of(String.class);
|
||||
|
||||
private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
|
||||
|
||||
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable.mapOf(String.class, String.class);
|
||||
|
||||
@Test
|
||||
void validateWhenValid() {
|
||||
assertValid("test");
|
||||
assertValid("dev-test");
|
||||
assertValid("dev-test_123");
|
||||
assertValid("dev-테스트_123");
|
||||
assertValid("d-e_v-t-.e_@@s+t");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenInvalidThrowsException() {
|
||||
assertInvalid("-dev");
|
||||
assertInvalid("_dev");
|
||||
assertInvalid("+dev");
|
||||
assertInvalid(".dev");
|
||||
assertInvalid("dev_");
|
||||
assertInvalid("dev*test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenInvalidBoundStringThrowsException() {
|
||||
assertInvalid(Map.of("profile", "dev*test"), STRING);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenInvalidBoundCollectionThrowsException() {
|
||||
assertInvalid(Map.of("profile", "dev*test"), STRING_LIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenInvalidBoundCollectionFromIndexedThrowsException() {
|
||||
assertInvalid(Map.of("profile[0]", "ok,", "profile[1]", "dev*test"), STRING_LIST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenInvalidBoundMapFromIndexedThrowsException() {
|
||||
assertInvalid(Map.of("profile.foo", "dev*test"), STRING_STRING_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenInvalidThrowsUsefulExceptionMessage() {
|
||||
assertThatExceptionOfType(BindException.class).isThrownBy(() -> bind(Map.of("profile", "b*d")))
|
||||
.havingCause()
|
||||
.withMessageContaining(
|
||||
"Profile 'b*d' must contain a letter, digit or allowed char ('-', '_', '.', '+', '@')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWhenInvalidStartCharacterThrowsUsefulExceptionMessage() {
|
||||
assertThatExceptionOfType(BindException.class).isThrownBy(() -> bind(Map.of("profile", "_bad")))
|
||||
.havingCause()
|
||||
.withMessageContaining("Profile '_bad' must start and end with a letter or digit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWithWrappedExceptionMessageWhenValid() {
|
||||
assertThatNoException().isThrownBy(() -> ProfilesValidator.get(new Binder()).validate("ok", () -> "context"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateWithWrappedExceptionMessageWhenInvalidThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> ProfilesValidator.get(new Binder()).validate("b*d", () -> "context"))
|
||||
.withMessage("context")
|
||||
.havingCause()
|
||||
.withMessageContaining("must contain a letter");
|
||||
}
|
||||
|
||||
private void assertValid(String value) {
|
||||
assertThatNoException().isThrownBy(() -> bind(Map.of("profile", value)));
|
||||
}
|
||||
|
||||
private void assertInvalid(String value) {
|
||||
assertThatExceptionOfType(BindException.class).isThrownBy(() -> bind(Map.of("profile", value)));
|
||||
}
|
||||
|
||||
private <T> void assertInvalid(Map<String, String> map, Bindable<T> target) {
|
||||
assertThatExceptionOfType(BindException.class).isThrownBy(() -> bind(map, target));
|
||||
}
|
||||
|
||||
private void bind(Map<?, ?> map) {
|
||||
bind(map, STRING);
|
||||
}
|
||||
|
||||
private <T> void bind(Map<?, ?> map, Bindable<T> target) {
|
||||
MapConfigurationPropertySource source = new MapConfigurationPropertySource(map);
|
||||
Binder binder = new Binder(source);
|
||||
ProfilesValidator validator = ProfilesValidator.get(binder);
|
||||
binder.bind("profile", target, validator);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -306,83 +306,6 @@ class StandardConfigDataLocationResolverTests {
|
|||
assertThatNoException().isThrownBy(() -> this.resolver.resolve(this.context, location));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWhenProfileIsValidShouldNotThrowException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("dev-test_123");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWithNonAsciiCharactersShouldNotThrowException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("dev-테스트_123");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWithAdditionalValidProfilesShouldNotThrowException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("dev-test");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, List.of("prod-test", "stage-test"));
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWhenProfileStartsWithDashThrowsException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("-dev");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
|
||||
.withMessageStartingWith("Invalid profile '-dev': must not start with '-' or '_'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWhenProfileStartsWithUnderscoreThrowsException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("_dev");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
|
||||
.withMessageStartingWith("Invalid profile '_dev': must not start with '-' or '_'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWhenProfileEndsWithDashThrowsException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("dev-");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
|
||||
.withMessageStartingWith("Invalid profile 'dev-': must not end with '-' or '_'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWhenProfileEndsWithUnderscoreThrowsException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("dev_");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
|
||||
.withMessageStartingWith("Invalid profile 'dev_': must not end with '-' or '_'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificWhenProfileContainsInvalidCharactersThrowsException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
|
||||
this.environment.setActiveProfiles("dev*test");
|
||||
Profiles profiles = new Profiles(this.environment, this.environmentBinder, Collections.emptyList());
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.resolver.resolveProfileSpecific(this.context, location, profiles))
|
||||
.withMessageStartingWith("Invalid profile 'dev*test': must contain only letters, digits, '-', or '_'");
|
||||
}
|
||||
|
||||
private String filePath(String... components) {
|
||||
return "file [" + String.join(File.separator, components) + "]";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,6 @@
|
|||
test.name=Phil
|
||||
sample.name=Andy
|
||||
|
||||
spring.profiles.validate=false
|
||||
spring.profiles.active=a+very(silly)!name
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue