feat: 添加数组属性校验器

- 新增 `ArrayPropertyValidator` 类,提供针对数组类型的属性校验功能。
- 在 `BaseValidator` 中添加 `ruleForArray` 方法,用于创建数组属性校验器。
This commit is contained in:
zhouxy108 2025-06-01 19:00:10 +08:00
parent 7a9e15fd45
commit 907d883be8
5 changed files with 605 additions and 3 deletions

View File

@ -0,0 +1,206 @@
/*
* Copyright 2023-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.zhouxy.plusone.validator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import xyz.zhouxy.plusone.commons.util.ArrayTools;
/**
* 针对数组类型的属性校验器
*
* <p>
* 内置数组相关的校验规则
*
* @author ZhouXY
*/
public class ArrayPropertyValidator<T, TElement>
extends BasePropertyValidator<T, TElement[], ArrayPropertyValidator<T, TElement>> {
ArrayPropertyValidator(Function<T, TElement[]> getter) {
super(getter);
}
// ================================
// #region - notEmpty
// ================================
/**
* 添加一条校验属性的规则校验属性是否非空
*
* @return 属性校验器
*/
public ArrayPropertyValidator<T, TElement> notEmpty() {
return notEmpty("The input must not be empty.");
}
/**
* 添加一条校验属性的规则校验属性是否非空
*
* @param errMsg 异常信息
* @return 属性校验器
*/
public ArrayPropertyValidator<T, TElement> notEmpty(String errMsg) {
return notEmpty(convertToExceptionFunction(errMsg));
}
/**
* 添加一条校验属性的规则校验属性是否非空
*
* @param <E> 自定义异常类型
* @param e 自定义异常
* @return 属性校验器
*/
public <E extends RuntimeException> ArrayPropertyValidator<T, TElement> notEmpty(
Supplier<E> e) {
return notEmpty(convertToExceptionFunction(e));
}
/**
* 添加一条校验属性的规则校验属性是否非空
*
* @param <E> 自定义异常类型
* @param e 自定义异常
* @return 属性校验器
*/
public <E extends RuntimeException> ArrayPropertyValidator<T, TElement> notEmpty(
Function<TElement[], E> e) {
withRule(ArrayTools::isNotEmpty, e);
return this;
}
// ================================
// #endregion - notEmpty
// ================================
// ================================
// #region - isEmpty
// ================================
/**
* 添加一条校验属性的规则校验属性是否为空
*
* @return 属性校验器
*/
public ArrayPropertyValidator<T, TElement> isEmpty() {
return isEmpty("The input must be empty.");
}
/**
* 添加一条校验属性的规则校验属性是否为空
*
* @param errMsg 异常信息
* @return 属性校验器
*/
public ArrayPropertyValidator<T, TElement> isEmpty(String errMsg) {
return isEmpty(convertToExceptionFunction(errMsg));
}
/**
* 添加一条校验属性的规则校验属性是否为空
*
* @param e 自定义异常
* @return 属性校验器
*/
public <E extends RuntimeException> ArrayPropertyValidator<T, TElement> isEmpty(
Supplier<E> e) {
return isEmpty(convertToExceptionFunction(e));
}
/**
* 添加一条校验属性的规则校验属性是否为空
*
* @param e 自定义异常
* @return 属性校验器
*/
public <E extends RuntimeException> ArrayPropertyValidator<T, TElement> isEmpty(
Function<TElement[], E> e) {
withRule(ArrayTools::isEmpty, e);
return this;
}
// ================================
// #endregion - isEmpty
// ================================
// ================================
// #region - allMatch
// ================================
/**
* 添加一条校验属性的规则校验是否所有元素都满足条件
*
* @param condition 校验规则
* @return 属性校验器
*/
public ArrayPropertyValidator<T, TElement> allMatch(Predicate<TElement> condition) {
return allMatch(condition, convertToExceptionFunction("All elements must match the condition."));
}
/**
* 添加一条校验属性的规则校验是否所有元素都满足条件
*
* @param condition 校验规则
* @param errMsg 异常信息
* @return 属性校验器
*/
public ArrayPropertyValidator<T, TElement> allMatch(Predicate<TElement> condition, String errMsg) {
return allMatch(condition, convertToExceptionFunction(errMsg));
}
/**
* 添加一条校验属性的规则校验是否所有元素都满足条件
*
* @param condition 校验规则
* @param e 自定义异常
* @return 属性校验器
*/
public <E extends RuntimeException> ArrayPropertyValidator<T, TElement> allMatch(
Predicate<TElement> condition, Supplier<E> e) {
return allMatch(condition, convertToExceptionFunction(e));
}
/**
* 添加一条校验属性的规则校验是否所有元素都满足条件
*
* @param condition 校验规则
* @param e 自定义异常
* @return 属性校验器
*/
public <E extends RuntimeException> ArrayPropertyValidator<T, TElement> allMatch(
Predicate<TElement> condition, Function<TElement, E> e) {
withRule(c -> {
for (TElement element : c) {
if (!condition.test(element)) {
throw e.apply(element);
}
}
});
return this;
}
// ================================
// #endregion - allMatch
// ================================
@Override
protected ArrayPropertyValidator<T, TElement> thisObject() {
return this;
}
}

