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.BindValidationFailureAnalyzer,\
|
||||||
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
|
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
|
||||||
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
|
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
|
||||||
|
org.springframework.boot.diagnostics.analyzer.MutuallyExclusiveConfigurationPropertiesFailureAnalyzer,\
|
||||||
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
|
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
|
||||||
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
|
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
|
||||||
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
|
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