Add exception and analyzer for mutually exclusive config props
Add `MutuallyExclusiveConfigurationPropertiesException` and a related failure analyzer so that a nice message can be displayed if more than one mutually exclusive property is defined. Closes gh-28121 Co-authored-by: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
parent
528ced4f0d
commit
5e426394db
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.properties.source;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Exception thrown when more than one mutually exclusive configuration property has been
|
||||
* configured.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.6.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MutuallyExclusiveConfigurationPropertiesException extends RuntimeException {
|
||||
|
||||
private final Set<String> configuredNames;
|
||||
|
||||
private final Set<String> mutuallyExclusiveNames;
|
||||
|
||||
/**
|
||||
* Creates a new instance for mutually exclusive configuration properties when two or
|
||||
* more of those properties have been configured.
|
||||
* @param configuredNames the names of the properties that have been configured
|
||||
* @param mutuallyExclusiveNames the names of the properties that are mutually
|
||||
* exclusive
|
||||
*/
|
||||
public MutuallyExclusiveConfigurationPropertiesException(Collection<String> configuredNames,
|
||||
Collection<String> mutuallyExclusiveNames) {
|
||||
this(asSet(configuredNames), asSet(mutuallyExclusiveNames));
|
||||
}
|
||||
|
||||
private MutuallyExclusiveConfigurationPropertiesException(Set<String> configuredNames,
|
||||
Set<String> mutuallyExclusiveNames) {
|
||||
super(buildMessage(mutuallyExclusiveNames, configuredNames));
|
||||
this.configuredNames = configuredNames;
|
||||
this.mutuallyExclusiveNames = mutuallyExclusiveNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of the properties that have been configured.
|
||||
* @return the names of the configured properties
|
||||
*/
|
||||
public Set<String> getConfiguredNames() {
|
||||
return this.configuredNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of the properties that are mutually exclusive.
|
||||
* @return the names of the mutually exclusive properties
|
||||
*/
|
||||
public Set<String> getMutuallyExclusiveNames() {
|
||||
return this.mutuallyExclusiveNames;
|
||||
}
|
||||
|
||||
private static Set<String> asSet(Collection<String> collection) {
|
||||
return (collection != null) ? new LinkedHashSet<>(collection) : null;
|
||||
}
|
||||
|
||||
private static String buildMessage(Set<String> mutuallyExclusiveNames, Set<String> configuredNames) {
|
||||
Assert.isTrue(configuredNames != null && configuredNames.size() > 1,
|
||||
"ConfiguredNames must contain 2 or more names");
|
||||
Assert.isTrue(mutuallyExclusiveNames != null && mutuallyExclusiveNames.size() > 1,
|
||||
"MutuallyExclusiveNames must contain 2 or more names");
|
||||
return "The configuration properties '" + String.join(", ", mutuallyExclusiveNames)
|
||||
+ "' are mutually exclusive and '" + String.join(", ", configuredNames)
|
||||
+ "' have been configured together";
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a new {@link MutuallyExclusiveConfigurationPropertiesException} if multiple
|
||||
* non-null values are defined in a set of entries.
|
||||
* @param entries a consumer used to populate the entries to check
|
||||
*/
|
||||
public static void throwIfMultipleNonNullValuesIn(Consumer<Map<String, Object>> entries) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
entries.accept(map);
|
||||
Set<String> configuredNames = map.entrySet().stream().filter((entry) -> entry.getValue() != null)
|
||||
.map(Map.Entry::getKey).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
if (configuredNames.size() > 1) {
|
||||
throw new MutuallyExclusiveConfigurationPropertiesException(configuredNames, map.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.diagnostics.analyzer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
|
||||
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||
import org.springframework.boot.diagnostics.FailureAnalyzer;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.boot.origin.OriginLookup;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
|
||||
/**
|
||||
* A {@link FailureAnalyzer} that performs analysis of failures caused by an
|
||||
* {@link MutuallyExclusiveConfigurationPropertiesException}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class MutuallyExclusiveConfigurationPropertiesFailureAnalyzer
|
||||
extends AbstractFailureAnalyzer<MutuallyExclusiveConfigurationPropertiesException> implements EnvironmentAware {
|
||||
|
||||
private ConfigurableEnvironment environment;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = (ConfigurableEnvironment) environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FailureAnalysis analyze(Throwable rootFailure, MutuallyExclusiveConfigurationPropertiesException cause) {
|
||||
List<Descriptor> descriptors = new ArrayList<>();
|
||||
for (String name : cause.getConfiguredNames()) {
|
||||
List<Descriptor> descriptorsForName = getDescriptors(name);
|
||||
if (descriptorsForName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
descriptors.addAll(descriptorsForName);
|
||||
}
|
||||
StringBuilder description = new StringBuilder();
|
||||
appendDetails(description, cause, descriptors);
|
||||
return new FailureAnalysis(description.toString(),
|
||||
"Update your configuration so that only one of the mutually exclusive properties is configured.",
|
||||
cause);
|
||||
}
|
||||
|
||||
private List<Descriptor> getDescriptors(String propertyName) {
|
||||
return getPropertySources().filter((source) -> source.containsProperty(propertyName))
|
||||
.map((source) -> Descriptor.get(source, propertyName)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Stream<PropertySource<?>> getPropertySources() {
|
||||
if (this.environment == null) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return this.environment.getPropertySources().stream()
|
||||
.filter((source) -> !ConfigurationPropertySources.isAttachedConfigurationPropertySource(source));
|
||||
}
|
||||
|
||||
private void appendDetails(StringBuilder message, MutuallyExclusiveConfigurationPropertiesException cause,
|
||||
List<Descriptor> descriptors) {
|
||||
descriptors.sort((d1, d2) -> d1.propertyName.compareTo(d2.propertyName));
|
||||
message.append(String.format("The following configuration properties are mutually exclusive:%n%n"));
|
||||
sortedStrings(cause.getMutuallyExclusiveNames())
|
||||
.forEach((name) -> message.append(String.format("\t%s%n", name)));
|
||||
message.append(String.format("%n"));
|
||||
message.append(
|
||||
String.format("However, more than one of those properties has been configured at the same time:%n%n"));
|
||||
Set<String> configuredDescriptions = sortedStrings(descriptors,
|
||||
(descriptor) -> String.format("\t%s%s%n", descriptor.propertyName,
|
||||
(descriptor.origin != null) ? " (originating from '" + descriptor.origin + "')" : ""));
|
||||
configuredDescriptions.forEach(message::append);
|
||||
}
|
||||
|
||||
private <S> Set<String> sortedStrings(Collection<String> input) {
|
||||
return sortedStrings(input, Function.identity());
|
||||
}
|
||||
|
||||
private <S> Set<String> sortedStrings(Collection<S> input, Function<S, String> converter) {
|
||||
TreeSet<String> results = new TreeSet<>();
|
||||
for (S item : input) {
|
||||
results.add(converter.apply(item));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static final class Descriptor {
|
||||
|
||||
private final String propertyName;
|
||||
|
||||
private final Origin origin;
|
||||
|
||||
private Descriptor(String propertyName, Origin origin) {
|
||||
this.propertyName = propertyName;
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
static Descriptor get(PropertySource<?> source, String propertyName) {
|
||||
Origin origin = OriginLookup.getOrigin(source, propertyName);
|
||||
return new Descriptor(propertyName, origin);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -66,6 +66,7 @@ org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
|
|||
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.MutuallyExclusiveConfigurationPropertiesFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
|
||||
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.properties.source;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link MutuallyExclusiveConfigurationPropertiesException}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class MutuallyExclusiveConfigurationPropertiesExceptionTests {
|
||||
|
||||
@Test
|
||||
void createWhenConfiguredNamesIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MutuallyExclusiveConfigurationPropertiesException(null, Arrays.asList("a", "b")))
|
||||
.withMessage("ConfiguredNames must contain 2 or more names");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenConfiguredNamesContainsOneElementThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MutuallyExclusiveConfigurationPropertiesException(Collections.singleton("a"),
|
||||
Arrays.asList("a", "b")))
|
||||
.withMessage("ConfiguredNames must contain 2 or more names");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenMutuallyExclusiveNamesIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MutuallyExclusiveConfigurationPropertiesException(Arrays.asList("a", "b"), null))
|
||||
.withMessage("MutuallyExclusiveNames must contain 2 or more names");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenMutuallyExclusiveNamesContainsOneElementThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MutuallyExclusiveConfigurationPropertiesException(Arrays.asList("a", "b"),
|
||||
Collections.singleton("a")))
|
||||
.withMessage("MutuallyExclusiveNames must contain 2 or more names");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createBuildsSensibleMessage() {
|
||||
List<String> names = Arrays.asList("a", "b");
|
||||
assertThat(new MutuallyExclusiveConfigurationPropertiesException(names, names))
|
||||
.hasMessage("The configuration properties 'a, b' are mutually exclusive "
|
||||
+ "and 'a, b' have been configured together");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getConfiguredNamesReturnsConfiguredNames() {
|
||||
List<String> configuredNames = Arrays.asList("a", "b");
|
||||
List<String> mutuallyExclusiveNames = Arrays.asList("a", "b", "c");
|
||||
MutuallyExclusiveConfigurationPropertiesException exception = new MutuallyExclusiveConfigurationPropertiesException(
|
||||
configuredNames, mutuallyExclusiveNames);
|
||||
assertThat(exception.getConfiguredNames()).hasSameElementsAs(configuredNames);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMutuallyExclusiveNamesReturnsMutuallyExclusiveNames() {
|
||||
List<String> configuredNames = Arrays.asList("a", "b");
|
||||
List<String> mutuallyExclusiveNames = Arrays.asList("a", "b", "c");
|
||||
MutuallyExclusiveConfigurationPropertiesException exception = new MutuallyExclusiveConfigurationPropertiesException(
|
||||
configuredNames, mutuallyExclusiveNames);
|
||||
assertThat(exception.getMutuallyExclusiveNames()).hasSameElementsAs(mutuallyExclusiveNames);
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwIfMultipleNonNullValuesInWhenEntriesHasAllNullsDoesNotThrowException() {
|
||||
assertThatNoException().isThrownBy(
|
||||
() -> MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> {
|
||||
entries.put("a", null);
|
||||
entries.put("b", null);
|
||||
entries.put("c", null);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwIfMultipleNonNullValuesInWhenEntriesHasSingleNonNullDoesNotThrowException() {
|
||||
assertThatNoException().isThrownBy(
|
||||
() -> MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> {
|
||||
entries.put("a", null);
|
||||
entries.put("b", "B");
|
||||
entries.put("c", null);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwIfMultipleNonNullValuesInWhenEntriesHasTwoNonNullsThrowsException() {
|
||||
assertThatExceptionOfType(MutuallyExclusiveConfigurationPropertiesException.class).isThrownBy(
|
||||
() -> MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> {
|
||||
entries.put("a", "a");
|
||||
entries.put("b", "B");
|
||||
entries.put("c", null);
|
||||
})).satisfies((ex) -> {
|
||||
assertThat(ex.getConfiguredNames()).containsExactly("a", "b");
|
||||
assertThat(ex.getMutuallyExclusiveNames()).containsExactly("a", "b", "c");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright 2012-2021 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.diagnostics.analyzer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.boot.origin.OriginLookup;
|
||||
import org.springframework.core.env.EnumerablePropertySource;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link MutuallyExclusiveConfigurationPropertiesFailureAnalyzer}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class MutuallyExclusiveConfigurationPropertiesFailureAnalyzerTests {
|
||||
|
||||
private final MockEnvironment environment = new MockEnvironment();
|
||||
|
||||
@Test
|
||||
void analyzeWhenEnvironmentIsNullShouldReturnNull() {
|
||||
MutuallyExclusiveConfigurationPropertiesException failure = new MutuallyExclusiveConfigurationPropertiesException(
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")),
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")));
|
||||
FailureAnalysis failureAnalysis = new MutuallyExclusiveConfigurationPropertiesFailureAnalyzer()
|
||||
.analyze(failure);
|
||||
assertThat(failureAnalysis).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWhenNotAllPropertiesAreInTheEnvironmentShouldReturnNull() {
|
||||
MapPropertySource source = new MapPropertySource("test", Collections.singletonMap("com.example.a", "alpha"));
|
||||
this.environment.getPropertySources().addFirst(OriginCapablePropertySource.get(source));
|
||||
MutuallyExclusiveConfigurationPropertiesException failure = new MutuallyExclusiveConfigurationPropertiesException(
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")),
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")));
|
||||
FailureAnalysis analysis = performAnalysis(failure);
|
||||
assertThat(analysis).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWhenAllConfiguredPropertiesAreInTheEnvironmentShouldReturnAnalysis() {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("com.example.a", "alpha");
|
||||
properties.put("com.example.b", "bravo");
|
||||
MapPropertySource source = new MapPropertySource("test", properties);
|
||||
this.environment.getPropertySources().addFirst(OriginCapablePropertySource.get(source));
|
||||
MutuallyExclusiveConfigurationPropertiesException failure = new MutuallyExclusiveConfigurationPropertiesException(
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")),
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")));
|
||||
FailureAnalysis analysis = performAnalysis(failure);
|
||||
assertThat(analysis.getAction()).isEqualTo(
|
||||
"Update your configuration so that only one of the mutually exclusive properties is configured.");
|
||||
assertThat(analysis.getDescription()).contains(String.format(
|
||||
"The following configuration properties are mutually exclusive:%n%n\tcom.example.a%n\tcom.example.b%n"))
|
||||
.contains(String
|
||||
.format("However, more than one of those properties has been configured at the same time:%n%n"
|
||||
+ "\tcom.example.a (originating from 'TestOrigin test')%n"
|
||||
+ "\tcom.example.b (originating from 'TestOrigin test')%n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeWhenPropertyIsInMultiplePropertySourcesShouldListEachSourceInAnalysis() {
|
||||
Map<String, Object> properties = new LinkedHashMap<>();
|
||||
properties.put("com.example.a", "alpha");
|
||||
properties.put("com.example.b", "bravo");
|
||||
this.environment.getPropertySources()
|
||||
.addFirst(OriginCapablePropertySource.get(new MapPropertySource("test-one", properties)));
|
||||
this.environment.getPropertySources()
|
||||
.addLast(OriginCapablePropertySource.get(new MapPropertySource("test-two", properties)));
|
||||
MutuallyExclusiveConfigurationPropertiesException failure = new MutuallyExclusiveConfigurationPropertiesException(
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")),
|
||||
new HashSet<>(Arrays.asList("com.example.a", "com.example.b")));
|
||||
FailureAnalysis analysis = performAnalysis(failure);
|
||||
assertThat(analysis.getAction()).isEqualTo(
|
||||
"Update your configuration so that only one of the mutually exclusive properties is configured.");
|
||||
assertThat(analysis.getDescription()).contains(String.format(
|
||||
"The following configuration properties are mutually exclusive:%n%n\tcom.example.a%n\tcom.example.b%n"))
|
||||
.contains(String
|
||||
.format("However, more than one of those properties has been configured at the same time:%n%n"
|
||||
+ "\tcom.example.a (originating from 'TestOrigin test-one')%n"
|
||||
+ "\tcom.example.a (originating from 'TestOrigin test-two')%n"
|
||||
+ "\tcom.example.b (originating from 'TestOrigin test-one')%n"
|
||||
+ "\tcom.example.b (originating from 'TestOrigin test-two')%n"));
|
||||
}
|
||||
|
||||
private FailureAnalysis performAnalysis(MutuallyExclusiveConfigurationPropertiesException failure) {
|
||||
MutuallyExclusiveConfigurationPropertiesFailureAnalyzer analyzer = new MutuallyExclusiveConfigurationPropertiesFailureAnalyzer();
|
||||
analyzer.setEnvironment(this.environment);
|
||||
return analyzer.analyze(failure);
|
||||
}
|
||||
|
||||
static class OriginCapablePropertySource<T> extends EnumerablePropertySource<T> implements OriginLookup<String> {
|
||||
|
||||
private final EnumerablePropertySource<T> propertySource;
|
||||
|
||||
OriginCapablePropertySource(EnumerablePropertySource<T> propertySource) {
|
||||
super(propertySource.getName(), propertySource.getSource());
|
||||
this.propertySource = propertySource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return this.propertySource.getProperty(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPropertyNames() {
|
||||
return this.propertySource.getPropertyNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Origin getOrigin(String name) {
|
||||
return new Origin() {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestOrigin " + getName();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
static <T> OriginCapablePropertySource<T> get(EnumerablePropertySource<T> propertySource) {
|
||||
return new OriginCapablePropertySource<>(propertySource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue