From ea62967ef4da960ae489052c8f1ab2384e97c204 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 23 Jun 2021 20:51:42 -0700 Subject: [PATCH] Improve "reason" message in BindFailureAnalyzer Update `BindFailureAnalyzer` so that the "Reason" message includes the root cause exception type and message. Closes gh-27028 --- .../analyzer/BindFailureAnalyzer.java | 30 +++++++++++++++---- .../analyzer/BindFailureAnalyzerTests.java | 24 +++++++++++++-- 2 files changed, 46 insertions(+), 8 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 1a735ed667e..75f1b1c643a 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 @@ -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 { @@ -68,16 +69,33 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer { } 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; + } + if (rootCause != null && StringUtils.hasText(rootCause.getMessage())) { + return getExceptionTypeAndMessage(rootCause); } - Throwable failure = cause; - while (failure.getCause() != null) { - failure = failure.getCause(); + return getExceptionTypeAndMessage(cause); + } + + private Throwable getRootCause(Throwable cause) { + Throwable rootCause = cause; + while (rootCause != null && rootCause.getCause() != null) { + rootCause = rootCause.getCause(); } - return (StringUtils.hasText(failure.getMessage()) ? failure.getMessage() : cause.getMessage()); + 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) { 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 2d7ad180d3e..c0b5cdb418f 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 @@ -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")