-
Notifications
You must be signed in to change notification settings - Fork 0
add common validators #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/RepeatedField.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package it.aboutbits.springboot.toolbox.validation.annotation; | ||
|
|
||
| import it.aboutbits.springboot.toolbox.validation.validator.RepeatedFieldValidator; | ||
| import jakarta.validation.Constraint; | ||
| import jakarta.validation.Payload; | ||
|
|
||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Repeatable; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
|
||
| @Target(ElementType.TYPE) | ||
| @Retention(RUNTIME) | ||
| @Constraint(validatedBy = RepeatedFieldValidator.class) | ||
| @Documented | ||
| @Repeatable(RepeatedField.List.class) | ||
| public @interface RepeatedField { | ||
| String message() default "{shared.error.validation.repetitionMismatch}"; | ||
|
|
||
| String originalField(); | ||
|
|
||
| String repeatedField(); | ||
|
|
||
| Class<?>[] groups() default {}; | ||
|
|
||
| Class<? extends Payload>[] payload() default {}; | ||
|
|
||
| @Target({ElementType.TYPE}) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Documented | ||
| @interface List { | ||
| RepeatedField[] value(); | ||
| } | ||
| } | ||
48 changes: 48 additions & 0 deletions
48
src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidDateRange.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package it.aboutbits.springboot.toolbox.validation.annotation; | ||
|
|
||
| import it.aboutbits.springboot.toolbox.validation.validator.ValidDateRangeValidator; | ||
| import jakarta.validation.Constraint; | ||
| import jakarta.validation.Payload; | ||
|
|
||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Repeatable; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
|
||
| /** | ||
| * Validator bean to check if the {@code to} date field is after the {@code from} date field.<br> | ||
| * If one of the two fields is {@code null} this validation bean will still return true, as in a form, for example, | ||
| * the {@code from} field could be specified. | ||
| * <p> | ||
| * Supports {@link java.time.LocalDateTime}, {@link java.time.LocalDate}, {@link java.time.OffsetDateTime}, | ||
| * {@link java.time.ZonedDateTime}, and {@link java.time.Instant}. | ||
| */ | ||
| @Documented | ||
| @Constraint(validatedBy = ValidDateRangeValidator.class) | ||
| @Target(ElementType.TYPE) | ||
| @Retention(RUNTIME) | ||
| @Repeatable(ValidDateRange.List.class) | ||
| public @interface ValidDateRange { | ||
| String message() default "{shared.error.validation.invalidDateRange}"; | ||
|
|
||
| boolean allowEmptyRange() default false; | ||
|
|
||
| String fromDateField(); | ||
|
|
||
| String toDateField(); | ||
|
|
||
| Class<?>[] groups() default {}; | ||
|
|
||
| Class<? extends Payload>[] payload() default {}; | ||
|
|
||
| @Target({ElementType.TYPE}) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Documented | ||
| @interface List { | ||
| ValidDateRange[] value(); | ||
| } | ||
| } |
47 changes: 47 additions & 0 deletions
47
src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidNumericRange.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package it.aboutbits.springboot.toolbox.validation.annotation; | ||
|
|
||
| import it.aboutbits.springboot.toolbox.validation.validator.ValidNumericRangeValidator; | ||
| import jakarta.validation.Constraint; | ||
| import jakarta.validation.Payload; | ||
|
|
||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Repeatable; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
|
||
| /** | ||
| * Validator bean to check if the lower bound numeric field is not higher than the upper bound numeric field.<br> | ||
| * If one of the two fields is {@code null} this validation bean will still return true, as in a form, for example, | ||
| * only one field could be specified. | ||
| * <p> | ||
| * Supports numeric types like {@link Integer}, {@link Long}, {@link Float}, {@link Double}, etc. | ||
| */ | ||
| @Documented | ||
| @Constraint(validatedBy = ValidNumericRangeValidator.class) | ||
| @Target(ElementType.TYPE) | ||
| @Retention(RUNTIME) | ||
| @Repeatable(ValidNumericRange.List.class) | ||
| public @interface ValidNumericRange { | ||
| String message() default "{shared.error.validation.invalidNumericRange}"; | ||
|
|
||
| boolean allowEqualValues() default true; | ||
|
|
||
| String lowerBoundField(); | ||
|
|
||
| String upperBoundField(); | ||
|
|
||
| Class<?>[] groups() default {}; | ||
|
|
||
| Class<? extends Payload>[] payload() default {}; | ||
|
|
||
| @Target({ElementType.TYPE}) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Documented | ||
| @interface List { | ||
| ValidNumericRange[] value(); | ||
| } | ||
| } |
31 changes: 31 additions & 0 deletions
31
src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidPassword.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package it.aboutbits.springboot.toolbox.validation.annotation; | ||
|
|
||
| import it.aboutbits.springboot.toolbox.validation.validator.ValidPasswordValidator; | ||
| import jakarta.validation.Constraint; | ||
| import jakarta.validation.Payload; | ||
|
|
||
| import java.lang.annotation.Documented; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| import static java.lang.annotation.ElementType.ANNOTATION_TYPE; | ||
| import static java.lang.annotation.ElementType.FIELD; | ||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
|
||
| @Documented | ||
| @Constraint(validatedBy = ValidPasswordValidator.class) | ||
| @Target({FIELD, ANNOTATION_TYPE}) | ||
| @Retention(RUNTIME) | ||
| public @interface ValidPassword { | ||
| String message() default "{shared.error.validation.invalidPassword}"; | ||
|
|
||
| String lengthMessage() default "{shared.error.validation.invalidPassword.length}"; | ||
|
|
||
| int minLength() default 8; | ||
|
|
||
| int maxLength() default 50; | ||
|
|
||
| Class<?>[] groups() default {}; | ||
|
|
||
| Class<? extends Payload>[] payload() default {}; | ||
| } |
44 changes: 44 additions & 0 deletions
44
...ain/java/it/aboutbits/springboot/toolbox/validation/validator/RepeatedFieldValidator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package it.aboutbits.springboot.toolbox.validation.validator; | ||
|
|
||
| import it.aboutbits.springboot.toolbox.validation.annotation.RepeatedField; | ||
| import jakarta.validation.ConstraintValidator; | ||
| import jakarta.validation.ConstraintValidatorContext; | ||
| import org.jspecify.annotations.NullMarked; | ||
| import org.jspecify.annotations.Nullable; | ||
| import org.springframework.beans.BeanWrapperImpl; | ||
|
|
||
| @NullMarked | ||
| public class RepeatedFieldValidator implements ConstraintValidator<RepeatedField, Object> { | ||
| private @Nullable String originalField; | ||
| private @Nullable String repeatedField; | ||
| private @Nullable String message; | ||
|
|
||
| @Override | ||
| public void initialize(RepeatedField constraintAnnotation) { | ||
| this.originalField = constraintAnnotation.originalField(); | ||
| this.repeatedField = constraintAnnotation.repeatedField(); | ||
| this.message = constraintAnnotation.message(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isValid(Object value, ConstraintValidatorContext context) { | ||
| if (originalField == null || repeatedField == null) { | ||
| throw new IllegalStateException("Both originalField and repeatedField must be set."); | ||
| } | ||
|
|
||
| var fieldValue = new BeanWrapperImpl(value).getPropertyValue(originalField); | ||
| var fieldMatchValue = new BeanWrapperImpl(value).getPropertyValue(repeatedField); | ||
|
|
||
| var isValid = fieldValue != null && fieldValue.equals(fieldMatchValue); | ||
|
|
||
| if (!isValid) { | ||
| context.disableDefaultConstraintViolation(); | ||
| context | ||
| .buildConstraintViolationWithTemplate(message) | ||
| .addPropertyNode(repeatedField) | ||
| .addConstraintViolation(); | ||
| } | ||
|
|
||
| return isValid; | ||
| } | ||
| } |
73 changes: 73 additions & 0 deletions
73
...in/java/it/aboutbits/springboot/toolbox/validation/validator/ValidDateRangeValidator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package it.aboutbits.springboot.toolbox.validation.validator; | ||
|
|
||
| import it.aboutbits.springboot.toolbox.validation.annotation.ValidDateRange; | ||
| import jakarta.validation.ConstraintValidator; | ||
| import jakarta.validation.ConstraintValidatorContext; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.jspecify.annotations.NullMarked; | ||
| import org.jspecify.annotations.Nullable; | ||
| import org.springframework.beans.PropertyAccessorFactory; | ||
|
|
||
| import java.time.Instant; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.time.OffsetDateTime; | ||
| import java.time.ZonedDateTime; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @NullMarked | ||
| public class ValidDateRangeValidator implements ConstraintValidator<ValidDateRange, Object> { | ||
| @Nullable | ||
| private String fromDateField; | ||
| @Nullable | ||
| private String toDateField; | ||
| @Nullable | ||
| private String message; | ||
| private boolean allowEmptyRange = false; | ||
|
|
||
| @Override | ||
| public void initialize(ValidDateRange constraintAnnotation) { | ||
| this.fromDateField = constraintAnnotation.fromDateField(); | ||
| this.toDateField = constraintAnnotation.toDateField(); | ||
| this.message = constraintAnnotation.message(); | ||
| this.allowEmptyRange = constraintAnnotation.allowEmptyRange(); | ||
| } | ||
|
|
||
| public boolean isValid(Object value, ConstraintValidatorContext context) { | ||
| if (fromDateField == null || toDateField == null || message == null) { | ||
| throw new IllegalStateException("All fields must be set (fromDateField, toDateField, message)."); | ||
| } | ||
|
|
||
| var propertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(value); | ||
| var fromDate = propertyAccessor.getPropertyValue(fromDateField); | ||
| var toDate = propertyAccessor.getPropertyValue(toDateField); | ||
|
|
||
| if (fromDate == null || toDate == null) { | ||
| return true; // Consider null dates valid; use @NotNull for null checks if needed | ||
| } | ||
|
|
||
| boolean isValid = switch (fromDate) { | ||
| case LocalDate from when toDate instanceof LocalDate to -> | ||
| allowEmptyRange ? (to.isEqual(from) || to.isAfter(from)) : to.isAfter(from); | ||
| case LocalDateTime from when toDate instanceof LocalDateTime to -> | ||
| allowEmptyRange ? (to.isEqual(from) || to.isAfter(from)) : to.isAfter(from); | ||
| case OffsetDateTime from when toDate instanceof OffsetDateTime to -> | ||
| allowEmptyRange ? (to.isEqual(from) || to.isAfter(from)) : to.isAfter(from); | ||
| case ZonedDateTime from when toDate instanceof ZonedDateTime to -> | ||
| allowEmptyRange ? (to.isEqual(from) || to.isAfter(from)) : to.isAfter(from); | ||
| case Instant from when toDate instanceof Instant to -> | ||
| allowEmptyRange ? (to.equals(from) || to.isAfter(from)) : to.isAfter(from); | ||
| default -> throw new IllegalArgumentException("Unsupported date data types!"); | ||
| }; | ||
|
|
||
| if (!isValid) { | ||
| context.disableDefaultConstraintViolation(); | ||
| context | ||
| .buildConstraintViolationWithTemplate(message) | ||
| .addPropertyNode(toDateField) | ||
| .addConstraintViolation(); | ||
| } | ||
|
|
||
| return isValid; | ||
| } | ||
| } |
100 changes: 100 additions & 0 deletions
100
...java/it/aboutbits/springboot/toolbox/validation/validator/ValidNumericRangeValidator.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| package it.aboutbits.springboot.toolbox.validation.validator; | ||
|
|
||
| import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal; | ||
| import it.aboutbits.springboot.toolbox.validation.annotation.ValidNumericRange; | ||
| import jakarta.validation.ConstraintValidator; | ||
| import jakarta.validation.ConstraintValidatorContext; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.jspecify.annotations.NullMarked; | ||
| import org.jspecify.annotations.Nullable; | ||
| import org.springframework.beans.PropertyAccessorFactory; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.math.BigInteger; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @NullMarked | ||
| public class ValidNumericRangeValidator implements ConstraintValidator<ValidNumericRange, Object> { | ||
| @Nullable | ||
| private String lowerBoundField; | ||
| @Nullable | ||
| private String upperBoundField; | ||
| @Nullable | ||
| private String message; | ||
| private boolean allowEqualValues = true; | ||
|
|
||
| @Override | ||
| public void initialize(ValidNumericRange constraintAnnotation) { | ||
| this.lowerBoundField = constraintAnnotation.lowerBoundField(); | ||
| this.upperBoundField = constraintAnnotation.upperBoundField(); | ||
| this.message = constraintAnnotation.message(); | ||
| this.allowEqualValues = constraintAnnotation.allowEqualValues(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isValid(Object value, ConstraintValidatorContext context) { | ||
| if (lowerBoundField == null || upperBoundField == null || message == null) { | ||
| throw new IllegalStateException("All fields must be set (lowerBoundField, upperBoundField, message)."); | ||
| } | ||
|
|
||
| var propertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(value); | ||
| var lowerBound = propertyAccessor.getPropertyValue(lowerBoundField); | ||
| var upperBound = propertyAccessor.getPropertyValue(upperBoundField); | ||
|
|
||
| if (lowerBound == null || upperBound == null) { | ||
| return true; // Consider null values valid; use @NotNull for null checks if needed | ||
| } | ||
|
|
||
| var isValid = false; | ||
|
|
||
| if (lowerBound instanceof ScaledBigDecimal lower && upperBound instanceof ScaledBigDecimal upper) { | ||
| isValid = compareValues(lower.compareTo(upper)); | ||
| } else if (lowerBound instanceof ScaledBigDecimal || upperBound instanceof ScaledBigDecimal) { | ||
| throw new IllegalArgumentException("Both fields must be of the same type!"); | ||
| } else { | ||
| if (!(lowerBound instanceof Number) || !(upperBound instanceof Number)) { | ||
| throw new IllegalArgumentException("Both fields must be numeric types!"); | ||
| } | ||
|
|
||
| isValid = compareNumbers((Number) lowerBound, (Number) upperBound); | ||
| } | ||
|
|
||
| if (!isValid) { | ||
| context.disableDefaultConstraintViolation(); | ||
| context | ||
| .buildConstraintViolationWithTemplate(message) | ||
| .addPropertyNode(upperBoundField) | ||
| .addConstraintViolation(); | ||
| } | ||
|
|
||
| return isValid; | ||
| } | ||
|
|
||
| private boolean compareNumbers(Number lowerBound, Number upperBound) { | ||
| // Convert both numbers to BigDecimal for accurate comparison | ||
| var lower = convertToBigDecimal(lowerBound); | ||
| var upper = convertToBigDecimal(upperBound); | ||
|
|
||
| return compareValues(lower.compareTo(upper)); | ||
| } | ||
|
|
||
| private boolean compareValues(int comparisonResult) { | ||
| if (allowEqualValues) { | ||
| return comparisonResult <= 0; // lower <= upper | ||
| } else { | ||
| return comparisonResult < 0; // lower < upper | ||
| } | ||
| } | ||
|
|
||
| private BigDecimal convertToBigDecimal(Number number) { | ||
| if (number instanceof BigDecimal bigDecimal) { | ||
| return bigDecimal; | ||
| } else if (number instanceof BigInteger bigInteger) { | ||
| return new BigDecimal(bigInteger); | ||
| } else if (number instanceof Double || number instanceof Float) { | ||
| return BigDecimal.valueOf(number.doubleValue()); | ||
| } else { | ||
| return BigDecimal.valueOf(number.longValue()); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.