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 b941e681..1a72fd88 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 | | `@MinDate` | Validates that the annotated element is after specified minimum date | | `@NotNullWhen` | Validates that the annotated element must not be null when condition is satisfied | diff --git a/nrich-validation/README.md b/nrich-validation/README.md index 32391d80..e1792c5a 100644 --- a/nrich-validation/README.md +++ b/nrich-validation/README.md @@ -334,3 +334,36 @@ public class ExampleRequest { ``` Above request will require that the date property is after 01.01.2023 + +#### LastTimestampInDay + +Validates that the annotated element is before end of the day + +Supported types are: +- java.util.Date +- java.util.Calendar +- java.time.Instant +- java.time.LocalDate +- java.time.LocalDateTime +- java.time.LocalTime +- java.time.MonthDay +- java.time.OffsetDateTime +- java.time.OffsetTime +- java.time.Year +- java.time.YearMonth +- java.time.ZonedDateTime + +```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 5b2f312b..cafa315b 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.MinDateValidator; import net.croz.nrich.validation.constraint.validator.SpelExpressionValidator; import net.croz.nrich.validation.constraint.validator.InListValidator; @@ -45,7 +46,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, MinDateValidator.class + NotNullWhenValidator.class, NullWhenValidator.class, ValidFileValidator.class, ValidFileResolvableValidator.class, InListValidator.class, SpelExpressionValidator.class, + MinDateValidator.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 263d030d..bc969069 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.MinDate; import net.croz.nrich.validation.api.constraint.SpelExpression; import net.croz.nrich.validation.api.constraint.InList; @@ -29,6 +30,7 @@ import net.croz.nrich.validation.api.constraint.ValidRange; import net.croz.nrich.validation.api.constraint.ValidSearchProperties; import net.croz.nrich.validation.constraint.validator.MinDateValidator; +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; @@ -56,5 +58,6 @@ public void createConstraintMappings(ConstraintMappingBuilder builder) { builder.addConstraintMapping().constraintDefinition(InList.class).validatedBy(InListValidator.class); builder.addConstraintMapping().constraintDefinition(SpelExpression.class).validatedBy(SpelExpressionValidator.class); builder.addConstraintMapping().constraintDefinition(MinDate.class).validatedBy(MinDateValidator.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..ad0218e9 --- /dev/null +++ b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/validator/LastTimestampInDayValidator.java @@ -0,0 +1,43 @@ +/* + * 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.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; + +import static net.croz.nrich.validation.constraint.util.DateConverterUtil.convertToInstant; + +public class LastTimestampInDayValidator implements ConstraintValidator { + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + if (value == null) { + return true; + } + + Instant lastTimestampInDay = LocalDate.now().atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()).toInstant(); + + return convertToInstant(value).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..df18bef3 --- /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 localDate; +} 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..9b0db32c --- /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 shouldNotReportErrorDateIsBeforeLastTimestampInDay() { + // given + LastTimestampInDayTestRequest request = new LastTimestampInDayTestRequest(LocalDate.now()); + + // when + Set> constraintViolationList = validator.validate(request); + + // then + assertThat(constraintViolationList).isEmpty(); + } + + @Test + void shouldReportErrorWhenDateIsAfterLastTimestampInDay() { + // given + LastTimestampInDayTestRequest request = new LastTimestampInDayTestRequest(LocalDate.now().plusDays(1)); + + // when + Set> constraintViolationList = validator.validate(request); + + // then + assertThat(constraintViolationList).isNotEmpty(); + } + +}