From e10c405716e25e71c94c82c311501226798017de Mon Sep 17 00:00:00 2001 From: Mihael Cacko Date: Tue, 24 Oct 2023 11:47:59 +0200 Subject: [PATCH] Add last timestamp in a day constraint --- .../api/constraint/LastTimestampInDay.java | 63 +++++++++++++++++++ .../README.md | 1 + nrich-validation/README.md | 19 ++++++ .../aot/ValidationRuntimeHintsRegistrar.java | 4 +- .../DefaultConstraintMappingContributor.java | 3 + .../LastTimestampInDayValidator.java | 46 ++++++++++++++ .../stub/LastTimestampInDayTestRequest.java | 15 +++++ .../LastTimestampInDayValidatorTest.java | 58 +++++++++++++++++ 8 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 nrich-validation-api/src/main/java/net/croz/nrich/validation/api/constraint/LastTimestampInDay.java create mode 100644 nrich-validation/src/main/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidator.java create mode 100644 nrich-validation/src/test/java/net/croz/nrich/validation/constraint/stub/LastTimestampInDayTestRequest.java create mode 100644 nrich-validation/src/test/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidatorTest.java diff --git a/nrich-validation-api/src/main/java/net/croz/nrich/validation/api/constraint/LastTimestampInDay.java b/nrich-validation-api/src/main/java/net/croz/nrich/validation/api/constraint/LastTimestampInDay.java new file mode 100644 index 00000000..bc152eb4 --- /dev/null +++ b/nrich-validation-api/src/main/java/net/croz/nrich/validation/api/constraint/LastTimestampInDay.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.api.constraint; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * The annotated element must be before end of the day + */ +@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) +@Retention(RUNTIME) +@Repeatable(LastTimestampInDay.List.class) +@Documented +@Constraint(validatedBy = {}) +public @interface LastTimestampInDay { + + String message() default "{nrich.constraint.lastTimestampInDay.invalid.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + /** + * Defines several {@link LastTimestampInDay.List} annotations on the same element. + * + * @see LastTimestampInDay.List + */ + @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) + @Retention(RUNTIME) + @Documented + @interface List { + + LastTimestampInDay[] value(); + } +} diff --git a/nrich-validation-spring-boot-starter/README.md b/nrich-validation-spring-boot-starter/README.md index 5209a0fd..b0feab35 100644 --- a/nrich-validation-spring-boot-starter/README.md +++ b/nrich-validation-spring-boot-starter/README.md @@ -62,6 +62,7 @@ and default ones disabled then through `nrich.validation.register-messages` prop | constraint | description | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `@InList` | Validates that the annotated element is in the specified list of values | +| `@LastTimestampInDay` | Validates that the annotated element is before end of the day | | `@MaxSizeInBytes` | Validates that the annotated element size in bytes must be less than specified maximum | | `@NotNullWhen` | Validates that the annotated element must not be null when condition is satisfied | | `@NullWhen` | Validates that the annotated element must be null when condition is satisfied | diff --git a/nrich-validation/README.md b/nrich-validation/README.md index bf4cce84..61df98b5 100644 --- a/nrich-validation/README.md +++ b/nrich-validation/README.md @@ -301,3 +301,22 @@ public class ExampleTypeRequest { ``` In an example above the property uuid is required to be a valid UUID format. The format check is achieved by calling `validateUuid` method of `spelValidationTestService` bean. + +#### LastTimestampInDay + +Validates that the annotated element is before end of the day + +```java + +@Setter +@Getter +public class ExampleRequest { + + @LastTimestampInDay + private LocalDate date; + +} + +``` + +Above request will require that the date property is before the end of the day diff --git a/nrich-validation/src/main/java/net/croz/nrich/validation/aot/ValidationRuntimeHintsRegistrar.java b/nrich-validation/src/main/java/net/croz/nrich/validation/aot/ValidationRuntimeHintsRegistrar.java index 3fb53487..8cccd11a 100644 --- a/nrich-validation/src/main/java/net/croz/nrich/validation/aot/ValidationRuntimeHintsRegistrar.java +++ b/nrich-validation/src/main/java/net/croz/nrich/validation/aot/ValidationRuntimeHintsRegistrar.java @@ -18,6 +18,7 @@ package net.croz.nrich.validation.aot; import net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingContributor; +import net.croz.nrich.validation.constraint.validator.LastTimestampInDayValidator; import net.croz.nrich.validation.constraint.validator.SpelExpressionValidator; import net.croz.nrich.validation.constraint.validator.InListValidator; import net.croz.nrich.validation.constraint.validator.MaxSizeInBytesValidator; @@ -44,7 +45,8 @@ public class ValidationRuntimeHintsRegistrar implements RuntimeHintsRegistrar { public static final List TYPE_REFERENCE_LIST = Collections.unmodifiableList(TypeReference.listOf( DefaultConstraintMappingContributor.class, ValidOibValidator.class, ValidSearchPropertiesValidator.class, ValidRangeValidator.class, MaxSizeInBytesValidator.class, - NotNullWhenValidator.class, NullWhenValidator.class, ValidFileValidator.class, ValidFileResolvableValidator.class, InListValidator.class, SpelExpressionValidator.class + NotNullWhenValidator.class, NullWhenValidator.class, ValidFileValidator.class, ValidFileResolvableValidator.class, InListValidator.class, SpelExpressionValidator.class, + LastTimestampInDayValidator.class )); @Override diff --git a/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java index 6e96b9c6..edacbf91 100644 --- a/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java +++ b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java @@ -17,6 +17,7 @@ package net.croz.nrich.validation.constraint.mapping; +import net.croz.nrich.validation.api.constraint.LastTimestampInDay; import net.croz.nrich.validation.api.constraint.SpelExpression; import net.croz.nrich.validation.api.constraint.InList; import net.croz.nrich.validation.api.constraint.MaxSizeInBytes; @@ -27,6 +28,7 @@ import net.croz.nrich.validation.api.constraint.ValidOib; import net.croz.nrich.validation.api.constraint.ValidRange; import net.croz.nrich.validation.api.constraint.ValidSearchProperties; +import net.croz.nrich.validation.constraint.validator.LastTimestampInDayValidator; import net.croz.nrich.validation.constraint.validator.SpelExpressionValidator; import net.croz.nrich.validation.constraint.validator.InListValidator; import net.croz.nrich.validation.constraint.validator.MaxSizeInBytesValidator; @@ -53,5 +55,6 @@ public void createConstraintMappings(ConstraintMappingBuilder builder) { builder.addConstraintMapping().constraintDefinition(ValidFileResolvable.class).validatedBy(ValidFileResolvableValidator.class); builder.addConstraintMapping().constraintDefinition(InList.class).validatedBy(InListValidator.class); builder.addConstraintMapping().constraintDefinition(SpelExpression.class).validatedBy(SpelExpressionValidator.class); + builder.addConstraintMapping().constraintDefinition(LastTimestampInDay.class).validatedBy(LastTimestampInDayValidator.class); } } diff --git a/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidator.java b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidator.java new file mode 100644 index 00000000..a36c7660 --- /dev/null +++ b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidator.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.constraint.validator; + +import net.croz.nrich.validation.api.constraint.LastTimestampInDay; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.ChronoField; + +public class LastTimestampInDayValidator implements ConstraintValidator { + + private LocalDateTime lastTimestampInDay; + + @Override + public void initialize(LastTimestampInDay constraintAnnotation) { + lastTimestampInDay = LocalDateTime.now().with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay()); + } + + @Override + public boolean isValid(LocalDate value, ConstraintValidatorContext context) { + if (value == null) { + return true; + } + + return value.atTime(LocalTime.now()).isBefore(lastTimestampInDay); + } +} diff --git a/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/stub/LastTimestampInDayTestRequest.java b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/stub/LastTimestampInDayTestRequest.java new file mode 100644 index 00000000..64626796 --- /dev/null +++ b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/stub/LastTimestampInDayTestRequest.java @@ -0,0 +1,15 @@ +package net.croz.nrich.validation.constraint.stub; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.croz.nrich.validation.api.constraint.LastTimestampInDay; + +import java.time.LocalDate; + +@RequiredArgsConstructor +@Getter +public class LastTimestampInDayTestRequest { + + @LastTimestampInDay + private final LocalDate date; +} diff --git a/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidatorTest.java b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidatorTest.java new file mode 100644 index 00000000..c24ff725 --- /dev/null +++ b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidatorTest.java @@ -0,0 +1,58 @@ +package net.croz.nrich.validation.constraint.validator; + +import net.croz.nrich.validation.ValidationTestConfiguration; +import net.croz.nrich.validation.constraint.stub.LastTimestampInDayTestRequest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; +import java.time.LocalDate; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringJUnitConfig(ValidationTestConfiguration.class) +class LastTimestampInDayValidatorTest { + + @Autowired + private Validator validator; + + @Test + void shouldNotReportErrorForNullValue() { + // given + LastTimestampInDayTestRequest request = new LastTimestampInDayTestRequest(null); + + // when + Set> constraintViolationList = validator.validate(request); + + // then + assertThat(constraintViolationList).isEmpty(); + } + + @Test + void shouldNotReportErrorDateIsAfterMinDate() { + // given + LastTimestampInDayTestRequest request = new LastTimestampInDayTestRequest(LocalDate.of(2023, 10, 23)); + + // when + Set> constraintViolationList = validator.validate(request); + + // then + assertThat(constraintViolationList).isEmpty(); + } + + @Test + void shouldReportErrorWhenDateIsBeforeMinDate() { + // given + LastTimestampInDayTestRequest request = new LastTimestampInDayTestRequest(LocalDate.now().plusDays(1)); + + // when + Set> constraintViolationList = validator.validate(request); + + // then + assertThat(constraintViolationList).isNotEmpty(); + } + +}