Commit 11064b5d authored by Andy Wilkinson's avatar Andy Wilkinson

List valid values in failure analysis for enum binding failure

Closes gh-11771
parent cedb6b2f
...@@ -16,12 +16,19 @@ ...@@ -16,12 +16,19 @@
package org.springframework.boot.diagnostics.analyzer; package org.springframework.boot.diagnostics.analyzer;
import java.util.Collection;
import java.util.Collections;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException; import org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException;
import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.bind.validation.BindValidationException;
import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
...@@ -46,7 +53,7 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer<BindException> { ...@@ -46,7 +53,7 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer<BindException> {
private FailureAnalysis analyzeGenericBindException(BindException cause) { private FailureAnalysis analyzeGenericBindException(BindException cause) {
StringBuilder description = new StringBuilder( StringBuilder description = new StringBuilder(
String.format("Binding to target %s failed:%n", cause.getTarget())); String.format("%s:%n", cause.getMessage()));
ConfigurationProperty property = cause.getProperty(); ConfigurationProperty property = cause.getProperty();
buildDescription(description, property); buildDescription(description, property);
description.append(String.format("%n Reason: %s", getMessage(cause))); description.append(String.format("%n Reason: %s", getMessage(cause)));
...@@ -71,8 +78,29 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer<BindException> { ...@@ -71,8 +78,29 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer<BindException> {
} }
private FailureAnalysis getFailureAnalysis(Object description, BindException cause) { private FailureAnalysis getFailureAnalysis(Object description, BindException cause) {
return new FailureAnalysis(description.toString(), StringBuilder message = new StringBuilder(
"Update your application's configuration", cause); "Update your application's configuration");
Collection<String> validValues = findValidValues(cause);
if (!validValues.isEmpty()) {
message.append(String.format(". The following values are valid:%n"));
validValues
.forEach((value) -> message.append(String.format("%n %s", value)));
}
return new FailureAnalysis(description.toString(), message.toString(), cause);
}
private Collection<String> findValidValues(BindException ex) {
ConversionFailedException conversionFailure = findCause(ex,
ConversionFailedException.class);
if (conversionFailure != null) {
Object[] enumConstants = conversionFailure.getTargetType().getType()
.getEnumConstants();
if (enumConstants != null) {
return Stream.of(enumConstants).map(Object::toString)
.collect(Collectors.toCollection(TreeSet::new));
}
}
return Collections.emptySet();
} }
} }
...@@ -19,6 +19,7 @@ package org.springframework.boot.diagnostics.analyzer; ...@@ -19,6 +19,7 @@ package org.springframework.boot.diagnostics.analyzer;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.validation.constraints.Min; import javax.validation.constraints.Min;
...@@ -67,6 +68,15 @@ public class BindFailureAnalyzerTests { ...@@ -67,6 +68,15 @@ public class BindFailureAnalyzerTests {
"Could not resolve placeholder 'BAR' in value \"${BAR}\"")); "Could not resolve placeholder 'BAR' in value \"${BAR}\""));
} }
@Test
public void bindExceptionForUnknownValueInEnumListsValidValuesInAction() {
FailureAnalysis analysis = performAnalysis(EnumFailureConfiguration.class,
"test.foo.fruit=apple,strawberry");
for (Fruit fruit : Fruit.values()) {
assertThat(analysis.getAction()).contains(fruit.name());
}
}
private static String failure(String property, String value, String origin, private static String failure(String property, String value, String origin,
String reason) { String reason) {
return String.format( return String.format(
...@@ -124,6 +134,11 @@ public class BindFailureAnalyzerTests { ...@@ -124,6 +134,11 @@ public class BindFailureAnalyzerTests {
} }
@EnableConfigurationProperties(EnumFailureProperties.class)
static class EnumFailureConfiguration {
}
@ConfigurationProperties("test.foo") @ConfigurationProperties("test.foo")
@Validated @Validated
static class FieldValidationFailureProperties { static class FieldValidationFailureProperties {
...@@ -170,4 +185,25 @@ public class BindFailureAnalyzerTests { ...@@ -170,4 +185,25 @@ public class BindFailureAnalyzerTests {
} }
@ConfigurationProperties("test.foo")
static class EnumFailureProperties {
private Set<Fruit> fruit;
public Set<Fruit> getFruit() {
return this.fruit;
}
public void setFruit(Set<Fruit> fruit) {
this.fruit = fruit;
}
}
enum Fruit {
APPLE, BANANA, ORANGE;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment