Compare commits

...

3 Commits

5 changed files with 111 additions and 150 deletions

105
README.md Normal file
View File

@@ -0,0 +1,105 @@
# Plusone Validator
## 简介
Plusone Validator 是一个校验库,使用 lambda 表达式(包括方法引用)和流式 API 构建校验规则,对对象进行校验。
## 安装
Plusone Validator 暂未提交 maven 中央仓库,可 clone 代码仓库,通过 maven 安装到本地库,然后在项目中引入。
## 示例
### 校验对象
定义校验器:
```java
class CustomerValidator extends BaseValidator<Customer> {
private static final CustomerValidator INSTANCE = new CustomerValidator();
private CustomerValidator() {
ruleFor(Customer::getName).notBlank("姓名不能为空");
ruleFor(Customer::getEmailAddress).notBlank().emailAddress();
ruleFor(Customer::getVipLevel).notNull().inRange(Range.closed(0, 10), "会员等级必须在0-10之间");
ruleFor(Customer::getCustomerId).notBlank("客户编号不能为空");
ruleFor(Customer::getBirthday)
.notNull("生日不能为空")
.must(LocalDate.now().minusYears(16)::isAfter, "用户必须大于16周岁");
ruleFor(Customer::getAddress).length(20, 250, "地址长度必须在20-250之间");
ruleFor((Customer customer) -> Pair.of(customer.getVipLevel(), customer.getBirthday()))
.must(CustomerValidator::validateAge, "5级以上会员必须满18周岁");
}
private static boolean validateAge(Pair<Integer, LocalDate> vipLevelAndBirthday) {
Integer vipLevel = vipLevelAndBirthday.getLeft();
LocalDate birthday = vipLevelAndBirthday.getRight();
return vipLevel <= 5 || LocalDate.now().minusYears(18).isAfter(birthday);
}
public static CustomerValidator getInstance() {
return INSTANCE;
}
}
```
使用:
```java
public void foo(Customer customer) {
CustomerValidator.getInstance().validate(customer);
// ...
}
```
### 校验 Map
定义校验器:
```java
class CustomerMapValidator extends MapValidator<String, Object> {
private static final CustomerMapValidator INSTANCE = new CustomerMapValidator();
private static final String[] FIELD_NAMES = {
"name", "emailAddress", "vipLevel", "customerId", "birthday", "address"
};
private CustomerMapValidator() {
super(FIELD_NAMES);
ruleForString("name").notBlank("姓名不能为空");
ruleForString("emailAddress").notBlank().emailAddress();
ruleForInt("vipLevel").notNull().inRange(Range.closed(0, 10), "会员等级必须在0-10之间");
ruleForString("customerId").notBlank("客户编号不能为空");
this.<LocalDate>ruleFor("birthday")
.notNull("生日不能为空")
.must(LocalDate.now().minusYears(16)::isAfter, "用户必须大于16周岁");
ruleForString("address").length(20, 250, "地址长度必须在20-250之间");
this.<Pair<Integer, LocalDate>>ruleFor((Map<String, Object> customer) ->
Pair.of(MapUtils.getInteger(customer, "vipLevel"), (LocalDate) customer.get("birthday")))
.must(CustomerMapValidator::validateAge, "5级以上会员必须满18周岁");
}
private static boolean validateAge(Pair<Integer, LocalDate> vipLevelAndBirthday) {
Integer vipLevel = vipLevelAndBirthday.getLeft();
LocalDate birthday = vipLevelAndBirthday.getRight();
return vipLevel <= 5 || LocalDate.now().minusYears(18).isAfter(birthday);
}
public static CustomerMapValidator getInstance() {
return INSTANCE;
}
}
```
使用:
```java
public void foo(Map<String, Object> customer) {
CustomerMapValidator.getInstance().validate(customer);
// ...
}
```
---
## 其他
Plusone Validator 是个人在学习 Microsoft 的 [eShop](https://github.com/dotnet/eShop) 时,被其中 [FluentValidation](https://github.com/FluentValidation/FluentValidation) 的 API 所吸引,出于学习和个人使用的目的进行开发和维护。使用 Apache License 2.0 开源。
欢迎通过 issue 反馈使用过程中发现的问题和建议。

View File

@@ -73,7 +73,7 @@ public abstract class BaseComparablePropertyValidator<T, TProperty extends Compa
*/
public <E extends RuntimeException> TPropertyValidator inRange(
Range<TProperty> range, Supplier<E> e) {
withRule(value -> value != null && range.contains(value), e);
withRule(value -> value != null && range.contains(value), convertToExceptionFunction(e));
return thisObject();
}

View File

@@ -44,28 +44,6 @@ public abstract class BasePropertyValidator<T, TProperty, TPropertyValidator ext
this.getter = getter;
}
/**
* 添加一条校验属性的规则
*
* @param rule 校验规则
* @return 属性校验器
*/
public final TPropertyValidator withRule(Predicate<? super TProperty> rule) {
return withRule(rule, v -> ValidationException.withDefaultMessage());
}
/**
* 添加一条校验属性的规则
*
* @param rule 校验规则
* @param errMsg 校验失败的错误信息
* @return 属性校验器
*/
public final TPropertyValidator withRule(
Predicate<? super TProperty> rule, String errMsg) {
return withRule(rule, convertToExceptionFunction(errMsg));
}
/**
* 添加一条校验属性的规则
*
@@ -73,19 +51,7 @@ public abstract class BasePropertyValidator<T, TProperty, TPropertyValidator ext
* @param e 自定义异常
* @return 属性校验器
*/
public final <E extends RuntimeException> TPropertyValidator withRule(
Predicate<? super TProperty> rule, Supplier<E> e) {
return withRule(rule, convertToExceptionFunction(e));
}
/**
* 添加一条校验属性的规则
*
* @param rule 校验规则
* @param e 自定义异常
* @return 属性校验器
*/
public final <E extends RuntimeException> TPropertyValidator withRule(
protected final <E extends RuntimeException> TPropertyValidator withRule(
Predicate<? super TProperty> rule, Function<TProperty, E> e) {
this.consumers.add(v -> {
if (!rule.test(v)) {

View File

@@ -110,9 +110,10 @@ public abstract class MapValidator<K, V> extends BaseValidator<Map<K, V>> {
* @param key key
* @return 属性校验器
*/
protected final ObjectPropertyValidator<Map<K, V>, V> ruleFor(K key) {
final Function<Map<K, V>, V> func = m -> m.get(key);
return ruleFor(func);
protected final <T extends V> ObjectPropertyValidator<Map<K, V>, T> ruleFor(K key) {
@SuppressWarnings("unchecked")
final Function<Map<K, V>, T> func = m -> (T) m.get(key);
return super.<T>ruleFor(func);
}
/**

View File

@@ -20,7 +20,6 @@ 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.time.DateTimeException;
import java.time.LocalDateTime;
import java.util.Objects;
@@ -30,8 +29,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import xyz.zhouxy.plusone.ExampleException;
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
import xyz.zhouxy.plusone.commons.util.DateTimeTools;
import xyz.zhouxy.plusone.commons.util.StringTools;
import xyz.zhouxy.plusone.example.ExampleCommand;
import xyz.zhouxy.plusone.example.Foo;
@@ -41,114 +38,6 @@ import xyz.zhouxy.plusone.validator.ValidationException;
public class ObjectPropertyValidatorTests {
// ================================
// #region - withRule
// ================================
@Test
void withRule_validInput() {
IValidator<ExampleCommand> validator = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getBoolProperty)
.notNull("The boolProperty cannot be null.")
.withRule(Boolean.TRUE::equals);
ruleFor(ExampleCommand::getIntProperty)
.withRule(intProperty -> intProperty > 0, "The intProperty should be greater than 0.");
ruleFor(ExampleCommand::getLongProperty)
.withRule(longProperty -> longProperty > 0L,
() -> ExampleException.withMessage("The longProperty should be greater than 0."));
ruleFor(ExampleCommand::getDoubleProperty)
.withRule(doubleProperty -> doubleProperty > 0.00,
doubleProperty -> ExampleException.withMessage("The doubleProperty should be greater than 0, but it was: %s", doubleProperty));
ruleFor(ExampleCommand::getStringProperty)
.notNull()
.withRule(stringProperty -> stringProperty.length() > 2,
() -> ExampleException.withMessage("The length of stringProperty should be greater than 2."));
ruleFor(ExampleCommand::getDateTimeProperty)
.withRule(DateTimeTools::isFuture,
() -> new DateTimeException("The dateTimeProperty should be a future time."));
ruleFor(ExampleCommand::getObjectProperty)
.notNull("The objectProperty cannot be null.");
ruleFor(ExampleCommand::getStringListProperty)
.withRule(CollectionTools::isNotEmpty, "The stringListProperty cannot be empty.");
withRule(command -> {
Foo objectProperty = command.getObjectProperty();
if (!Objects.equals(command.getIntProperty(), objectProperty.getIntProperty())) {
throw ExampleException.withMessage("intProperty invalid.");
}
});
}
};
ExampleCommand command = new ExampleCommand(
true,
Integer.MAX_VALUE,
Long.MAX_VALUE,
Double.MAX_VALUE,
"StringValue",
LocalDateTime.now().plusDays(1),
new Foo(Integer.MAX_VALUE, "StringValue"),
Lists.newArrayList("ABC", "DEF"));
assertDoesNotThrow(() -> validator.validate(command));
}
@Test
void withRule_invalidInputs() {
ExampleCommand command = new ExampleCommand();
IValidator<ExampleCommand> ruleWithDefaultMessage = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getObjectProperty)
.withRule(x -> false);
}
};
ValidationException eWithDefaultMessage = assertThrows(
ValidationException.class, () -> ruleWithDefaultMessage.validate(command));
assertEquals(ValidationException.DEFAULT_MESSAGE, eWithDefaultMessage.getMessage());
IValidator<ExampleCommand> ruleWithMessage = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getObjectProperty)
.withRule(x -> false, "invalid input.");
}
};
ValidationException eWithMessage = assertThrows(
ValidationException.class, () -> ruleWithMessage.validate(command));
assertEquals("invalid input.", eWithMessage.getMessage());
IValidator<ExampleCommand> ruleWithExceptionSupplier = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getObjectProperty)
.withRule(x -> false, () -> ExampleException.withMessage("invalid input."));
}
};
ExampleException specifiedException = assertThrows(
ExampleException.class, () -> ruleWithExceptionSupplier.validate(command));
assertEquals("invalid input.", specifiedException.getMessage());
IValidator<ExampleCommand> ruleWithExceptionFunction = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getObjectProperty)
.withRule(x -> false, x -> ExampleException.withMessage("invalid input: [%s].", x));
}
};
ExampleException specifiedException2 = assertThrows(
ExampleException.class, () -> ruleWithExceptionFunction.validate(command));
assertEquals("invalid input: [null].", specifiedException2.getMessage());
}
// ================================
// #endregion - withRule
// ================================
// ================================
// #region - notNull
// ================================