2 Commits

Author SHA1 Message Date
2977c9a7fb refactor!: 优化二元组校验的 API 设计 2025-07-25 10:57:47 +08:00
09d596b599 feat: 重载 BaseValidator#ruleForPair
- 重载 BaseValidator#ruleForPair,允许传入两个 Function,用于分别从目标对象中获取两个属性
- 升级项目版本至 1.0.0-RC2
2025-07-14 09:43:13 +08:00
5 changed files with 102 additions and 34 deletions

View File

@@ -8,7 +8,7 @@
<parent> <parent>
<groupId>xyz.zhouxy.plusone</groupId> <groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-validator-parent</artifactId> <artifactId>plusone-validator-parent</artifactId>
<version>1.0.0-RC1</version> <version>1.0.0-RC2</version>
</parent> </parent>
<artifactId>plusone-validator</artifactId> <artifactId>plusone-validator</artifactId>

View File

@@ -19,6 +19,7 @@ package xyz.zhouxy.plusone.validator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@@ -280,13 +281,34 @@ public abstract class BaseValidator<T> implements IValidator<T> {
* @param <V2> 第二个元素的类型 * @param <V2> 第二个元素的类型
* @param getter 根据对象构造一个二元组,通常是两个属性的值。 * @param getter 根据对象构造一个二元组,通常是两个属性的值。
* @return {@code PairPropertyValidator}。用于添加针对该二元组的校验规则。 * @return {@code PairPropertyValidator}。用于添加针对该二元组的校验规则。
*
* @deprecated 请使用 {@link #ruleFor(Function, Function)} 代替。
*/ */
protected final <V1, V2> PairPropertyValidator<T, V1, V2> ruleForPair(Function<T, Entry<V1, V2>> getter) { @Deprecated
protected final <V1, V2> PairPropertyValidator<T, V1, V2> ruleForPair( // NOSONAR
Function<T, Entry<V1, V2>> getter) {
PairPropertyValidator<T, V1, V2> validator = new PairPropertyValidator<>(getter); PairPropertyValidator<T, V1, V2> validator = new PairPropertyValidator<>(getter);
this.rules.add(validator::validate); this.rules.add(validator::validate);
return validator; return validator;
} }
/**
* 添加一个针对二元组的校验器
*
* @param <V1> 第1个元素的类型
* @param <V2> 第2个元素的类型
* @param v1Getter 用于从目标对象获取第1个元素的函数式接口。示例{@code Person::getName1}。
* @param v2Getter 用于从目标对象获取第2个元素的函数式接口。示例{@code Person::getName2}。
* @return {@code PairPropertyValidator}。用于添加针对该二元组的校验规则。
*/
protected final <V1, V2> PairPropertyValidator<T, V1, V2> ruleFor(
Function<T, V1> v1Getter, Function<T, V2> v2Getter) {
PairPropertyValidator<T, V1, V2> validator = new PairPropertyValidator<>(
t -> new SimpleImmutableEntry<>(v1Getter.apply(t), v2Getter.apply(t)));
this.rules.add(validator::validate);
return validator;
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void validate(T obj) { public void validate(T obj) {

View File

@@ -16,11 +16,9 @@
package xyz.zhouxy.plusone.validator; package xyz.zhouxy.plusone.validator;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -196,18 +194,19 @@ public abstract class MapValidator<K, V> extends BaseValidator<Map<K, V>> {
/** /**
* 添加一个属性校验器,对指定的两个 key 对应的 value 进行校验 * 添加一个属性校验器,对指定的两个 key 对应的 value 进行校验
* @param <V1> 第个属性的类型 * @param <V1> 第1个属性的类型
* @param <V2> 第个属性的类型 * @param <V2> 第2个属性的类型
* @param k1 第个 key * @param k1 第1个 key
* @param k2 第个 key * @param k2 第2个 key
* @return 属性校验器 * @return 属性校验器
*/ */
protected final <V1 extends V, V2 extends V> protected final <V1 extends V, V2 extends V>
PairPropertyValidator<Map<K, V>, V1, V2> ruleForPair(K k1, K k2) { PairPropertyValidator<Map<K, V>, V1, V2> ruleForPair(K k1, K k2) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Function<Map<K, V>, Entry<V1, V2>> getter = m -> final Function<Map<K, V>, V1> v1Getter = m -> (V1) m.get(k1);
new SimpleImmutableEntry<>((V1) m.get(k1), (V2) m.get(k2)); @SuppressWarnings("unchecked")
return ruleForPair(getter); final Function<Map<K, V>, V2> v2Getter = m -> (V2) m.get(k2);
return ruleFor(v1Getter, v2Getter);
} }
// ================================ // ================================

View File

@@ -31,6 +31,7 @@ import xyz.zhouxy.plusone.validator.BaseValidator;
import xyz.zhouxy.plusone.validator.IValidator; import xyz.zhouxy.plusone.validator.IValidator;
import xyz.zhouxy.plusone.validator.ValidationException; import xyz.zhouxy.plusone.validator.ValidationException;
@SuppressWarnings("deprecation")
public class PairPropertyValidatorTests { public class PairPropertyValidatorTests {
static final String MESSAGE = "Validation failed."; static final String MESSAGE = "Validation failed.";
@@ -41,7 +42,9 @@ public class PairPropertyValidatorTests {
@Test @Test
void must_validInput() { void must_validInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() { ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "100");
IValidator<ExampleCommand> validator1 = new BaseValidator<ExampleCommand>() {
{ {
ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty())) ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty()))
.must((str, intValue) -> Objects.equals(str, intValue.toString())) .must((str, intValue) -> Objects.equals(str, intValue.toString()))
@@ -51,69 +54,113 @@ public class PairPropertyValidatorTests {
(str, intValue) -> ExampleException.withMessage("Validation failed: ('%s', %d).", str, intValue)); (str, intValue) -> ExampleException.withMessage("Validation failed: ('%s', %d).", str, intValue));
} }
}; };
assertDoesNotThrow(() -> validator1.validate(command));
ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "100"); IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
assertDoesNotThrow(() -> validator.validate(command)); {
ruleFor(ExampleCommand::getStringProperty, ExampleCommand::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));
}
};
assertDoesNotThrow(() -> validator2.validate(command));
} }
@Test @Test
void must_default_invalidInput() { void must_default_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() { ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "");
IValidator<ExampleCommand> validator1 = new BaseValidator<ExampleCommand>() {
{ {
ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty())) ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty()))
.must((str, intValue) -> Objects.equals(str, intValue.toString())); .must((str, intValue) -> Objects.equals(str, intValue.toString()));
} }
}; };
ValidationException e1 = assertThrows(ValidationException.class, () -> validator1.validate(command));
assertEquals("The specified condition was not met for the input.", e1.getMessage());
ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command)); ruleFor(ExampleCommand::getStringProperty, ExampleCommand::getIntProperty)
assertEquals("The specified condition was not met for the input.", e.getMessage()); .must((str, intValue) -> Objects.equals(str, intValue.toString()));
}
};
ValidationException e2 = assertThrows(ValidationException.class, () -> validator2.validate(command));
assertEquals("The specified condition was not met for the input.", e2.getMessage());
} }
@Test @Test
void must_message_invalidInput() { void must_message_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() { ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "");
IValidator<ExampleCommand> validator1 = new BaseValidator<ExampleCommand>() {
{ {
ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty())) ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty()))
.must((str, intValue) -> Objects.equals(str, intValue.toString()), MESSAGE); .must((str, intValue) -> Objects.equals(str, intValue.toString()), MESSAGE);
} }
}; };
ValidationException e1 = assertThrows(ValidationException.class, () -> validator1.validate(command));
assertEquals(MESSAGE, e1.getMessage());
ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command)); {
assertEquals(MESSAGE, e.getMessage()); ruleFor(ExampleCommand::getStringProperty, ExampleCommand::getIntProperty)
.must((str, intValue) -> Objects.equals(str, intValue.toString()), MESSAGE);
}
};
ValidationException e2 = assertThrows(ValidationException.class, () -> validator2.validate(command));
assertEquals(MESSAGE, e2.getMessage());
} }
@Test @Test
void must_exceptionSupplier_invalidInput() { void must_exceptionSupplier_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() { ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "");
IValidator<ExampleCommand> validator1 = new BaseValidator<ExampleCommand>() {
{ {
ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty())) ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty()))
.must((str, intValue) -> Objects.equals(str, intValue.toString()), () -> ExampleException.withMessage(MESSAGE)); .must((str, intValue) -> Objects.equals(str, intValue.toString()), () -> ExampleException.withMessage(MESSAGE));
} }
}; };
ExampleException e1 = assertThrows(ExampleException.class, () -> validator1.validate(command));
assertEquals(MESSAGE, e1.getMessage());
ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command)); ruleFor(ExampleCommand::getStringProperty, ExampleCommand::getIntProperty)
assertEquals(MESSAGE, e.getMessage()); .must((str, intValue) -> Objects.equals(str, intValue.toString()), () -> ExampleException.withMessage(MESSAGE));
}
};
ExampleException e2 = assertThrows(ExampleException.class, () -> validator2.validate(command));
assertEquals(MESSAGE, e2.getMessage());
} }
@Test @Test
void must_exceptionFunction_invalidInput() { void must_exceptionFunction_invalidInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() { ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "");
IValidator<ExampleCommand> validator1 = new BaseValidator<ExampleCommand>() {
{ {
ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty())) ruleForPair((ExampleCommand command) -> new SimpleImmutableEntry<String,Integer>(command.getStringProperty(), command.getIntProperty()))
.must((str, intValue) -> Objects.equals(str, intValue.toString()), .must((str, intValue) -> Objects.equals(str, intValue.toString()),
(str, intValue) -> ExampleException.withMessage("Validation failed: ('%s', %d).", str, intValue)); (str, intValue) -> ExampleException.withMessage("Validation failed: ('%s', %d).", str, intValue));
} }
}; };
ExampleException e1 = assertThrows(ExampleException.class, () -> validator1.validate(command));
assertEquals("Validation failed: ('', 100).", e1.getMessage());
ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, ""); IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command)); ruleFor(ExampleCommand::getStringProperty, ExampleCommand::getIntProperty)
assertEquals("Validation failed: ('', 100).", e.getMessage()); .must((str, intValue) -> Objects.equals(str, intValue.toString()),
(str, intValue) -> ExampleException.withMessage("Validation failed: ('%s', %d).", str, intValue));
}
};
ExampleException e2 = assertThrows(ExampleException.class, () -> validator2.validate(command));
assertEquals("Validation failed: ('', 100).", e2.getMessage());
} }
// ================================ // ================================

View File

@@ -6,7 +6,7 @@
<groupId>xyz.zhouxy.plusone</groupId> <groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-validator-parent</artifactId> <artifactId>plusone-validator-parent</artifactId>
<version>1.0.0-RC1</version> <version>1.0.0-RC2</version>
<name>plusone-validator-parent</name> <name>plusone-validator-parent</name>
<url>http://zhouxy.xyz</url> <url>http://zhouxy.xyz</url>