Skip to content

Commit

Permalink
Add new methods to KiwiValidations (#752)
Browse files Browse the repository at this point in the history
* Add methods that validate and throw ConstraintViolationException if
  validation fails
* Add methods that are intended to validate method arguments and throw
  IllegalArgumentException if validation fails.
* Update javadoc of KiwiPreconditions to mention the argument validation
  methods in KiwiValidations

Closes #750
Closes #751
  • Loading branch information
sleberknight authored Jul 18, 2022
1 parent 85a7d12 commit c776ddf
Show file tree
Hide file tree
Showing 3 changed files with 588 additions and 8 deletions.
5 changes: 4 additions & 1 deletion src/main/java/org/kiwiproject/base/KiwiPreconditions.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
* Static utility methods similar to those found in {@link Preconditions}, but with a lovely
* Kiwi flavor to them. That class has good documentation, so go read it if you need more information on the
* intent and general usage.
* <p>
* If you're looking for preconditions related to validating arguments using Jakarta Beans Validation, they
* are in {@link org.kiwiproject.validation.KiwiValidations KiwiValidations}.
*
* @implNote Many of the methods in this class use Lombok's {@link SneakyThrows} so that methods do not need to declare
* @implNote Several methods in this class use Lombok's {@link SneakyThrows} so that they do not need to declare
* that they throw exceptions of type T, <em>for the case that T is a checked exception</em>. Read more details about
* how this works in {@link SneakyThrows}. Most notably, this should give you more insight into how the JVM (versus
* Java the language) actually work: <em>"The JVM does not check for the consistency of the checked exception system;
Expand Down
239 changes: 239 additions & 0 deletions src/main/java/org/kiwiproject/validation/KiwiValidations.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package org.kiwiproject.validation;

import static org.kiwiproject.base.KiwiStrings.format;
import static org.kiwiproject.collect.KiwiSets.isNotNullOrEmpty;
import static org.kiwiproject.collect.KiwiSets.isNullOrEmpty;

import com.google.common.base.Preconditions;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.kiwiproject.base.KiwiStrings;
import org.kiwiproject.reflect.KiwiReflection;

import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

/**
* Static utilities related to Jakarta Bean Validation (formerly Java Bean Validation).
Expand Down Expand Up @@ -92,6 +101,236 @@ public static <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... gro
return validatorInstance.validate(object, groupClasses);
}

/**
* Validate the given object using the singleton validator instance against the {@link Default} group, and throw
* a {@link ConstraintViolationException} if validation fails.
*
* @param object the object to validate
* @param <T> the object type
*/
public static <T> void validateThrowing(T object) {
var violations = KiwiValidations.validate(object);
throwConstraintViolationExceptionIfNotEmpty(violations);
}

/**
* Validate the given object using the singleton validator instance against the specified validation groups, and
* throw a {@link ConstraintViolationException} if validation fails.
*
* @param object the object to validate
* @param groupClasses zero or more validation group classes
* @param <T> the object type
*/
public static <T> void validateThrowing(T object, Class<?>... groupClasses) {
var violations = KiwiValidations.validate(object, groupClasses);
throwConstraintViolationExceptionIfNotEmpty(violations);
}

/**
* If the set of constraint violations is not empty, throw a {@link ConstraintViolationException}.
*
* @param violations the constraint violations
* @param <T> the object type
*/
public static <T> void throwConstraintViolationExceptionIfNotEmpty(Set<ConstraintViolation<T>> violations) {
if (isNotNullOrEmpty(violations)) {
throw new ConstraintViolationException(violations);
}
}

/**
* Validate the given object using the singleton validator instance against the {@link Default} group.
* If the argument is not valid, throws an {@link IllegalArgumentException}. The exception message is
* supplied by {@link #checkArgumentNoViolations(Set)}.
*
* @param object the object to validate
* @param <T> the object type
* @see #checkArgumentNoViolations(Set)
*/
public static <T> void checkArgumentValid(T object) {
var violations = KiwiValidations.validate(object);
checkArgumentNoViolations(violations);
}

/**
* Validate the given object using the singleton validator instance against the {@link Default} group.
* If the argument is not valid, throws an {@link IllegalArgumentException}.
*
* @param object the object to validate
* @param errorMessage the error message for the exception
* @param <T> the object type
*/
public static <T> void checkArgumentValid(T object, String errorMessage) {
var violations = KiwiValidations.validate(object);
checkArgumentNoViolations(violations, errorMessage);
}

/**
* Validate the given object using the singleton validator instance against the {@link Default} group.
* If the argument is not valid, throws an {@link IllegalArgumentException}.
*
* @param object the object to validate
* @param errorMessageTemplate a template for the exception message should the check fail, according to how
* {@link KiwiStrings#format(String, Object...)} handles placeholders
* @param errorMessageArgs the arguments to be substituted into the message template. Arguments
* are converted to Strings using {@link String#valueOf(Object)}.
* @param <T> the object type
*/
public static <T> void checkArgumentValid(T object, String errorMessageTemplate, Object... errorMessageArgs) {
var violations = KiwiValidations.validate(object);
checkArgumentNoViolations(violations, errorMessageTemplate, errorMessageArgs);
}

/**
* Validate the given object using the singleton validator instance against the {@link Default} group.
* If the argument is not valid, throws an {@link IllegalArgumentException}.
*
* @param object the object to validate
* @param errorMessageCreator a Function that transforms constraint violations into an error message for the exception
* @param <T> the object type
*/
public static <T> void checkArgumentValid(T object,
Function<Set<ConstraintViolation<T>>, String> errorMessageCreator) {
var violations = KiwiValidations.validate(object);
checkArgumentNoViolations(violations, errorMessageCreator);
}

/**
* Validate the given object using the singleton validator instance against the specified validation groups.
* If the argument is not valid, throws an {@link IllegalArgumentException}. The exception message is
* supplied by {@link #checkArgumentNoViolations(Set)}.
*
* @param object the object to validate
* @param groupClasses zero or more validation group classes
* @param <T> the object type
* @see #checkArgumentNoViolations(Set)
*/
public static <T> void checkArgumentValid(T object, Class<?>... groupClasses) {
var violations = KiwiValidations.validate(object, groupClasses);
checkArgumentNoViolations(violations);
}

/**
* Validate the given object using the singleton validator instance against the specified validation groups.
* If the argument is not valid, throws an {@link IllegalArgumentException}.
*
* @param object the object to validate
* @param errorMessage the error message for the exception
* @param groupClasses zero or more validation group classes
* @param <T> the object type
*/
public static <T> void checkArgumentValid(T object, String errorMessage, Class<?>... groupClasses) {
var violations = KiwiValidations.validate(object, groupClasses);
checkArgumentNoViolations(violations, errorMessage);
}

/**
* Validate the given object using the singleton validator instance against the specified validation groups.
* If the argument is not valid, throws an {@link IllegalArgumentException}.
*
* @param object the object to validate
* @param errorMessageTemplate a template for the exception message should the check fail, according to how
* {@link KiwiStrings#format(String, Object...)} handles placeholders
* @param errorMessageArgs the arguments to be substituted into the message template. Arguments
* are converted to Strings using {@link String#valueOf(Object)}.
* @param groupClasses zero or more validation group classes
* @param <T> the object type
*/
public static <T> void checkArgumentValid(T object,
String errorMessageTemplate,
List<Object> errorMessageArgs,
Class<?>... groupClasses) {
var violations = KiwiValidations.validate(object, groupClasses);
checkArgumentNoViolations(violations, errorMessageTemplate, errorMessageArgs.toArray());
}

/**
* Validate the given object using the singleton validator instance against the specified validation groups.
* If the argument is not valid, throws an {@link IllegalArgumentException}.
*
* @param object the object to validate
* @param errorMessageCreator a Function that transforms constraint violations into an error message for the exception
* @param groupClasses zero or more validation group classes
* @param <T> the object type
*/
public static <T> void checkArgumentValid(T object,
Function<Set<ConstraintViolation<T>>, String> errorMessageCreator,
Class<?>... groupClasses) {
var violations = KiwiValidations.validate(object, groupClasses);
checkArgumentNoViolations(violations, errorMessageCreator);
}

/**
* Ensures the set of constraint violations is empty, throwing an {@link IllegalArgumentException} otherwise.
* The exception message is supplied by {@link KiwiConstraintViolations#simpleCombinedErrorMessageOrNull(Set)}.
*
* @param violations the set of constraint violations to check
* @param <T> the object type
*/
public static <T> void checkArgumentNoViolations(Set<ConstraintViolation<T>> violations) {
checkArgumentNoViolations(violations, KiwiConstraintViolations::simpleCombinedErrorMessageOrNull);
}

/**
* Ensures the set of constraint violations is empty, throwing an {@link IllegalArgumentException} otherwise.
*
* @param violations the set of constraint violations to check
* @param errorMessage the error message for the exception
* @param <T> the object type
*/
public static <T> void checkArgumentNoViolations(Set<ConstraintViolation<T>> violations,
String errorMessage) {
Preconditions.checkArgument(isNullOrEmpty(violations), errorMessage);
}

/**
* Ensures the set of constraint violations is empty, throwing an {@link IllegalArgumentException} otherwise.
*
* @param violations the set of constraint violations to check
* @param errorMessageTemplate a template for the exception message should the check fail, according to how
* {@link KiwiStrings#format(String, Object...)} handles placeholders
* @param errorMessageArgs the arguments to be substituted into the message template. Arguments
* are converted to Strings using {@link String#valueOf(Object)}.
* @param <T> the object type
*/
public static <T> void checkArgumentNoViolations(Set<ConstraintViolation<T>> violations,
String errorMessageTemplate,
Object... errorMessageArgs) {
if (isNotNullOrEmpty(violations)) {
var errorMessage = format(errorMessageTemplate, errorMessageArgs);
throw new IllegalArgumentException(errorMessage);
}
}

/**
* Ensures the set of constraint violations is empty, throwing an {@link IllegalArgumentException} otherwise.
*
* @param violations the set of constraint violations to check
* @param errorMessageCreator a Function that transforms constraint violations into an error message for the exception
* @param <T> the object type
*/
public static <T> void checkArgumentNoViolations(Set<ConstraintViolation<T>> violations,
Function<Set<ConstraintViolation<T>>, String> errorMessageCreator) {

if (isNotNullOrEmpty(violations)) {
var errorMessage = getErrorMessageOrFallback(violations, errorMessageCreator);
throw new IllegalArgumentException(errorMessage);
}
}

private static <T> String getErrorMessageOrFallback(Set<ConstraintViolation<T>> violations,
Function<Set<ConstraintViolation<T>>, String> errorMessageCreator) {
try {
return errorMessageCreator.apply(violations);
} catch (Exception e) {
LOG.warn("errorMessageCreator threw exception creating message. Falling back to default message.", e);
}

return KiwiConstraintViolations
.simpleCombinedErrorMessageOrEmpty(violations)
.orElse("Argument contained one or more constraint violations");
}

/**
* Adds an error to the {@link ConstraintValidatorContext} using the specified template, thereby overriding
* the constraint's default message.
Expand Down
Loading

0 comments on commit c776ddf

Please sign in to comment.