From a5c2f0fc74f2968e670ad18d61c7ae6e7c61940e Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 17 Dec 2024 19:20:01 +0000 Subject: [PATCH] Improve diagnostics when config prop value conversion fails Closes gh-43378 Co-Authored-By: Phillip Webb --- ...rationPropertySourcesPropertyResolver.java | 10 ++++++- ...idConfigurationPropertyValueException.java | 6 ++++- ...nPropertySourcesPropertyResolverTests.java | 27 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java index 9cb2d77b736..e1940ad66f2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java @@ -16,6 +16,7 @@ package org.springframework.boot.context.properties.source; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.env.AbstractPropertyResolver; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySources; @@ -79,7 +80,14 @@ class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResol if (resolveNestedPlaceholders && value instanceof String string) { value = resolveNestedPlaceholders(string); } - return convertValueIfNecessary(value, targetValueType); + try { + return convertValueIfNecessary(value, targetValueType); + } + catch (ConversionFailedException ex) { + Exception wrappedCause = new InvalidConfigurationPropertyValueException(key, value, + "Failed to convert to type " + ex.getTargetType(), ex.getCause()); + throw new ConversionFailedException(ex.getSourceType(), ex.getTargetType(), ex.getValue(), wrappedCause); + } } private Object findPropertyValue(String key) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java index edba3777cab..1bef986b34e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java @@ -43,7 +43,11 @@ public class InvalidConfigurationPropertyValueException extends RuntimeException * returns are allowed. */ public InvalidConfigurationPropertyValueException(String name, Object value, String reason) { - super("Property " + name + " with value '" + value + "' is invalid: " + reason); + this(name, value, reason, null); + } + + InvalidConfigurationPropertyValueException(String name, Object value, String reason, Throwable cause) { + super("Property " + name + " with value '" + value + "' is invalid: " + reason, cause); Assert.notNull(name, "Name must not be null"); this.name = name; this.value = value; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java index 8032febc231..4378789df4a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java @@ -22,12 +22,15 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.StandardEnvironment; import org.springframework.mock.env.MockPropertySource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link ConfigurationPropertySourcesPropertyResolver}. @@ -113,6 +116,30 @@ class ConfigurationPropertySourcesPropertyResolverTests { assertThat(environment.getProperty("v2", Integer.class)).isOne(); } + @Test + void throwsInvalidConfigurationPropertyValueExceptionWhenGetPropertyAsTypeFailsToConvert() { + ResolverEnvironment environment = new ResolverEnvironment(); + MockPropertySource propertySource = new MockPropertySource(); + propertySource.withProperty("v1", "one"); + propertySource.withProperty("v2", "${v1}"); + environment.getPropertySources().addFirst(propertySource); + assertThat(environment.getProperty("v2")).isEqualTo("one"); + assertThatExceptionOfType(ConversionFailedException.class) + .isThrownBy(() -> environment.getProperty("v2", Integer.class)) + .satisfies((ex) -> { + assertThat(ex.getValue()).isEqualTo("one"); + assertThat(ex.getSourceType()).isEqualTo(TypeDescriptor.valueOf(String.class)); + assertThat(ex.getTargetType()).isEqualTo(TypeDescriptor.valueOf(Integer.class)); + }) + .havingCause() + .satisfies((ex) -> { + InvalidConfigurationPropertyValueException invalidValueEx = (InvalidConfigurationPropertyValueException) ex; + assertThat(invalidValueEx.getName()).isEqualTo("v2"); + assertThat(invalidValueEx.getValue()).isEqualTo("one"); + assertThat(ex).cause().isInstanceOf(NumberFormatException.class); + }); + } + private CountingMockPropertySource createMockPropertySource(StandardEnvironment environment, boolean attach) { CountingMockPropertySource propertySource = new CountingMockPropertySource(); propertySource.withProperty("spring", "boot");