Skip to content

Instantly share code, notes, and snippets.

@dhsrocha
Last active April 9, 2025 16:06
Show Gist options
  • Save dhsrocha/2c05b1b3d142dc967492a44ed9e0885c to your computer and use it in GitHub Desktop.
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.
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