diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/RepeatedField.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/RepeatedField.java
new file mode 100644
index 0000000..e9fbce9
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/RepeatedField.java
@@ -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();
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidDateRange.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidDateRange.java
new file mode 100644
index 0000000..dba23ce
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidDateRange.java
@@ -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.
+ * 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.
+ *
+ * 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();
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidNumericRange.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidNumericRange.java
new file mode 100644
index 0000000..11c3ce3
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidNumericRange.java
@@ -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.
+ * 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.
+ *
+ * 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();
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidPassword.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidPassword.java
new file mode 100644
index 0000000..2f84021
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidPassword.java
@@ -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 {};
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/RepeatedFieldValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/RepeatedFieldValidator.java
new file mode 100644
index 0000000..2617726
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/RepeatedFieldValidator.java
@@ -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 {
+ 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;
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidDateRangeValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidDateRangeValidator.java
new file mode 100644
index 0000000..5db440b
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidDateRangeValidator.java
@@ -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 {
+ @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;
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidNumericRangeValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidNumericRangeValidator.java
new file mode 100644
index 0000000..27b5ab5
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidNumericRangeValidator.java
@@ -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 {
+ @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());
+ }
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidPasswordValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidPasswordValidator.java
new file mode 100644
index 0000000..1e02cb2
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/ValidPasswordValidator.java
@@ -0,0 +1,56 @@
+package it.aboutbits.springboot.toolbox.validation.validator;
+
+import it.aboutbits.springboot.toolbox.validation.annotation.ValidPassword;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import org.apache.commons.lang3.StringUtils;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@NullMarked
+public class ValidPasswordValidator implements ConstraintValidator {
+ private @Nullable String message;
+ private @Nullable String lengthMessage;
+ private int minLength = 8;
+ private int maxLength = 50;
+
+ @Override
+ public void initialize(ValidPassword constraintAnnotation) {
+ this.message = constraintAnnotation.message();
+ this.lengthMessage = constraintAnnotation.lengthMessage();
+ this.minLength = constraintAnnotation.minLength();
+ this.maxLength = constraintAnnotation.maxLength();
+ }
+
+ @Override
+ public boolean isValid(@Nullable String password, ConstraintValidatorContext context) {
+ if (message == null || lengthMessage == null) {
+ throw new IllegalStateException("Both message and lengthMessage must be set.");
+ }
+
+ if (minLength > maxLength) {
+ throw new IllegalStateException("minLength must be less than maxLength.");
+ }
+
+ if (password == null) {
+ return true; // Consider null as valid; use @NotNull for null checks if needed
+ }
+
+ var length = password.length();
+ var lengthValid = length >= minLength && length <= maxLength;
+
+ if (StringUtils.isNotBlank(password) && lengthValid) {
+ return true;
+ }
+ context.disableDefaultConstraintViolation();
+ if (!lengthValid) {
+ context.buildConstraintViolationWithTemplate(lengthMessage)
+ .addConstraintViolation();
+ } else {
+ context.buildConstraintViolationWithTemplate(message)
+ .addConstraintViolation();
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/MaxScaledBigDecimalValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MaxScaledBigDecimalValidator.java
similarity index 91%
rename from src/main/java/it/aboutbits/springboot/toolbox/validation/validator/MaxScaledBigDecimalValidator.java
rename to src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MaxScaledBigDecimalValidator.java
index 1bdc7ad..558d97c 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/MaxScaledBigDecimalValidator.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MaxScaledBigDecimalValidator.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.ConstraintValidator;
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/MinScaledBigDecimalValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MinScaledBigDecimalValidator.java
similarity index 92%
rename from src/main/java/it/aboutbits/springboot/toolbox/validation/validator/MinScaledBigDecimalValidator.java
rename to src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MinScaledBigDecimalValidator.java
index 18d50fc..d6dce1d 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/MinScaledBigDecimalValidator.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MinScaledBigDecimalValidator.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.ConstraintValidator;
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeOrZeroScaledBigDecimalValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeOrZeroScaledBigDecimalValidator.java
similarity index 89%
rename from src/main/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeOrZeroScaledBigDecimalValidator.java
rename to src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeOrZeroScaledBigDecimalValidator.java
index 827f52d..1bc95b2 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeOrZeroScaledBigDecimalValidator.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeOrZeroScaledBigDecimalValidator.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.ConstraintValidator;
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeScaledBigDecimalValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeScaledBigDecimalValidator.java
similarity index 89%
rename from src/main/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeScaledBigDecimalValidator.java
rename to src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeScaledBigDecimalValidator.java
index b4617fd..0eccaeb 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeScaledBigDecimalValidator.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeScaledBigDecimalValidator.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.ConstraintValidator;
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveOrZeroScaledBigDecimalValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveOrZeroScaledBigDecimalValidator.java
similarity index 89%
rename from src/main/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveOrZeroScaledBigDecimalValidator.java
rename to src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveOrZeroScaledBigDecimalValidator.java
index a5c5b26..8918867 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveOrZeroScaledBigDecimalValidator.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveOrZeroScaledBigDecimalValidator.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.ConstraintValidator;
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveScaledBigDecimalValidator.java b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveScaledBigDecimalValidator.java
similarity index 89%
rename from src/main/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveScaledBigDecimalValidator.java
rename to src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveScaledBigDecimalValidator.java
index c2a6e18..2371386 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveScaledBigDecimalValidator.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveScaledBigDecimalValidator.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.ConstraintValidator;
diff --git a/src/main/resources/META-INF/validation/scaled-big-decimal.xml b/src/main/resources/META-INF/validation/scaled-big-decimal.xml
index 9d80254..37a972b 100644
--- a/src/main/resources/META-INF/validation/scaled-big-decimal.xml
+++ b/src/main/resources/META-INF/validation/scaled-big-decimal.xml
@@ -4,42 +4,42 @@
- it.aboutbits.springboot.toolbox.validation.validator.MaxScaledBigDecimalValidator
+ it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal.MaxScaledBigDecimalValidator
- it.aboutbits.springboot.toolbox.validation.validator.MinScaledBigDecimalValidator
+ it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal.MinScaledBigDecimalValidator
- it.aboutbits.springboot.toolbox.validation.validator.NegativeScaledBigDecimalValidator
+ it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal.NegativeScaledBigDecimalValidator
- it.aboutbits.springboot.toolbox.validation.validator.NegativeOrZeroScaledBigDecimalValidator
+ it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal.NegativeOrZeroScaledBigDecimalValidator
- it.aboutbits.springboot.toolbox.validation.validator.PositiveScaledBigDecimalValidator
+ it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal.PositiveScaledBigDecimalValidator
- it.aboutbits.springboot.toolbox.validation.validator.PositiveOrZeroScaledBigDecimalValidator
+ it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal.PositiveOrZeroScaledBigDecimalValidator
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/RepeatedFieldTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/RepeatedFieldTest.java
new file mode 100644
index 0000000..1c21d9d
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/RepeatedFieldTest.java
@@ -0,0 +1,101 @@
+package it.aboutbits.springboot.toolbox.validation.annotation;
+
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@NullMarked
+class RepeatedFieldTest {
+ private static final ValidatorFactory VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory();
+ private final Validator validator = VALIDATOR_FACTORY.getValidator();
+
+ @Test
+ void testIsValidWithMatchingFields() {
+ var testObject = new TestObject("password123", "password123");
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithNonMatchingFields() {
+ var testObject = new TestObject("password123", "differentPassword");
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getPropertyPath().toString()).isEqualTo("repeatPassword");
+ assertThat(violation.getMessageTemplate()).isEqualTo("{shared.error.validation.repetitionMismatch}");
+ });
+ }
+
+ @Test
+ void testIsValidWithNullValues() {
+ var testObject = new TestObject(null, null);
+
+ var result = validator.validate(testObject);
+
+ // Based on implementation: fieldValue != null && fieldValue.equals(fieldMatchValue)
+ // So null, null should be INVALID
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void testIsValidWithOneNullValue() {
+ var testObject = new TestObject("password123", null);
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void testIsValidWithMultipleAnnotations() {
+ var testObject = new MultipleRepeatedFieldsObject(
+ "email@example.com",
+ "email@example.com",
+ "password",
+ "wrong-password"
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).hasSize(1);
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getPropertyPath().toString()).isEqualTo("repeatPassword");
+ });
+
+ testObject = new MultipleRepeatedFieldsObject(
+ "email@example.com",
+ "wrong-email@example.com",
+ "password",
+ "wrong-password"
+ );
+ result = validator.validate(testObject);
+ assertThat(result).hasSize(2);
+ }
+
+ @RepeatedField(originalField = "password", repeatedField = "repeatPassword")
+ record TestObject(
+ @Nullable String password,
+ @Nullable String repeatPassword
+ ) {
+ }
+
+ @RepeatedField(originalField = "email", repeatedField = "repeatEmail")
+ @RepeatedField(originalField = "password", repeatedField = "repeatPassword")
+ record MultipleRepeatedFieldsObject(
+ @Nullable String email,
+ @Nullable String repeatEmail,
+ @Nullable String password,
+ @Nullable String repeatPassword
+ ) {
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidDateRangeTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidDateRangeTest.java
new file mode 100644
index 0000000..80a859b
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidDateRangeTest.java
@@ -0,0 +1,93 @@
+package it.aboutbits.springboot.toolbox.validation.annotation;
+
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@NullMarked
+class ValidDateRangeTest {
+ private static final ValidatorFactory VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory();
+ private final Validator validator = VALIDATOR_FACTORY.getValidator();
+
+ @Test
+ void testIsValidWithValidRange() {
+ var testObject = new TestObject(
+ LocalDate.of(2023, 1, 1),
+ LocalDate.of(2023, 1, 2)
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithEqualValues() {
+ var testObject = new TestObject(
+ LocalDate.of(2023, 1, 1),
+ LocalDate.of(2023, 1, 1)
+ );
+
+ var result = validator.validate(testObject);
+
+ // Default allowEmptyRange is false
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void testIsValidWithEqualValuesWhenAllowed() {
+ var testObject = new TestObjectEmptyAllowed(
+ LocalDate.of(2023, 1, 1),
+ LocalDate.of(2023, 1, 1)
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithInvalidRange() {
+ var testObject = new TestObject(
+ LocalDate.of(2023, 1, 2),
+ LocalDate.of(2023, 1, 1)
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getPropertyPath().toString()).isEqualTo("toDate");
+ });
+ }
+
+ @Test
+ void testIsValidWithNullValues() {
+ var testObject = new TestObject(null, null);
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @ValidDateRange(fromDateField = "fromDate", toDateField = "toDate")
+ record TestObject(
+ @Nullable LocalDate fromDate,
+ @Nullable LocalDate toDate
+ ) {
+ }
+
+ @ValidDateRange(fromDateField = "fromDate", toDateField = "toDate", allowEmptyRange = true)
+ record TestObjectEmptyAllowed(
+ @Nullable LocalDate fromDate,
+ @Nullable LocalDate toDate
+ ) {
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidNumericRangeTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidNumericRangeTest.java
new file mode 100644
index 0000000..1e38fad
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidNumericRangeTest.java
@@ -0,0 +1,109 @@
+package it.aboutbits.springboot.toolbox.validation.annotation;
+
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@NullMarked
+class ValidNumericRangeTest {
+ private static final ValidatorFactory VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory();
+ private final Validator validator = VALIDATOR_FACTORY.getValidator();
+
+ @Test
+ void testIsValidWithScaledBigDecimalValidRange() {
+ var testObject = new TestObject(
+ ScaledBigDecimal.valueOf(10.5),
+ ScaledBigDecimal.valueOf(20.5)
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithScaledBigDecimalEqualValues() {
+ var testObject = new TestObject(
+ ScaledBigDecimal.valueOf(15.0),
+ ScaledBigDecimal.valueOf(15.0)
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithScaledBigDecimalEqualValuesWhenNotAllowed() {
+ var testObject = new TestObjectNoEqual(
+ ScaledBigDecimal.valueOf(15.0),
+ ScaledBigDecimal.valueOf(15.0)
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ }
+
+ @Test
+ void testIsValidWithScaledBigDecimalInvalidRange() {
+ var testObject = new TestObject(
+ ScaledBigDecimal.valueOf(30.5),
+ ScaledBigDecimal.valueOf(20.5)
+ );
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getPropertyPath().toString()).isEqualTo("upperBound");
+ });
+ }
+
+ @Test
+ void testIsValidWithNullValues() {
+ var testObject = new TestObject(null, null);
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithNullLowerBound() {
+ var testObject = new TestObject(null, ScaledBigDecimal.valueOf(20.5));
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithNullUpperBound() {
+ var testObject = new TestObject(ScaledBigDecimal.valueOf(10.5), null);
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @ValidNumericRange(lowerBoundField = "lowerBound", upperBoundField = "upperBound")
+ record TestObject(
+ @Nullable Object lowerBound,
+ @Nullable Object upperBound
+ ) {
+ }
+
+ @ValidNumericRange(lowerBoundField = "lowerBound", upperBoundField = "upperBound", allowEqualValues = false)
+ record TestObjectNoEqual(
+ @Nullable Object lowerBound,
+ @Nullable Object upperBound
+ ) {
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidPasswordTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidPasswordTest.java
new file mode 100644
index 0000000..8c2770f
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/annotation/ValidPasswordTest.java
@@ -0,0 +1,123 @@
+package it.aboutbits.springboot.toolbox.validation.annotation;
+
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@NullMarked
+class ValidPasswordTest {
+ private static final ValidatorFactory VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory();
+ private final Validator validator = VALIDATOR_FACTORY.getValidator();
+
+ @Test
+ void testIsValidWithValidPassword() {
+ var testObject = new TestObject("validPassword123");
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithNullPassword() {
+ var testObject = new TestObject(null);
+
+ var result = validator.validate(testObject);
+
+ // Password null is considered valid by the validator
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testIsValidWithBlankPassword() {
+ var testObject = new TestObject("");
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getMessageTemplate()).isEqualTo("{shared.error.validation.invalidPassword.length}");
+ });
+
+ testObject = new TestObject(" ");
+ result = validator.validate(testObject);
+ assertThat(result).isNotEmpty();
+ // For blank but within length (e.g. 8 spaces), it should trigger the general message
+ }
+
+ @Test
+ void testIsValidWithTooShortPassword() {
+ var testObject = new TestObject("short");
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getMessageTemplate()).isEqualTo("{shared.error.validation.invalidPassword.length}");
+ });
+ }
+
+ @Test
+ void testIsValidWithTooLongPassword() {
+ var testObject = new TestObject("thisPasswordIsWayTooLongAndShouldFailValidationBecauseItExceedsFiftyCharacters");
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getMessageTemplate()).isEqualTo("{shared.error.validation.invalidPassword.length}");
+ });
+ }
+
+ @Test
+ void testIsValidWithCustomLengths() {
+ var testObject = new CustomLengthObject("pass"); // 4 chars, min is 5
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getMessageTemplate()).isEqualTo("too short");
+ });
+
+ testObject = new CustomLengthObject("password123"); // 11 chars, max is 10
+ result = validator.validate(testObject);
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getMessageTemplate()).isEqualTo("too short"); // The message template in CustomLengthObject is "too short" for lengthMessage
+ });
+ }
+
+ @Test
+ void testIsValidWithSpacesOnly() {
+ // " " is 8 spaces, so lengthValid is true, but StringUtils.isNotBlank is false
+ var testObject = new TestObject(" ");
+
+ var result = validator.validate(testObject);
+
+ assertThat(result).isNotEmpty();
+ assertThat(result).anySatisfy(violation -> {
+ assertThat(violation.getMessageTemplate()).isEqualTo("{shared.error.validation.invalidPassword}");
+ });
+ }
+
+ record TestObject(
+ @ValidPassword @Nullable String password
+ ) {
+ }
+
+ record CustomLengthObject(
+ @ValidPassword(
+ minLength = 5,
+ maxLength = 10,
+ lengthMessage = "too short",
+ message = "invalid"
+ ) @Nullable String password
+ ) {
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/MaxScaledBigDecimalValidatorTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MaxScaledBigDecimalValidatorTest.java
similarity index 94%
rename from src/test/java/it/aboutbits/springboot/toolbox/validation/validator/MaxScaledBigDecimalValidatorTest.java
rename to src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MaxScaledBigDecimalValidatorTest.java
index ab9a607..2dda326 100644
--- a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/MaxScaledBigDecimalValidatorTest.java
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MaxScaledBigDecimalValidatorTest.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.Validation;
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/MinScaledBigDecimalValidatorTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MinScaledBigDecimalValidatorTest.java
similarity index 94%
rename from src/test/java/it/aboutbits/springboot/toolbox/validation/validator/MinScaledBigDecimalValidatorTest.java
rename to src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MinScaledBigDecimalValidatorTest.java
index a9aef51..b0db913 100644
--- a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/MinScaledBigDecimalValidatorTest.java
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/MinScaledBigDecimalValidatorTest.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.Validation;
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeOrZeroScaledBigDecimalValidatorTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeOrZeroScaledBigDecimalValidatorTest.java
similarity index 94%
rename from src/test/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeOrZeroScaledBigDecimalValidatorTest.java
rename to src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeOrZeroScaledBigDecimalValidatorTest.java
index 1c567eb..11a09f8 100644
--- a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeOrZeroScaledBigDecimalValidatorTest.java
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeOrZeroScaledBigDecimalValidatorTest.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.Validation;
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeScaledBigDecimalValidatorTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeScaledBigDecimalValidatorTest.java
similarity index 94%
rename from src/test/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeScaledBigDecimalValidatorTest.java
rename to src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeScaledBigDecimalValidatorTest.java
index 6909a7f..0753760 100644
--- a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/NegativeScaledBigDecimalValidatorTest.java
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/NegativeScaledBigDecimalValidatorTest.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.Validation;
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveOrZeroScaledBigDecimalValidatorTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveOrZeroScaledBigDecimalValidatorTest.java
similarity index 94%
rename from src/test/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveOrZeroScaledBigDecimalValidatorTest.java
rename to src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveOrZeroScaledBigDecimalValidatorTest.java
index 84f05e0..948f1d4 100644
--- a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveOrZeroScaledBigDecimalValidatorTest.java
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveOrZeroScaledBigDecimalValidatorTest.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.Validation;
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveScaledBigDecimalValidatorTest.java b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveScaledBigDecimalValidatorTest.java
similarity index 94%
rename from src/test/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveScaledBigDecimalValidatorTest.java
rename to src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveScaledBigDecimalValidatorTest.java
index 71fe925..71214e3 100644
--- a/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/PositiveScaledBigDecimalValidatorTest.java
+++ b/src/test/java/it/aboutbits/springboot/toolbox/validation/validator/scaled_big_decimal/PositiveScaledBigDecimalValidatorTest.java
@@ -1,4 +1,4 @@
-package it.aboutbits.springboot.toolbox.validation.validator;
+package it.aboutbits.springboot.toolbox.validation.validator.scaled_big_decimal;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import jakarta.validation.Validation;