-
Notifications
You must be signed in to change notification settings - Fork 41.8k
Description
A comment on issue #43437 (comment) along with #24133 shows that we're currently lacking some functionality in our binder support.
Sample use-case
The sample in #43437 (comment) has the following use-case.
Imagine you have existing properties that allow you to configure throttling. There's a simple type enum supporting fixed, none and doubling:
my-application:
throttling:
type: fixed
duration: 2sAfter a while, you realize you want to offer more properties to configure various aspects of the throttling strategy. The properties are different for each strategy you support. To do this you expand the properties, and programmatically enforce that only one is specified.
For example:
my-application:
throttling:
fixed:
duration: 2sor
my-application:
throttling:
doubling:
min: 2s
max: 2sThe following would be an invalid configuration and throw an exception because you can't have both fixed and doubling:
my-application:
throttling:
fixed:
duration: 2s
doubling:
min: 2s
max: 2sThe problem is, you also need properties to represent none. You want the following to work:
my-application:
throttling:
none: {}This means you need to know the difference between my-application.throttling.none being specified as an empty object or missing entirely.
Initial Ideal (using @DefaultValue as an indicator)
The @DefaultValue annotation could be used to replace both null values (as it currently does) as well as empty values (empty strings, or empty collections). It would also be nice if the annotation could indicate if it always applies, or if it should only apply when the underlying PropertySource.containsProperty method returns true.
One possible option is to add replace and mustContainProperty attributes to @DefaultValue. The replace attribute would be an enum of NULL_VALUES or NULL_AND_EMPTY_VALUES. The mustContainProperty would be a boolean to indicate if the property must be contained in the underlying source.
In order to be completely useful, some changes would also be need to OriginTrackedYamlLoader (and probably the underlying Spring Framework class) so the empty objects are inserted into the Map with null values.
With the suggested annotations, this would be possible using:
public record ThrottlingProperties(
@DefaultValue(replace = NULL_OR_EMPTY_VALUES, mustContainProperty = true) Fixed fixed,
@DefaultValue(replace = NULL_OR_EMPTY_VALUES, mustContainProperty = true) Doubling doubling,
@DefaultValue(replace = NULL_OR_EMPTY_VALUES, mustContainProperty = true) None none) {
public ThrottlingProperties {
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((values) -> {
values.put("fixed", fixed);
values.put("doubling", doubling);
values.put("none", none);
});
}
public record Fixed(Duration duration) {
}
public record Doubling(Duration min, Duration max) {
}
public record None() {
}
}Using NULL_OR_EMPTY_VALUES means that .properties files could also work (since they don't support null values):
my-application.throttling.none