From 3ef2ebac2f57fe57bbd0a312ca78c8b93fb19770 Mon Sep 17 00:00:00 2001 From: ZhouXY108 Date: Sun, 1 Jun 2025 08:46:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BA=8C=E5=85=83?= =?UTF-8?q?=E7=BB=84=E5=B1=9E=E6=80=A7=E6=A0=A1=E9=AA=8C=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `PairPropertyValidator` 类,用于校验二元组属性 - 在 `BaseValidator` 和 `MapValidator` 中添加对二元组校验器的支持 - 添加相关单元测试 --- .../plusone/validator/BaseValidator.java | 14 ++ .../plusone/validator/MapValidator.java | 18 +++ .../validator/PairPropertyValidator.java | 86 ++++++++++++ .../validator/PairPropertyValidatorTests.java | 129 ++++++++++++++++++ .../map/validator/MapValidatorTests.java | 7 +- 5 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/PairPropertyValidator.java create mode 100644 plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/PairPropertyValidatorTests.java 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 b42a33f..6f891df 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 @@ -19,6 +19,7 @@ package xyz.zhouxy.plusone.validator; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map.Entry; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -246,6 +247,19 @@ public abstract class BaseValidator implements IValidator { return validator; } + /** + * 添加一个针对二元组的校验器 + * @param 第一个元素的类型 + * @param 第二个元素的类型 + * @param getter 获取属性值的函数 + * @return 二元组校验器 + */ + protected final PairPropertyValidator ruleForPair(Function> getter) { + PairPropertyValidator validator = new PairPropertyValidator<>(getter); + this.rules.add(validator::validate); + return validator; + } + /** {@inheritDoc} */ @Override public void validate(T obj) { diff --git a/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/MapValidator.java b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/MapValidator.java index 8124858..dabe9a4 100644 --- a/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/MapValidator.java +++ b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/MapValidator.java @@ -16,9 +16,11 @@ package xyz.zhouxy.plusone.validator; +import java.util.AbstractMap.SimpleEntry; import java.util.Arrays; import java.util.Collection; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -107,6 +109,7 @@ public abstract class MapValidator extends BaseValidator> { /** * 添加一个属性校验器,对指定 key 对应的 value 进行校验 * + * @param 属性类型 * @param key key * @return 属性校验器 */ @@ -191,6 +194,21 @@ public abstract class MapValidator extends BaseValidator> { return ruleForCollection(getter); } + /** + * 添加一个属性校验器,对指定的两个 key 对应的 value 进行校验 + * @param 第一个属性的类型 + * @param 第二个属性的类型 + * @param k1 第一个 key + * @param k2 第二个 key + * @return 属性校验器 + */ + protected final + PairPropertyValidator, V1, V2> ruleForPair(K k1, K k2) { + @SuppressWarnings("unchecked") + Function, Entry> getter = m -> new SimpleEntry((V1) m.get(k1), (V2) m.get(k2)); + return ruleForPair(getter); + } + // ================================ // #endregion - ruleFor // ================================ diff --git a/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/PairPropertyValidator.java b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/PairPropertyValidator.java new file mode 100644 index 0000000..d8f247e --- /dev/null +++ b/plusone-validator/src/main/java/xyz/zhouxy/plusone/validator/PairPropertyValidator.java @@ -0,0 +1,86 @@ +/* + * 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.validator; + +import java.util.Map.Entry; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 针对二元组的属性校验器 + * + * @author ZhouXY + */ +public class PairPropertyValidator + extends BasePropertyValidator, PairPropertyValidator> { + + protected PairPropertyValidator(Function> getter) { + super(getter); + } + + /** + * 添加一条校验属性的规则,校验二元组是否满足给定的条件 + * + * @param condition 校验规则 + * @return 属性校验器 + */ + public final PairPropertyValidator must(BiPredicate condition) { + return must(pair -> condition.test(pair.getKey(), pair.getValue())); + } + + /** + * 添加一条校验属性的规则,校验二元组是否满足给定的条件 + * + * @param condition 校验规则 + * @param errMsg 错误信息 + * @return 属性校验器 + */ + public final PairPropertyValidator must(BiPredicate condition, String errMsg) { + return must(pair -> condition.test(pair.getKey(), pair.getValue()), errMsg); + } + + /** + * 添加一条校验属性的规则,校验二元组是否满足给定的条件 + * + * @param condition 校验规则 + * @param e 自定义异常 + * @return 属性校验器 + */ + public final PairPropertyValidator must( + BiPredicate condition, Supplier e) { + return must(pair -> condition.test(pair.getKey(), pair.getValue()), e); + } + + /** + * 添加一条校验属性的规则,校验二元组是否满足给定的条件 + * + * @param condition 校验规则 + * @param e 自定义异常 + * @return 属性校验器 + */ + public final PairPropertyValidator must( + BiPredicate condition, BiFunction e) { + return must(pair -> condition.test(pair.getKey(), pair.getValue()), + pair -> e.apply(pair.getKey(), pair.getValue())); + } + + @Override + protected PairPropertyValidator thisObject() { + return this; + } +} diff --git a/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/PairPropertyValidatorTests.java b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/PairPropertyValidatorTests.java new file mode 100644 index 0000000..7b7ed03 --- /dev/null +++ b/plusone-validator/src/test/java/xyz/zhouxy/plusone/example/validator/PairPropertyValidatorTests.java @@ -0,0 +1,129 @@ +/* + * 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.Objects; +import java.util.AbstractMap.SimpleEntry; + +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 PairPropertyValidatorTests { + + static final String MESSAGE = "Validation failed."; + + // ================================ + // #region - must + // ================================ + + @Test + void must_validInput() { + IValidator validator = new BaseValidator() { + { + ruleForPair((ExampleCommand command) -> new SimpleEntry(command.getStringProperty(), command.getIntProperty())) + .must((str, intValue) -> Objects.equals(str, intValue.toString())) + .must((str, intValue) -> Objects.equals(str, intValue.toString()), MESSAGE) + .must((str, intValue) -> Objects.equals(str, intValue.toString()), () -> ExampleException.withMessage(MESSAGE)) + .must((str, intValue) -> Objects.equals(str, intValue.toString()), + (str, intValue) -> ExampleException.withMessage("Validation failed: ('%s', %d).", str, intValue)); + } + }; + + ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "100"); + assertDoesNotThrow(() -> validator.validate(command)); + } + + @Test + void must_default_invalidInput() { + IValidator validator = new BaseValidator() { + { + ruleForPair((ExampleCommand command) -> new SimpleEntry(command.getStringProperty(), command.getIntProperty())) + .must((str, intValue) -> Objects.equals(str, intValue.toString())); + } + }; + + ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); + + ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command)); + assertEquals("The specified condition was not met for the input.", e.getMessage()); + } + + @Test + void must_message_invalidInput() { + IValidator validator = new BaseValidator() { + { + ruleForPair((ExampleCommand command) -> new SimpleEntry(command.getStringProperty(), command.getIntProperty())) + .must((str, intValue) -> Objects.equals(str, intValue.toString()), MESSAGE); + } + }; + + ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); + ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command)); + assertEquals(MESSAGE, e.getMessage()); + } + + @Test + void must_exceptionSupplier_invalidInput() { + IValidator validator = new BaseValidator() { + { + ruleForPair((ExampleCommand command) -> new SimpleEntry(command.getStringProperty(), command.getIntProperty())) + .must((str, intValue) -> Objects.equals(str, intValue.toString()), () -> ExampleException.withMessage(MESSAGE)); + } + }; + + ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); + + ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command)); + assertEquals(MESSAGE, e.getMessage()); + } + + @Test + void must_exceptionFunction_invalidInput() { + IValidator validator = new BaseValidator() { + { + ruleForPair((ExampleCommand command) -> new SimpleEntry(command.getStringProperty(), command.getIntProperty())) + .must((str, intValue) -> Objects.equals(str, intValue.toString()), + (str, intValue) -> ExampleException.withMessage("Validation failed: ('%s', %d).", str, intValue)); + } + }; + + ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); + + ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command)); + assertEquals("Validation failed: ('', 100).", e.getMessage()); + } + + // ================================ + // #endregion - must + // ================================ + + static ExampleCommand exampleCommandWithIntAndStringListProperty(Integer intValue, String str) { + ExampleCommand exampleCommand = new ExampleCommand(); + exampleCommand.setIntProperty(intValue); + exampleCommand.setStringProperty(str); + return exampleCommand; + } +} diff --git a/plusone-validator/src/test/java/xyz/zhouxy/plusone/map/validator/MapValidatorTests.java b/plusone-validator/src/test/java/xyz/zhouxy/plusone/map/validator/MapValidatorTests.java index b3ebae8..8d7890b 100644 --- a/plusone-validator/src/test/java/xyz/zhouxy/plusone/map/validator/MapValidatorTests.java +++ b/plusone-validator/src/test/java/xyz/zhouxy/plusone/map/validator/MapValidatorTests.java @@ -22,7 +22,6 @@ import java.time.LocalDateTime; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Set; import org.junit.jupiter.api.Test; @@ -126,9 +125,9 @@ class ParamsValidator extends MapValidator { ruleForCollection(STRING_LIST_PROPERTY) .notNull(d -> ExampleException.withMessage("The stringListProperty cannot be null, but it was %s", d)); - // 校验到多个属性,只能针对 map 本身进行校验 - withRule(m -> Objects.equals(m.get(STRING_PROPERTY), m.get(STRING_PROPERTY2)), - "'stringProperty' must be equal to 'stringProperty2'."); + ruleForPair(STRING_PROPERTY, STRING_PROPERTY2) + .must((str1, str2) -> str1 != null && str1.equals(str2), + "'stringProperty' must be equal to 'stringProperty2'."); } public static Set keySet() {