diff --git a/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/ArrayPropertyValidator.java b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/ArrayPropertyValidator.java new file mode 100644 index 0000000..32eab14 --- /dev/null +++ b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/ArrayPropertyValidator.java @@ -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; + +/** + * 针对数组类型的属性校验器 + * + *

+ * 内置数组相关的校验规则。 + * + * @author ZhouXY + */ +public class ArrayPropertyValidator + extends BasePropertyValidator> { + + ArrayPropertyValidator(Function getter) { + super(getter); + } + + // ================================ + // #region - notEmpty + // ================================ + + /** + * 添加一条校验属性的规则,校验属性是否非空 + * + * @return 属性校验器 + */ + public ArrayPropertyValidator notEmpty() { + return notEmpty("The input must not be empty."); + } + + /** + * 添加一条校验属性的规则,校验属性是否非空 + * + * @param errMsg 异常信息 + * @return 属性校验器 + */ + public ArrayPropertyValidator notEmpty(String errMsg) { + return notEmpty(convertToExceptionFunction(errMsg)); + } + + /** + * 添加一条校验属性的规则,校验属性是否非空 + * + * @param 自定义异常类型 + * @param e 自定义异常 + * @return 属性校验器 + */ + public ArrayPropertyValidator notEmpty( + Supplier e) { + return notEmpty(convertToExceptionFunction(e)); + } + + /** + * 添加一条校验属性的规则,校验属性是否非空 + * + * @param 自定义异常类型 + * @param e 自定义异常 + * @return 属性校验器 + */ + public ArrayPropertyValidator notEmpty( + Function e) { + withRule(ArrayTools::isNotEmpty, e); + return this; + } + + // ================================ + // #endregion - notEmpty + // ================================ + + // ================================ + // #region - isEmpty + // ================================ + + /** + * 添加一条校验属性的规则,校验属性是否为空 + * + * @return 属性校验器 + */ + public ArrayPropertyValidator isEmpty() { + return isEmpty("The input must be empty."); + } + + /** + * 添加一条校验属性的规则,校验属性是否为空 + * + * @param errMsg 异常信息 + * @return 属性校验器 + */ + public ArrayPropertyValidator isEmpty(String errMsg) { + return isEmpty(convertToExceptionFunction(errMsg)); + } + + /** + * 添加一条校验属性的规则,校验属性是否为空 + * + * @param e 自定义异常 + * @return 属性校验器 + */ + public ArrayPropertyValidator isEmpty( + Supplier e) { + return isEmpty(convertToExceptionFunction(e)); + } + + /** + * 添加一条校验属性的规则,校验属性是否为空 + * + * @param e 自定义异常 + * @return 属性校验器 + */ + public ArrayPropertyValidator isEmpty( + Function e) { + withRule(ArrayTools::isEmpty, e); + return this; + } + + // ================================ + // #endregion - isEmpty + // ================================ + + // ================================ + // #region - allMatch + // ================================ + + /** + * 添加一条校验属性的规则,校验是否所有元素都满足条件 + * + * @param condition 校验规则 + * @return 属性校验器 + */ + public ArrayPropertyValidator allMatch(Predicate condition) { + return allMatch(condition, convertToExceptionFunction("All elements must match the condition.")); + } + + /** + * 添加一条校验属性的规则,校验是否所有元素都满足条件 + * + * @param condition 校验规则 + * @param errMsg 异常信息 + * @return 属性校验器 + */ + public ArrayPropertyValidator allMatch(Predicate condition, String errMsg) { + return allMatch(condition, convertToExceptionFunction(errMsg)); + } + + /** + * 添加一条校验属性的规则,校验是否所有元素都满足条件 + * + * @param condition 校验规则 + * @param e 自定义异常 + * @return 属性校验器 + */ + public ArrayPropertyValidator allMatch( + Predicate condition, Supplier e) { + return allMatch(condition, convertToExceptionFunction(e)); + } + + /** + * 添加一条校验属性的规则,校验是否所有元素都满足条件 + * + * @param condition 校验规则 + * @param e 自定义异常 + * @return 属性校验器 + */ + public ArrayPropertyValidator allMatch( + Predicate condition, Function e) { + withRule(c -> { + for (TElement element : c) { + if (!condition.test(element)) { + throw e.apply(element); + } + } + }); + return this; + } + + // ================================ + // #endregion - allMatch + // ================================ + + @Override + protected ArrayPropertyValidator thisObject() { + return this; + } +} diff --git a/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java index 6f891df..ed18fef 100644 --- a/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java +++ b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/BaseValidator.java @@ -247,6 +247,18 @@ public abstract class BaseValidator implements IValidator { return validator; } + /** + * 添加一个针对数组属性的校验器 + * + * @param getter 获取属性值的函数 + * @return 集合属性校验器 + */ + protected final ArrayPropertyValidator ruleForArray(Function getter) { + ArrayPropertyValidator validator = new ArrayPropertyValidator<>(getter); + this.rules.add(validator::validate); + return validator; + } + /** * 添加一个针对二元组的校验器 * @param 第一个元素的类型 diff --git a/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/ExampleCommand.java b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/ExampleCommand.java index bfb9ad2..cc0d9d5 100644 --- a/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/ExampleCommand.java +++ b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/ExampleCommand.java @@ -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 stringListProperty; + private String[] stringArrayProperty; public ExampleCommand() { } public ExampleCommand(Boolean boolProperty, Integer intProperty, Long longProperty, Double doubleProperty, String stringProperty, LocalDateTime dateTimeProperty, Foo objectProperty, - List stringListProperty) { + List 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) + "]"; } } diff --git a/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/ArrayPropertyValidatorTests.java b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/ArrayPropertyValidatorTests.java new file mode 100644 index 0000000..f131b3f --- /dev/null +++ b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/ArrayPropertyValidatorTests.java @@ -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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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 validator = new BaseValidator() { + { + 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; + } +} diff --git a/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/ObjectPropertyValidatorTests.java b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/ObjectPropertyValidatorTests.java index d3706a4..56d792d 100644 --- a/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/ObjectPropertyValidatorTests.java +++ b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/ObjectPropertyValidatorTests.java @@ -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();