From 33e853cbb5ac08a17e9afcd194239ee6a761ed86 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Tue, 29 Jun 2021 16:30:05 -0500 Subject: [PATCH] #1557 - Add support for @DecimalMin and @DecimalMax. Add support for decimal-based type validation constraints. --- src/main/asciidoc/mediatypes.adoc | 4 +-- .../hateoas/AffordanceModel.java | 4 +-- .../hateoas/mediatype/PropertyUtils.java | 35 ++++++++++++++++--- .../mediatype/hal/forms/HalFormsProperty.java | 13 +++---- .../HalFormsTemplateBuilderUnitTest.java | 23 ++++++++++++ 5 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/main/asciidoc/mediatypes.adoc b/src/main/asciidoc/mediatypes.adoc index 8479976f..73fca44e 100644 --- a/src/main/asciidoc/mediatypes.adoc +++ b/src/main/asciidoc/mediatypes.adoc @@ -342,9 +342,9 @@ Each property will get the following attributes defined: |`readOnly`| Set to `true` if there's no setter method for the property. If that is present, use Jackson's `@JsonProperty(Access.READ_ONLY)` on the accessors or field explicitly. Not rendered by default, thus defaulting to `false`. |`regex`| Can be customized by using JSR-303's `@Pattern` annotation either on the field or a type. In case of the latter the pattern will be used for every property declared as that particular type. Not rendered by default. |`required`| Can be customized by using JSR-303's `@NotNull`. Not rendered by default and thus defaulting to `false`. Templates using `PATCH` as method will automatically have all properties set to not required. -|`max`| The maximum value allowed for the property. Derived from Hibernate Validator's `@Range` or JSR-303's `@Max` annotations. +|`max`| The maximum value allowed for the property. Derived from Hibernate Validator's `@Range` or JSR-303's `@Max` and `@DecimalMax` annotations. |`maxLength`| The maximum length value allowed for the property. Derived from Hibernate Validator's `@Length` annotation. -|`min`| The minimum value allowed for the property. Derived from Hibernate Validator's `@Range` or JSR-303's `@Min` annotations. +|`min`| The minimum value allowed for the property. Derived from Hibernate Validator's `@Range` or JSR-303's `@Min` and `@DecimalMin` annotations. |`minLength`| The minimum length value allowed for the property. Derived from Hibernate Validator's `@Length` annotation. |`options`| The options to select a value from when submitting the form. For details, see <>. |`prompt`| The user readable prompt to use when rendering the form input. For details, see <>. diff --git a/src/main/java/org/springframework/hateoas/AffordanceModel.java b/src/main/java/org/springframework/hateoas/AffordanceModel.java index 4d5eb23c..b49bee8d 100644 --- a/src/main/java/org/springframework/hateoas/AffordanceModel.java +++ b/src/main/java/org/springframework/hateoas/AffordanceModel.java @@ -468,7 +468,7 @@ public abstract class AffordanceModel { * @since 1.3 */ @Nullable - default Long getMin() { + default Number getMin() { return null; } @@ -479,7 +479,7 @@ public abstract class AffordanceModel { * @since 1.3 */ @Nullable - default Long getMax() { + default Number getMax() { return null; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java b/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java index d528b940..ca5ae1a5 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java +++ b/src/main/java/org/springframework/hateoas/mediatype/PropertyUtils.java @@ -20,11 +20,14 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.math.BigDecimal; import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; import javax.validation.constraints.Email; import javax.validation.constraints.Max; import javax.validation.constraints.Min; @@ -616,7 +619,7 @@ public class PropertyUtils { */ @Nullable @Override - public Long getMin() { + public Number getMin() { if (RANGE_ANNOTATION != null) { @@ -627,7 +630,19 @@ public class PropertyUtils { } } - return getAnnotationAttribute(Min.class, "value", Long.class).orElse(null); + Optional minLong = getAnnotationAttribute(Min.class, "value", Long.class); + + if (minLong.isPresent()) { + return minLong.get(); + } + + Optional minDecimal = getAnnotationAttribute(DecimalMin.class, "value", String.class); + + if (minDecimal.isPresent()) { + return new BigDecimal(minDecimal.get()); + } + + return null; } /* @@ -636,7 +651,7 @@ public class PropertyUtils { */ @Nullable @Override - public Long getMax() { + public Number getMax() { if (RANGE_ANNOTATION != null) { @@ -647,7 +662,19 @@ public class PropertyUtils { } } - return getAnnotationAttribute(Max.class, "value", Long.class).orElse(null); + Optional maxLong = getAnnotationAttribute(Max.class, "value", Long.class); + + if (maxLong.isPresent()) { + return maxLong.get(); + } + + Optional maxDecimal = getAnnotationAttribute(DecimalMax.class, "value", String.class); + + if (maxDecimal.isPresent()) { + return new BigDecimal(maxDecimal.get()); + } + + return null; } /* diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java index 1cab152f..468b38e0 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsProperty.java @@ -42,7 +42,8 @@ final class HalFormsProperty implements Named { private final Object value; private final boolean templated, multi; private final @JsonInclude(Include.NON_DEFAULT) boolean readOnly, required; - private final @Nullable Long min, max, minLength, maxLength; + private final @Nullable Number min, max; + private final @Nullable Long minLength, maxLength; private final @Nullable HtmlInputType type; private final @Nullable HalFormsOptions options; @@ -67,7 +68,7 @@ final class HalFormsProperty implements Named { private HalFormsProperty(String name, boolean readOnly, @Nullable Object value, String prompt, String regex, boolean templated, - boolean required, boolean multi, String placeholder, @Nullable Long min, @Nullable Long max, + boolean required, boolean multi, String placeholder, @Nullable Number min, @Nullable Number max, @Nullable Long minLength, @Nullable Long maxLength, @Nullable HtmlInputType type, @Nullable HalFormsOptions options) { @@ -237,7 +238,7 @@ final class HalFormsProperty implements Named { * @param min can be {@literal null} * @return will never be {@literal null}. */ - HalFormsProperty withMin(@Nullable Long min) { + HalFormsProperty withMin(@Nullable Number min) { return Objects.equals(this.min, min) ? this : new HalFormsProperty(this.name, this.readOnly, this.value, this.prompt, this.regex, this.templated, @@ -251,7 +252,7 @@ final class HalFormsProperty implements Named { * @param max can be {@literal null} * @return will never be {@literal null}. */ - HalFormsProperty withMax(@Nullable Long max) { + HalFormsProperty withMax(@Nullable Number max) { return Objects.equals(this.max, max) ? this : new HalFormsProperty(this.name, this.readOnly, this.value, this.prompt, this.regex, this.templated, @@ -375,7 +376,7 @@ final class HalFormsProperty implements Named { */ @Nullable @JsonProperty - public Long getMin() { + public Number getMin() { return min; } @@ -384,7 +385,7 @@ final class HalFormsProperty implements Named { */ @Nullable @JsonProperty - public Long getMax() { + public Number getMax() { return max; } diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java index e74a8384..9c798b4c 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsTemplateBuilderUnitTest.java @@ -19,11 +19,14 @@ import static org.assertj.core.api.Assertions.*; import lombok.Getter; +import java.math.BigDecimal; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; @@ -124,6 +127,22 @@ class HalFormsTemplateBuilderUnitTest { assertThat(text).map(HalFormsProperty::getMaxLength).hasValue(5L); } + @Test // #1557 + void considersDecimalMinandDecimalMaxAnnotations() { + + Link link = Affordances.of(Link.of("/example")) // + .afford(HttpMethod.POST) // + .withInput(Payload.class) // + .toLink(); + + HalFormsTemplate template = new HalFormsTemplateBuilder(new HalFormsConfiguration(), // + MessageResolver.DEFAULTS_ONLY).findTemplates(new RepresentationModel<>().add(link)).get("default"); + + Optional decimal = template.getPropertyByName("decimal"); + assertThat(decimal).map(HalFormsProperty::getMin).hasValue(new BigDecimal("2.1")); + assertThat(decimal).map(HalFormsProperty::getMax).hasValue(new BigDecimal("5.3")); + } + @Test // #1427 void addsTargetAttributeForLinksNotPointingToSelf() { @@ -238,5 +257,9 @@ class HalFormsTemplateBuilderUnitTest { @Range(min = 8, max = 10) // Integer range; + + @DecimalMin("2.1") // + @DecimalMax("5.3") // + BigDecimal decimal; } }