From 914452b2cc89bc820515fcd91ce2c69da7a9c042 Mon Sep 17 00:00:00 2001 From: dreis2211 Date: Wed, 29 Jul 2020 17:41:31 +0200 Subject: [PATCH] Allow DurationFormat and PeriodFormat to be used on parameters See gh-22646 --- .../boot/convert/DurationFormat.java | 4 +- .../boot/convert/PeriodFormat.java | 2 +- .../ConfigurationPropertiesTests.java | 81 +++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java index 116a780833..3f53a2368b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2020 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. @@ -30,7 +30,7 @@ import java.time.Duration; * @author Phillip Webb * @since 2.0.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DurationFormat { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java index 45d45183b8..48f6537dbc 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodFormat.java @@ -31,7 +31,7 @@ import java.time.Period; * @author Edson Chávez * @since 2.3.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PeriodFormat { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index e1e289baf6..346e773326 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -56,7 +56,11 @@ import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.convert.DataSizeUnit; +import org.springframework.boot.convert.DurationFormat; +import org.springframework.boot.convert.DurationStyle; import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.convert.PeriodFormat; +import org.springframework.boot.convert.PeriodStyle; import org.springframework.boot.convert.PeriodUnit; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; @@ -808,6 +812,53 @@ class ConfigurationPropertiesTests { assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(4)); } + @Test + void loadWhenBindingToConstructorParametersWithCustomDataFormatShouldBind() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + Map source = new HashMap<>(); + source.put("test.duration", "12d"); + source.put("test.period", "13y"); + sources.addLast(new MapPropertySource("test", source)); + load(ConstructorParameterWithFormatConfiguration.class); + ConstructorParameterWithFormatProperties bean = this.context + .getBean(ConstructorParameterWithFormatProperties.class); + assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(12)); + assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(13)); + } + + @Test + void loadWhenBindingToConstructorParametersWithNotMatchingCustomDurationFormatShouldFail() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + Map source = new HashMap<>(); + source.put("test.duration", "P12D"); + sources.addLast(new MapPropertySource("test", source)); + assertThatExceptionOfType(Exception.class) + .isThrownBy(() -> load(ConstructorParameterWithFormatConfiguration.class)).satisfies((ex) -> { + assertThat(ex).hasCauseInstanceOf(BindException.class); + }); + } + + @Test + void loadWhenBindingToConstructorParametersWithNotMatchingCustomPeriodFormatShouldFail() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + Map source = new HashMap<>(); + source.put("test.period", "P12D"); + sources.addLast(new MapPropertySource("test", source)); + assertThatExceptionOfType(Exception.class) + .isThrownBy(() -> load(ConstructorParameterWithFormatConfiguration.class)).satisfies((ex) -> { + assertThat(ex).hasCauseInstanceOf(BindException.class); + }); + } + + @Test + void loadWhenBindingToConstructorParametersWithDefaultDataFormatShouldBind() { + load(ConstructorParameterWithFormatConfiguration.class); + ConstructorParameterWithFormatProperties bean = this.context + .getBean(ConstructorParameterWithFormatProperties.class); + assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(2)); + assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(3)); + } + @Test void loadWhenBindingToConstructorParametersShouldValidate() { assertThatExceptionOfType(Exception.class) @@ -2007,6 +2058,31 @@ class ConfigurationPropertiesTests { } + @ConstructorBinding + @ConfigurationProperties(prefix = "test") + static class ConstructorParameterWithFormatProperties { + + private final Duration duration; + + private final Period period; + + ConstructorParameterWithFormatProperties( + @DefaultValue("2d") @DurationFormat(DurationStyle.SIMPLE) Duration duration, + @DefaultValue("3y") @PeriodFormat(PeriodStyle.SIMPLE) Period period) { + this.duration = duration; + this.period = period; + } + + Duration getDuration() { + return this.duration; + } + + Period getPeriod() { + return this.period; + } + + } + @ConstructorBinding @ConfigurationProperties(prefix = "test") @Validated @@ -2035,6 +2111,11 @@ class ConfigurationPropertiesTests { } + @EnableConfigurationProperties(ConstructorParameterWithFormatProperties.class) + static class ConstructorParameterWithFormatConfiguration { + + } + @EnableConfigurationProperties(ConstructorParameterValidatedProperties.class) static class ConstructorParameterValidationConfiguration {