Skip to content

Commit

Permalink
Add iterable objects validation (halo-dev#970)
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnNiang authored Jul 13, 2020
1 parent 83d4ca1 commit 7a71c85
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 11 deletions.
59 changes: 48 additions & 11 deletions src/main/java/run/halo/app/utils/ValidationUtils.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package run.halo.app.utils;

import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.FieldError;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Object validation utilities.
Expand All @@ -30,7 +29,7 @@ private ValidationUtils() {
* @return validator
*/
@NonNull
public static Validator getValidatorOrCreate() {
public static Validator getValidator() {
if (VALIDATOR == null) {
synchronized (ValidationUtils.class) {
if (VALIDATOR == null) {
Expand All @@ -52,17 +51,55 @@ public static Validator getValidatorOrCreate() {
*/
public static void validate(Object obj, Class<?>... groups) {

Validator validator = getValidatorOrCreate();
Validator validator = getValidator();

// Validate the object
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj, groups);
if (obj instanceof Iterable) {
// validate for iterable
validate((Iterable<?>) obj, groups);
} else {
// validate the non-iterable object
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj, groups);

if (!CollectionUtils.isEmpty(constraintViolations)) {
// If contain some errors then throw constraint violation exception
throw new ConstraintViolationException(constraintViolations);
if (!CollectionUtils.isEmpty(constraintViolations)) {
// If contain some errors then throw constraint violation exception
throw new ConstraintViolationException(constraintViolations);
}
}
}

/**
* Validates iterable objects.
*
* @param objs iterable objects could be null
* @param groups validation groups
*/
public static void validate(@Nullable Iterable<?> objs, @Nullable Class<?>... groups) {
if (objs == null) {
return;
}

// get validator
Validator validator = getValidator();

// wrap index
AtomicInteger i = new AtomicInteger(0);
final Set<ConstraintViolation<?>> allViolations = new LinkedHashSet<>();
objs.forEach(obj -> {
int index = i.getAndIncrement();
Set<? extends ConstraintViolation<?>> violations = validator.validate(obj, groups);
violations.forEach(violation -> {
Path path = violation.getPropertyPath();
if (path instanceof PathImpl) {
PathImpl pathImpl = (PathImpl) path;
pathImpl.makeLeafNodeIterableAndSetIndex(index);
}
allViolations.add(violation);
});
});
if (!CollectionUtils.isEmpty(allViolations)) {
throw new ConstraintViolationException(allViolations);
}
}

/**
* 将字段验证错误转换为标准的map型,key:value = field:message
Expand Down
84 changes: 84 additions & 0 deletions src/test/java/run/halo/app/utils/ValidationUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package run.halo.app.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.jupiter.api.Test;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.validation.constraints.NotBlank;
import java.util.*;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
* Validation utils test.
*
* @author johnniang
*/
class ValidationUtilsTest {

Validator validator = ValidationUtils.getValidator();

@Test
void validateObjectTest() {
Car car = new Car(null);
Set<ConstraintViolation<Car>> violations = validator.validate(car);
validateObjectAssert(violations);

ConstraintViolationException exception = assertThrows(ConstraintViolationException.class,
() -> ValidationUtils.validate(car));
validateObjectAssert(exception.getConstraintViolations());
}

void validateObjectAssert(Set<? extends ConstraintViolation<?>> violations) {
assertEquals(1, violations.size());
ConstraintViolation<?> violation = violations.iterator().next();
assertEquals("name", violation.getPropertyPath().toString());
assertEquals("Name must not be blank", violation.getMessage());
}

@Test
void validateListTest() {
List<Car> cars = Arrays.asList(new Car(""),
new Car("car name"),
new Car(null));

ConstraintViolationException exception = assertThrows(ConstraintViolationException.class,
() -> ValidationUtils.validate(cars));

validateIteratorTest(exception.getConstraintViolations());
}

void validateIteratorTest(Set<? extends ConstraintViolation<?>> violations) {
assertEquals(2, violations.size());

LinkedList<? extends ConstraintViolation<?>> violationList = new LinkedList<>(violations);
violationList.sort(Comparator.comparing(v -> v.getPropertyPath().toString()));

// get first violation
ConstraintViolation<?> firstViolation = violationList.get(0);
// get second violation
ConstraintViolation<?> secondViolation = violationList.get(1);

assertEquals("name[0]", firstViolation.getPropertyPath().toString());
assertEquals("name[2]", secondViolation.getPropertyPath().toString());
}

/**
* Car entity.
*
* @author johnniang
*/
@Data
@AllArgsConstructor
static class Car {

@NotBlank(message = "Name must not be blank")
private String name;

}

}

0 comments on commit 7a71c85

Please sign in to comment.