Improve "reason" message in BindFailureAnalyzer

Update `BindFailureAnalyzer` so that the "Reason" message includes
the root cause exception type and message.

Closes gh-27028
This commit is contained in:
Phillip Webb 2021-06-23 20:51:42 -07:00
parent 0e28e24123
commit ea62967ef4
2 changed files with 46 additions and 8 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -38,6 +38,7 @@ import org.springframework.util.StringUtils;
*
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Phillip Webb
*/
class BindFailureAnalyzer extends AbstractFailureAnalyzer<BindException> {
@ -68,16 +69,33 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer<BindException> {
}
private String getMessage(BindException cause) {
Throwable rootCause = getRootCause(cause.getCause());
ConversionFailedException conversionFailure = findCause(cause, ConversionFailedException.class);
if (conversionFailure != null) {
return "failed to convert " + conversionFailure.getSourceType() + " to "
String message = "failed to convert " + conversionFailure.getSourceType() + " to "
+ conversionFailure.getTargetType();
if (rootCause != null) {
message += " (caused by " + getExceptionTypeAndMessage(rootCause) + ")";
}
return message;
}
Throwable failure = cause;
while (failure.getCause() != null) {
failure = failure.getCause();
if (rootCause != null && StringUtils.hasText(rootCause.getMessage())) {
return getExceptionTypeAndMessage(rootCause);
}
return (StringUtils.hasText(failure.getMessage()) ? failure.getMessage() : cause.getMessage());
return getExceptionTypeAndMessage(cause);
}
private Throwable getRootCause(Throwable cause) {
Throwable rootCause = cause;
while (rootCause != null && rootCause.getCause() != null) {
rootCause = rootCause.getCause();
}
return rootCause;
}
private String getExceptionTypeAndMessage(Throwable ex) {
String message = ex.getMessage();
return ex.getClass().getName() + (StringUtils.hasText(message) ? ": " + message : "");
}
private FailureAnalysis getFailureAnalysis(Object description, BindException cause) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Phillip Webb
*/
class BindFailureAnalyzerTests {
@ -76,7 +77,16 @@ class BindFailureAnalyzerTests {
void bindExceptionWithNestedFailureShouldDisplayNestedMessage() {
FailureAnalysis analysis = performAnalysis(NestedFailureConfiguration.class, "test.foo.value=hello");
assertThat(analysis.getDescription()).contains(failure("test.foo.value", "hello",
"\"test.foo.value\" from property source \"test\"", "This is a failure"));
"\"test.foo.value\" from property source \"test\"", "java.lang.RuntimeException: This is a failure"));
}
@Test // gh-27028
void bindExceptionDueToClassNotFoundConvertionFailure() {
FailureAnalysis analysis = performAnalysis(GenericFailureConfiguration.class,
"test.foo.type=com.example.Missing");
assertThat(analysis.getDescription()).contains(failure("test.foo.type", "com.example.Missing",
"\"test.foo.type\" from property source \"test\"",
"failed to convert java.lang.String to java.lang.Class<?> (caused by java.lang.ClassNotFoundException: com.example.Missing"));
}
private static String failure(String property, String value, String origin, String reason) {
@ -178,6 +188,8 @@ class BindFailureAnalyzerTests {
private int value;
private Class<?> type;
int getValue() {
return this.value;
}
@ -186,6 +198,14 @@ class BindFailureAnalyzerTests {
this.value = value;
}
Class<?> getType() {
return this.type;
}
void setType(Class<?> type) {
this.type = type;
}
}
@ConfigurationProperties("test.foo")