From 11064b5d782e0bc61aa97681b7ee48a8b8baf2a4 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 25 Jan 2018 13:04:02 +0000 Subject: [PATCH] List valid values in failure analysis for enum binding failure Closes gh-11771 --- .../analyzer/BindFailureAnalyzer.java | 34 ++++++++++++++++-- .../analyzer/BindFailureAnalyzerTests.java | 36 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java index 0abe42df54..a841baab44 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzer.java @@ -16,12 +16,19 @@ 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.UnboundConfigurationPropertiesException; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.util.StringUtils; /** @@ -46,7 +53,7 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer { private FailureAnalysis analyzeGenericBindException(BindException cause) { StringBuilder description = new StringBuilder( - String.format("Binding to target %s failed:%n", cause.getTarget())); + String.format("%s:%n", cause.getMessage())); ConfigurationProperty property = cause.getProperty(); buildDescription(description, property); description.append(String.format("%n Reason: %s", getMessage(cause))); @@ -71,8 +78,29 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer { } private FailureAnalysis getFailureAnalysis(Object description, BindException cause) { - return new FailureAnalysis(description.toString(), - "Update your application's configuration", cause); + StringBuilder message = new StringBuilder( + "Update your application's configuration"); + Collection 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 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(); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java index e4f1ba4fa6..d2a3c6fb9d 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/BindFailureAnalyzerTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.diagnostics.analyzer; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import javax.validation.constraints.Min; @@ -67,6 +68,15 @@ public class BindFailureAnalyzerTests { "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, String reason) { return String.format( @@ -124,6 +134,11 @@ public class BindFailureAnalyzerTests { } + @EnableConfigurationProperties(EnumFailureProperties.class) + static class EnumFailureConfiguration { + + } + @ConfigurationProperties("test.foo") @Validated static class FieldValidationFailureProperties { @@ -170,4 +185,25 @@ public class BindFailureAnalyzerTests { } + @ConfigurationProperties("test.foo") + static class EnumFailureProperties { + + private Set fruit; + + public Set getFruit() { + return this.fruit; + } + + public void setFruit(Set fruit) { + this.fruit = fruit; + } + + } + + enum Fruit { + + APPLE, BANANA, ORANGE; + + } + }