View File

@ -247,6 +247,18 @@ public abstract class BaseValidator<T> implements IValidator<T> {
return validator;
}
/**
* 添加一个针对数组属性的校验器
*
* @param getter 获取属性值的函数
* @return 集合属性校验器
*/
protected final <E> ArrayPropertyValidator<T, E> ruleForArray(Function<T, E[]> getter) {
ArrayPropertyValidator<T, E> validator = new ArrayPropertyValidator<>(getter);
this.rules.add(validator::validate);
return validator;
}
/**
* 添加一个针对二元组的校验器
* @param <V1> 第一个元素的类型

View File

@ -16,6 +16,7 @@
package xyz.zhouxy.plusone.example;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
/**
@ -30,13 +31,14 @@ public class ExampleCommand {
private LocalDateTime dateTimeProperty;
private Foo objectProperty;
private List<String> stringListProperty;
private String[] stringArrayProperty;
public ExampleCommand() {
}
public ExampleCommand(Boolean boolProperty, Integer intProperty, Long longProperty, Double doubleProperty,
String stringProperty, LocalDateTime dateTimeProperty, Foo objectProperty,
List<String> stringListProperty) {
List<String> stringListProperty, String[] stringArrayProperty) {
this.boolProperty = boolProperty;
this.intProperty = intProperty;
this.longProperty = longProperty;
@ -45,6 +47,7 @@ public class ExampleCommand {
this.dateTimeProperty = dateTimeProperty;
this.objectProperty = objectProperty;
this.stringListProperty = stringListProperty;
this.stringArrayProperty = stringArrayProperty;
}
public Boolean getBoolProperty() {
@ -111,11 +114,20 @@ public class ExampleCommand {
this.stringListProperty = stringListProperty;
}
public String[] getStringArrayProperty() {
return stringArrayProperty;
}
public void setStringArrayProperty(String[] stringArrayProperty) {
this.stringArrayProperty = stringArrayProperty;
}
@Override
public String toString() {
return "ExampleCommand [boolProperty=" + boolProperty + ", intProperty=" + intProperty + ", longProperty="
+ longProperty + ", doubleProperty=" + doubleProperty + ", stringProperty=" + stringProperty
+ ", dateTimeProperty=" + dateTimeProperty + ", objectProperty=" + objectProperty
+ ", stringListProperty=" + stringListProperty + "]";
+ ", stringListProperty=" + stringListProperty + ", stringArrayProperty="
+ Arrays.toString(stringArrayProperty) + "]";
}
}

View File

@ -0,0 +1,366 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.zhouxy.plusone.example.validator;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import xyz.zhouxy.plusone.ExampleException;
import xyz.zhouxy.plusone.example.ExampleCommand;
import xyz.zhouxy.plusone.validator.BaseValidator;
import xyz.zhouxy.plusone.validator.IValidator;
import xyz.zhouxy.plusone.validator.ValidationException;
public class ArrayPropertyValidatorTests {
static final String MESSAGE_NOT_EMPTY = "The stringArrayProperty should not be empty.";
static final String MESSAGE_EMPTY = "The stringArrayProperty should be empty.";
// ================================
// #region - notEmpty
// ================================
@Test
void notEmpty_stringListIsNotEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).notEmpty();
ruleForArray(ExampleCommand::getStringArrayProperty).notEmpty(MESSAGE_NOT_EMPTY);
ruleForArray(ExampleCommand::getStringArrayProperty)
.notEmpty(() -> ExampleException.withMessage(MESSAGE_NOT_EMPTY));
ruleForArray(ExampleCommand::getStringArrayProperty)
.notEmpty(strList -> ExampleException.withMessage(
"The stringArrayProperty should not be empty, but it is %s.", Arrays.toString(strList)));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] {"A", "B", "C"});
assertDoesNotThrow(() -> validator.validate(command));
}
@Test
void notEmpty_default_stringListIsEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).notEmpty();
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] {});
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals("The input must not be empty.", e.getMessage());
}
@Test
void notEmpty_message_stringListIsEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).notEmpty(MESSAGE_NOT_EMPTY);
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] {});
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals(MESSAGE_NOT_EMPTY, e.getMessage());
}
@Test
void notEmpty_exceptionSupplier_stringListIsEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.notEmpty(() -> ExampleException.withMessage(MESSAGE_NOT_EMPTY));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] {});
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals(MESSAGE_NOT_EMPTY, e.getMessage());
}
@Test
void notEmpty_exceptionFunction_stringListIsEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.notEmpty(strList -> ExampleException.withMessage(
"The stringArrayProperty should not be empty, but it is %s.", Arrays.toString(strList)));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] {});
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals("The stringArrayProperty should not be empty, but it is [].", e.getMessage());
}
@Test
void notEmpty_message_stringListIsNull() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).notEmpty(MESSAGE_NOT_EMPTY);
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(null);
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals(MESSAGE_NOT_EMPTY, e.getMessage());
}
@Test
void notEmpty_exceptionSupplier_stringListIsNull() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.notEmpty(() -> ExampleException.withMessage(MESSAGE_NOT_EMPTY));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(null);
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals(MESSAGE_NOT_EMPTY, e.getMessage());
}
@Test
void notEmpty_exceptionFunction_stringListIsNull() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.notEmpty(strList -> ExampleException.withMessage(
"The stringArrayProperty should not be empty, but it is %s.", Arrays.toString(strList)));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(null);
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals("The stringArrayProperty should not be empty, but it is null.", e.getMessage());
}
// ================================
// #endregion - notEmpty
// ================================
// ================================
// #region - isEmpty
// ================================
@Test
void isEmpty_stringListIsEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).isEmpty();
ruleForArray(ExampleCommand::getStringArrayProperty).isEmpty(MESSAGE_EMPTY);
ruleForArray(ExampleCommand::getStringArrayProperty)
.isEmpty(() -> ExampleException.withMessage(MESSAGE_EMPTY));
ruleForArray(ExampleCommand::getStringArrayProperty)
.isEmpty(strList -> ExampleException.withMessage(
"The stringArrayProperty should be empty, but it is %s.", Arrays.toString(strList)));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] {});
assertDoesNotThrow(() -> validator.validate(command));
}
@Test
void isEmpty_stringListIsNull() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).isEmpty();
ruleForArray(ExampleCommand::getStringArrayProperty).isEmpty(MESSAGE_EMPTY);
ruleForArray(ExampleCommand::getStringArrayProperty)
.isEmpty(() -> ExampleException.withMessage(MESSAGE_EMPTY));
ruleForArray(ExampleCommand::getStringArrayProperty)
.isEmpty(strList -> ExampleException.withMessage(
"The stringArrayProperty should be empty, but it is %s.", Arrays.toString(strList)));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(null);
assertDoesNotThrow(() -> validator.validate(command));
}
@Test
void isEmpty_default_stringListIsNotEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).isEmpty();
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "A", "B", "C" });
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals("The input must be empty.", e.getMessage());
}
@Test
void isEmpty_message_stringListIsNotEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty).isEmpty(MESSAGE_EMPTY);
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "A", "B", "C" });
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals(MESSAGE_EMPTY, e.getMessage());
}
@Test
void isEmpty_exceptionSupplier_stringListIsNotEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.isEmpty(() -> ExampleException.withMessage(MESSAGE_EMPTY));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "A", "B", "C" });
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals(MESSAGE_EMPTY, e.getMessage());
}
@Test
void isEmpty_exceptionFunction_stringListIsNotEmpty() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.isEmpty(strList -> ExampleException.withMessage(
"The stringArrayProperty should be empty, but it is %s.", Arrays.toString(strList)));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "A", "B", "C" });
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals("The stringArrayProperty should be empty, but it is [A, B, C].", e.getMessage());
}
// ================================
// #endregion - isEmpty
// ================================
// ================================
// #region - allMatch
// ================================
static boolean checkStringLength(String str, int min, int max) {
return str != null && (str.length() >= min && str.length() <= max);
}
@Test
void allMatch_validInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.allMatch(str -> checkStringLength(str, 4, 6))
.allMatch(str -> checkStringLength(str, 4, 6),
"String length must in the interval [4,6].")
.allMatch(str -> checkStringLength(str, 4, 6),
() -> ExampleException.withMessage("String length must in the interval [4,6]."))
.allMatch(str -> checkStringLength(str, 4, 6),
str -> ExampleException.withMessage("Validation failed: '%s'.", str));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "1234", "12345", "123456" });
assertDoesNotThrow(() -> validator.validate(command));
}
@Test
void allMatch_default_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.allMatch(str -> checkStringLength(str, 4, 6));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { null, "1234", "12345", "123456" });
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals("All elements must match the condition.", e.getMessage());
}
@Test
void allMatch_specifiedMessage_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.allMatch(str -> checkStringLength(str, 4, 6),
"String length must in the interval [4,6].");
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "1234", "", "12345", "123456" });
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals("String length must in the interval [4,6].", e.getMessage());
}
@Test
void allMatch_specifiedExceptionSupplier_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.allMatch(str -> checkStringLength(str, 4, 6),
() -> ExampleException.withMessage("String length must in the interval [4,6]."));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "1234", "12345", "123", "123456" });
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals("String length must in the interval [4,6].", e.getMessage());
}
@Test
void allMatch_specifiedExceptionFunction_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleForArray(ExampleCommand::getStringArrayProperty)
.allMatch(str -> checkStringLength(str, 4, 6),
str -> ExampleException.withMessage("Validation failed: '%s'.", str));
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "1234", "12345", "123456", "1234567" });
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals("Validation failed: '1234567'.", e.getMessage());
}
// ================================
// #endregion - allMatch
// ================================
static ExampleCommand exampleCommandWithStringArrayProperty(String[] property) {
ExampleCommand exampleCommand = new ExampleCommand();
exampleCommand.setStringArrayProperty(property);
return exampleCommand;
}
}

View File

@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;
import org.junit.jupiter.api.Test;
@ -62,6 +63,8 @@ public class ObjectPropertyValidatorTests {
.notNull(() -> ExampleException.withMessage("The objectProperty cannot be null"));
ruleFor(ExampleCommand::getStringListProperty)
.notNull(d -> ExampleException.withMessage("The stringListProperty cannot be null, but it was %s", d));
ruleFor(ExampleCommand::getStringArrayProperty)
.notNull(d -> ExampleException.withMessage("The stringListProperty cannot be null, but it was %s", Arrays.toString(d)));
}
};
ExampleCommand command = new ExampleCommand(
@ -72,7 +75,8 @@ public class ObjectPropertyValidatorTests {
"StringValue",
LocalDateTime.now().plusDays(1),
new Foo(Integer.MAX_VALUE, "StringValue"),
Lists.newArrayList("ABC", "DEF"));
Lists.newArrayList("ABC", "DEF"),
new String[] { "ABC", "DEF" });
assertDoesNotThrow(() -> validator.validate(command));
}
@ -150,6 +154,8 @@ public class ObjectPropertyValidatorTests {
.isNull(() -> ExampleException.withMessage("The objectProperty should be null"));
ruleFor(ExampleCommand::getStringListProperty)
.isNull(d -> ExampleException.withMessage("The stringListProperty should be null, but it was %s", d));
ruleFor(ExampleCommand::getStringArrayProperty)
.isNull(d -> ExampleException.withMessage("The stringListProperty should be null, but it was %s", Arrays.toString(d)));
}
};
ExampleCommand command = new ExampleCommand();