Last active
April 9, 2025 16:06
-
-
Save dhsrocha/2c05b1b3d142dc967492a44ed9e0885c to your computer and use it in GitHub Desktop.
Chainable validation mechanism that allows for the sequential application of predicates to a specified generic subject. Each predicate can be associated with a custom error message, which is used for reporting when a validation fails.
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
import java.util.Objects; | |
import java.util.function.Function; | |
import java.util.function.Predicate; | |
/** | |
* A class that represents a chain of predicates that can be applied to a subject. | |
* <p> | |
* Each predicate can have an associated message that is used for error reporting if the predicate | |
* test fails. | |
* | |
* @param <T> the type of the subject that the predicates operate on. | |
* @author <a href="mailto:[email protected]">Diego Rocha</a> | |
*/ | |
final class Validator<T> { | |
private final String message; | |
private final Predicate<T> test; | |
private final Validator<T> previous; | |
/** | |
* Constructs a new instance of the Chain class. | |
* | |
* @param message the message to display if the predicate test fails. | |
* @param test the predicate to apply to the subject. | |
* @param previous the previous chain in the sequence, or null if this is the first predicate. | |
* @throws NullPointerException if the message or operation is null. | |
*/ | |
private Validator(final String message, final Predicate<T> test, final Validator<T> previous) { | |
this.message = Objects.requireNonNull(message, "Message cannot be null"); | |
this.test = Objects.requireNonNull(test, "Operation cannot be null"); | |
this.previous = previous; | |
} | |
/** | |
* Creates a new chain with the specified message and predicate. | |
* | |
* @param message the message to display if the predicate test fails. | |
* @param test the predicate to apply to the subject. | |
* @param <T> the type of the subject. | |
* @return a new instance. | |
*/ | |
static <T> Validator<T> of(final String message, final Predicate<T> test) { | |
return new Validator<>(message, test, null); | |
} | |
/** | |
* Adds another predicate to the chain. | |
* | |
* @param message the message to display if the predicate test fails. | |
* @param test the predicate to apply to the subject. | |
* @return a new chained instance that includes the previous predicates and the new one. | |
*/ | |
Validator<T> chainTo(final String message, final Predicate<T> test) { | |
return new Validator<>(message, test, this); | |
} | |
/** | |
* Applies the predicates in the chain to the specified subject in sequence. If any predicate | |
* fails, an exception is thrown using the provided supplier function to create the exception with | |
* the corresponding message from the failing predicate. | |
* | |
* @param subject the subject to test against the predicates. | |
* @param supplier a function that creates an exception of type E with a message. | |
* @param <E> the type of exception that can be thrown, which must extend Exception. | |
* @throws E if any predicate fails, an exception created by the supplier is thrown. | |
*/ | |
<E extends Exception> void validate(final T subject, final Function<String, E> supplier) | |
throws E { | |
if (this.previous != null) { | |
this.previous.validate(subject, supplier); | |
} | |
this.check(subject, supplier); | |
} | |
/** | |
* Checks the subject against the current predicate. | |
* | |
* @param subject the subject to test against the predicate. | |
* @param supplier a function that creates an exception of type E with a message. | |
* @param <E> the type of exception that can be thrown, which must extend Exception. | |
* @throws E if the predicate test fails, an exception created by the supplier is thrown. | |
*/ | |
private <E extends Exception> void check(final T subject, final Function<String, E> supplier) | |
throws E { | |
if (!this.test.test(subject)) { | |
throw supplier.apply(message); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment