17 Commits

Author SHA1 Message Date
08e228c5f7 feat: 新增 package-info.java 并初步集成 JSpecify
- 添加 JSpecify 依赖到 pom.xml
- 创建 validator/package-info.java
- 创建 function/package-info.java
- 添加 @Nullable 注解到核心接口和类
2026-05-27 18:29:17 +08:00
ba9ccebb56 chore: 更新项目许可证和版权信息
- 在根目录添加 NOTICE 文件包含项目版权和许可信息
- 更新 pom.xml 添加许可证、开发者和 SCM 配置
- 更新所有 Java 文件中的版权声明
2026-05-27 18:08:07 +08:00
821f43d154 docs: 更新 README.md
- 更新项目介绍
- 添加“快速参考”,完善示例
2026-05-27 16:28:36 +08:00
4a604a64c4 chore: 更改 plusone-commons 依赖版本
开发分支项目版本和使用的 plusone-commons 都保持为最新的 SNAPSHOT 版本
2026-05-27 11:35:28 +08:00
9aa510820d fix: 修复 ObjectPropertyValidator 泛型类型定义
- 修改 `ObjectPropertyValidator` 构造函数中的泛型参数定义
- 将 `Function<T, TProperty>` 改为 `Function<T, ? extends TProperty>`
- 解决了泛型类型不匹配的问题,提高了类型安全性
2026-05-27 11:20:34 +08:00
98a7ed573b fix: MapValidator#validateAndCopy 添加 merge function
Collectors.toMap 使用 (v1, v2) -> v2 作为 merge function,这在正常情况下不会触发(因为是同一个 entry set 流),是安全的防御性编程。
2026-05-27 11:19:15 +08:00
f3c173818c fix: 修复双精度数值验证器中的格式化字符串问题 2026-05-27 11:16:18 +08:00
fdabd29347 fix: 修复数组和集合的 allMatch 校验方法对空值的处理
- 在 `ArrayPropertyValidator` 的 `allMatch` 方法中添加空值检查,当数组为空或 `null` 时直接返回
- 在 `CollectionPropertyValidator` 的 `allMatch` 方法中添加空值检查,当集合为空或 `null` 时直接返回
- 更新相关方法的文档注释,说明 `null` 值被视为通过校验
- 添加针对空数组、空集合和 `null` 值的测试用例
- 确保只有非空情况才会执行元素匹配校验逻辑
2026-05-27 11:15:32 +08:00
e22a95861d docs: 更新 BasePropertyValidator 类的 Javadoc 文档
- 为 `equal` 和 `notEqual` 方法添加 `null` 值处理说明,明确当值为 `null` 时视为通过
- 在所有相关方法中添加 `notNull()` 方法的引用提示
2026-05-27 11:09:54 +08:00
edeaec85da build: 保持开发分支的版本后缀标识为 SNAPSHOT 2025-08-21 19:58:18 +08:00
5705686375 fix: 修复 ruleForComparable 无法指定具体类型参数的 BUG (plusone/plusone-validator#19 from gitea)
更新 `BaseComparablePropertyValidator`、`ComparablePropertyValidator`、`BaseValidator` 和 `MapValidator` 中关于 `Comparable` 和 `Range` 的泛型定义

fixes plusone/plusone-validator#14 (gitea)

Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 17:39:03 +08:00
5b5d7b50d6 docs: 更新示例 (plusone/plusone-validator#18 from gitea)
Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 17:34:19 +08:00
aad5b6b4fe build: 统一管理依赖版本 (plusone/plusone-validator#17 from gitea)
Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 17:33:09 +08:00
75a3c7798a chore: 修改 author 信息 (plusone/plusone-validator#16 from gitea)
Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 17:32:15 +08:00
7d0a25144b chore: 更新项目 URL (plusone/plusone-validator#15 from gitea)
项目 URL 更新为 http://gitea.zhouxy.xyz/plusone/plusone-validator

Co-authored-by: ZhouXY108 <luquanlion@outlook.com>
Co-committed-by: ZhouXY108 <luquanlion@outlook.com>
2025-07-25 17:30:56 +08:00
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
44 changed files with 657 additions and 191 deletions

9
NOTICE Normal file
View File

@@ -0,0 +1,9 @@
Plusone Validator
Copyright 2022-present Zhou Xingyi
This product includes software developed by
Zhou Xingyi (周兴毅) (https://gitea.zhouxy.xyz/plusone).
This product is licensed to you under the Apache License, Version 2.0
(the "License"). You may not use this product except in compliance with
the License.

180
README.md
View File

@@ -1,16 +1,41 @@
# Plusone Validator
## 简介
Plusone Validator 是一个校验库,使用 lambda 表达式(包括方法引用)和流式 API 构建校验规则,对对象进行校验。
Plusone Validator 是一个受 [FluentValidation](https://github.com/FluentValidation/FluentValidation) 启发的 Java 校验库,使用 Lambda 表达式(方法引用)和流式 API 构建声明式校验规则。
**特性:**
- 链式调用
- 支持 **POJO****Map** 两种校验模式
- 每种校验规则支持四种错误信息提供方式(默认/自定义字符串/自定义异常/含属性值的自定义异常)
- 基于 JDK 1.8
## 环境要求
- **JDK**1.8 及以上
- **编码**UTF-8
## 安装
Plusone Validator 暂未提交 maven 中央仓库,可 clone 代码仓库,通过 maven 安装到本地库,然后在项目中引入。
Plusone Validator 暂未发布至 Maven 中央仓库,需 clone 代码仓库后通过 `mvn install` 安装到本地仓库,然后在项目中引入:
```xml
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-validator</artifactId>
<version>${plusone-validator.version}</version>
</dependency>
```
> 该项目依赖 [plusone-commons](https://gitea.zhouxy.xyz/plusone/plusone-commons),它本身依赖 Guava。
## 示例
### 校验对象
以下示例基于一个包含姓名、邮箱、会员等级、客户编号、生日、地址等字段的 `Customer` 对象。
定义校验器:
### 校验 POJO 对象
继承 `BaseValidator<T>`,在构造器中通过 `ruleFor` 添加规则链:
```java
class CustomerValidator extends BaseValidator<Customer> {
@@ -25,14 +50,9 @@ class CustomerValidator extends BaseValidator<Customer> {
.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);
ruleFor(Customer::getVipLevel, Customer::getBirthday)
.must((vipLevel, birthday) -> vipLevel <= 5 || LocalDate.now().minusYears(18).isAfter(birthday),
"5级以上会员必须满18周岁");
}
public static CustomerValidator getInstance() {
@@ -51,7 +71,11 @@ public void foo(Customer customer) {
### 校验 Map
定义校验器:
继承 `MapValidator<K,V>`,在构造器中通过 `ruleForString``ruleForInt` 等方法按 key 添加规则链。
`validateAndCopy()` 会**先校验,然后仅保留白名单 key**(构造时传入的 `FIELD_NAMES`),多余字段会被自动剥离,可防止 Map 注入攻击。
> 注意泛型类型见证语法 `this.<LocalDate>ruleFor(...)` — 当 Map 的 value 类型是 `Object` 时,需显式指定实际类型。
```java
class CustomerMapValidator extends MapValidator<String, Object> {
@@ -73,15 +97,9 @@ class CustomerMapValidator extends MapValidator<String, Object> {
.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);
this.<Integer, LocalDate>ruleForPair("vipLevel", "birthday")
.must((vipLevel, birthday) -> vipLevel <= 5 || LocalDate.now().minusYears(18).isAfter(birthday),
"5级以上会员必须满18周岁");
}
public static CustomerMapValidator getInstance() {
@@ -97,10 +115,120 @@ public void foo(Map<String, Object> customer) {
// ...
}
```
## 快速参考
### ruleFor 方法一览
| 方法 | 属性类型 | 适用场景 |
|------|---------|---------|
| `ruleFor(getter)` | 任意类型 | 通用对象属性校验 |
| `ruleForString(getter)` | `String` | 字符串属性校验 |
| `ruleForInt(getter)` | `Integer` | 整数属性校验 |
| `ruleForLong(getter)` | `Long` | 长整数属性校验 |
| `ruleForDouble(getter)` | `Double` | 浮点数属性校验 |
| `ruleForBool(getter)` | `Boolean` | 布尔属性校验 |
| `ruleForComparable(getter)` | `Comparable` | 可比较类型属性校验 |
| `ruleForCollection(getter)` | `Collection<E>` | 集合属性校验 |
| `ruleForArray(getter)` | `E[]` | 数组属性校验 |
| `ruleFor(g1, g2)` | 二元组 | 两个属性的联合校验 |
### 校验规则一览
| 规则 | Object | Comparable | 数值 | String | Bool | 集合/数组 | 说明 |
|------|:---:|:---:|:---:|:---:|:---:|:---:|------|
| `notNull()` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 不为 null |
| `isNull()` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 必须为 null |
| `equal(obj)` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 等于给定值 |
| `notEqual(obj)` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 不等于给定值 |
| `must(pred)` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 自定义条件 |
| `gt/ge/lt/le` | — | — | ✅ | — | — | — | 大于/大于等于/小于/小于等于 |
| `inRange(range)` | — | ✅ | ✅ | ✅ | — | — | 值在指定区间内 |
| `notBlank()` | — | — | — | ✅ | — | — | 不为空白字符串 |
| `notEmpty()` | — | — | — | ✅ | — | ✅ | 不为空(长度 > 0 |
| `isEmpty()` | — | — | — | — | — | ✅ | 必须为空 |
| `length(min, max)` | — | — | — | ✅ | — | ✅ | 长度/大小在范围内 |
| `emailAddress()` | — | — | — | ✅ | — | — | 满足邮箱格式 |
| `matches(pattern)` | — | — | — | ✅ | — | — | 匹配正则表达式 |
| `matchesAny(patterns)` | — | — | — | ✅ | — | — | 匹配任一正则 |
| `matchesAll(patterns)` | — | — | — | ✅ | — | — | 匹配所有正则 |
| `allMatch(pred)` | — | — | — | — | — | ✅ | 所有元素满足条件 |
| `isTrueValue()` | — | — | — | — | ✅ | — | 必须为 true |
| `isFalseValue()` | — | — | — | — | ✅ | — | 必须为 false |
### 自定义错误信息
每个校验规则都支持四种错误信息提供方式:
```java
// 1. 默认消息
ruleFor(Person::getName).notNull();
// 2. 自定义字符串
ruleFor(Person::getName).notNull("姓名不能为空");
// 3. 自定义异常Supplier
ruleFor(Person::getName).notNull(() -> new BizException("姓名不能为空"));
// 4. 自定义异常 + 属性值注入Function
ruleFor(Person::getAge).gt(0, age -> new BizException("年龄必须 > 0当前值%d", age));
```
### Null 值处理
多数校验规则对 `null` **宽松处理**(视为通过),让用户通过 `notNull()` 显式控制 null 行为:
```java
// ✅ null 视为通过 — 不会因 NPE 失败
ruleFor(Person::getEmail).emailAddress();
// ✅ 显式拒绝 null
ruleFor(Person::getEmail).notNull("邮箱不能为空").emailAddress();
```
例外:`notBlank()``notEmpty()``null` 视为不通过null 本身就是 blank/empty
### 嵌套对象校验
使用 `withRule(Consumer)` 在子类构造器中调用嵌套校验器:
```java
class OrderValidator extends BaseValidator<Order> {
private static final OrderValidator INSTANCE = new OrderValidator();
private OrderValidator() {
ruleFor(Order::getOrderId).notBlank("订单号不能为空");
// 嵌套校验 Customer
withRule(order -> {
Customer customer = order.getCustomer();
CustomerValidator.getInstance().validate(customer);
});
// 需要自定义错误信息时try-catch 重新包装
withRule(order -> {
try {
AddressValidator.getInstance().validate(order.getAddress());
} catch (ValidationException e) {
throw ValidationException.withMessage("地址校验失败:%s", e.getMessage());
}
});
}
}
```
### 对象整体校验
使用 `withRule(Predicate)` 对多个属性进行联合校验:
```java
// 三个及以上字段的联合校验
withRule(order -> order.getTotalAmount().compareTo(order.getPaidAmount()) >= 0,
"订单金额不能小于已付金额");
```
---
## 其他
## 关于本项目
Plusone Validator 是个人在学习 Microsoft 的 [eShop](https://github.com/dotnet/eShop) 时,被其中 [FluentValidation](https://github.com/FluentValidation/FluentValidation) API 所吸引,出于学习和个人使用的目的进行开发和维护。使用 [Apache License 2.0](./LICENSE) 开源。
欢迎通过 issue 反馈使用过程中发现的问题和建议。
Plusone Validator 受 .NET 的 [FluentValidation](https://github.com/FluentValidation/FluentValidation) API 启发,使用 [Apache License 2.0](./LICENSE) 开源。

View File

@@ -8,28 +8,46 @@
<parent>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-validator-parent</artifactId>
<version>1.0.0-RC1</version>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>plusone-validator</artifactId>
<name>plusone-validator</name>
<url>http://zhouxy.xyz</url>
<description>
Plusone Validator 是一个校验库,使用 lambda 表达式(包括方法引用)和流式 API 构建校验规则,对对象进行校验。
</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<name>Zhou Xingyi (周兴毅)</name>
<url>https://gitea.zhouxy.xyz/plusone</url>
</developer>
</developers>
<scm>
<connection>scm:git:https://gitea.zhouxy.xyz/plusone/plusone-validator.git</connection>
<developerConnection>scm:git:ssh://gitea.zhouxy.xyz/plusone/plusone-validator.git</developerConnection>
<url>https://gitea.zhouxy.xyz/plusone/plusone-validator</url>
</scm>
<dependencies>
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-commons</artifactId>
<version>1.1.0-RC1</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@ import xyz.zhouxy.plusone.commons.util.AssertTools;
*
* @param <T> 待校验对象的类型
* @param <E> 数组元素的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class ArrayPropertyValidator<T, E>
extends BasePropertyValidator<T, E[], ArrayPropertyValidator<T, E>> {
@@ -151,11 +151,17 @@ public class ArrayPropertyValidator<T, E>
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当数组为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param condition 校验条件
* @return 当前校验器实例,用于链式调用
*/
public final ArrayPropertyValidator<T, E> allMatch(final Predicate<E> condition) {
return withRule(c -> {
if (ArrayTools.isEmpty(c)) {
return;
}
for (E element : c) {
if (!condition.test(element)) {
throw ValidationException.withMessage("All elements must match the condition.");
@@ -167,6 +173,9 @@ public class ArrayPropertyValidator<T, E>
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当数组为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param condition 校验条件
* @param errorMessage 异常信息
* @return 当前校验器实例,用于链式调用
@@ -174,6 +183,9 @@ public class ArrayPropertyValidator<T, E>
public final ArrayPropertyValidator<T, E> allMatch(
final Predicate<E> condition, final String errorMessage) {
return withRule(c -> {
if (ArrayTools.isEmpty(c)) {
return;
}
for (E element : c) {
if (!condition.test(element)) {
throw ValidationException.withMessage(errorMessage);
@@ -185,6 +197,9 @@ public class ArrayPropertyValidator<T, E>
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当数组为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param condition 校验条件
* @param exceptionSupplier 自定义异常
@@ -193,6 +208,9 @@ public class ArrayPropertyValidator<T, E>
public final <X extends RuntimeException> ArrayPropertyValidator<T, E> allMatch(
final Predicate<E> condition, final Supplier<X> exceptionSupplier) {
return withRule(c -> {
if (ArrayTools.isEmpty(c)) {
return;
}
for (E element : c) {
if (!condition.test(element)) {
throw exceptionSupplier.get();
@@ -204,6 +222,9 @@ public class ArrayPropertyValidator<T, E>
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当数组为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param condition 校验条件
* @param exceptionFunction 自定义异常
@@ -212,6 +233,9 @@ public class ArrayPropertyValidator<T, E>
public final <X extends RuntimeException> ArrayPropertyValidator<T, E> allMatch(
final Predicate<E> condition, final Function<E, X> exceptionFunction) {
return withRule(c -> {
if (ArrayTools.isEmpty(c)) {
return;
}
for (E element : c) {
if (!condition.test(element)) {
throw exceptionFunction.apply(element);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,11 +29,11 @@ import com.google.common.collect.Range;
* @param <TProperty> 待校验属性的类型,必须实现 {@code Comparable} 接口
* @param <TPropertyValidator> 具体校验器类型,用于支持链式调用
* @see Range
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public abstract class BaseComparablePropertyValidator<
T,
TProperty extends Comparable<TProperty>,
TProperty extends Comparable<? super TProperty>,
TPropertyValidator extends BaseComparablePropertyValidator<T, TProperty, TPropertyValidator>>
extends BasePropertyValidator<T, TProperty, TPropertyValidator> {
@@ -47,9 +47,9 @@ public abstract class BaseComparablePropertyValidator<
* @param range 区间
* @return 当前校验器实例,用于链式调用
*/
public final TPropertyValidator inRange(final Range<TProperty> range) {
public final TPropertyValidator inRange(final Range<? super TProperty> range) {
return withRule(Conditions.inRange(range), value -> ValidationException.withMessage(
"The input must in the interval %s. You entered %s.", range, value));
"The input must in the interval %s. You entered %s.", range, value));
}
/**
@@ -60,7 +60,7 @@ public abstract class BaseComparablePropertyValidator<
* @return 当前校验器实例,用于链式调用
*/
public final TPropertyValidator inRange(
final Range<TProperty> range, final String errorMessage) {
final Range<? super TProperty> range, final String errorMessage) {
return withRule(Conditions.inRange(range), errorMessage);
}
@@ -73,7 +73,7 @@ public abstract class BaseComparablePropertyValidator<
* @return 当前校验器实例,用于链式调用
*/
public final <X extends RuntimeException> TPropertyValidator inRange(
final Range<TProperty> range, final Supplier<X> exceptionSupplier) {
final Range<? super TProperty> range, final Supplier<X> exceptionSupplier) {
return withRule(Conditions.inRange(range), exceptionSupplier);
}
@@ -86,7 +86,7 @@ public abstract class BaseComparablePropertyValidator<
* @return 当前校验器实例,用于链式调用
*/
public final <X extends RuntimeException> TPropertyValidator inRange(
final Range<TProperty> range, final Function<TProperty, X> exceptionFunction) {
final Range<? super TProperty> range, final Function<TProperty, X> exceptionFunction) {
return withRule(Conditions.inRange(range), exceptionFunction);
}
@@ -95,8 +95,8 @@ public abstract class BaseComparablePropertyValidator<
*/
private static class Conditions {
private static <TProperty extends Comparable<TProperty>> Predicate<TProperty> inRange(
final Range<TProperty> range) {
private static <TProperty extends Comparable<? super TProperty>> Predicate<TProperty> inRange(
final Range<? super TProperty> range) {
return value -> value == null || range.contains(value);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,8 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
/**
* 属性校验器。包含针对属性的校验规则。
*
@@ -33,7 +35,7 @@ import java.util.function.Supplier;
* @param <T> 待校验对象的类型
* @param <TProperty> 待校验属性的类型
* @param <TPropertyValidator> 具体校验器类型,用于支持链式调用
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public abstract class BasePropertyValidator<
T,
@@ -135,9 +137,9 @@ public abstract class BasePropertyValidator<
/**
* 校验属性
*
* @param obj 属性所在的对象
* @param obj 属性所在的对象,允许为 {@code null}
*/
public final void validate(T obj) {
public final void validate(@Nullable T obj) {
for (Consumer<? super TProperty> consumer : consumers) {
consumer.accept(getter.apply(obj));
}
@@ -258,6 +260,9 @@ public abstract class BasePropertyValidator<
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param obj 用于比较的对象
* @return 当前校验器实例,用于链式调用
*/
@@ -269,6 +274,9 @@ public abstract class BasePropertyValidator<
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param obj 用于比较的对象
* @param errorMessage 异常信息
* @return 当前校验器实例,用于链式调用
@@ -281,6 +289,9 @@ public abstract class BasePropertyValidator<
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param obj 用于比较的对象
* @param exceptionSupplier 自定义异常
@@ -294,6 +305,9 @@ public abstract class BasePropertyValidator<
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param obj 用于比较的对象
* @param exceptionFunction 自定义异常
@@ -313,7 +327,10 @@ public abstract class BasePropertyValidator<
// ================================
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param obj 用于比较的对象
* @return 当前校验器实例,用于链式调用
@@ -324,7 +341,10 @@ public abstract class BasePropertyValidator<
}
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param obj 用于比较的对象
* @param errorMessage 异常信息
@@ -335,7 +355,10 @@ public abstract class BasePropertyValidator<
}
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param obj 用于比较的对象
@@ -348,7 +371,10 @@ public abstract class BasePropertyValidator<
}
/**
* 添加一条校验属性的规则,校验属性是否等于给定值
* 添加一条校验属性的规则,校验属性是否等于给定值
*
* <p>
* 当值为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param obj 用于比较的对象

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022-2025 the original author or authors.
* Copyright 2022-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package xyz.zhouxy.plusone.validator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -27,13 +28,15 @@ import java.util.function.Supplier;
import xyz.zhouxy.plusone.validator.function.*;
import org.jspecify.annotations.Nullable;
/**
* 校验器基类
* <p>
* 子类可通过添加不同的校验规则,构建完整的校验逻辑,用于校验对象。
*
* @param <T> 待校验对象的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public abstract class BaseValidator<T> implements IValidator<T> {
@@ -108,7 +111,7 @@ public abstract class BaseValidator<T> implements IValidator<T> {
* 示例:{@code Person::getName}。
* @return {@code ComparablePropertyValidator}。用于添加针对该属性的校验规则。
*/
protected final <R extends Comparable<R>> ComparablePropertyValidator<T, R> ruleForComparable(
protected final <R extends Comparable<? super R>> ComparablePropertyValidator<T, R> ruleForComparable(
Function<T, R> getter) {
ComparablePropertyValidator<T, R> validator = new ComparablePropertyValidator<>(getter);
this.rules.add(validator::validate);
@@ -280,16 +283,37 @@ public abstract class BaseValidator<T> implements IValidator<T> {
* @param <V2> 第二个元素的类型
* @param getter 根据对象构造一个二元组,通常是两个属性的值。
* @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);
this.rules.add(validator::validate);
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} */
@Override
public void validate(T obj) {
public void validate(@Nullable T obj) {
this.rules.forEach(rule -> rule.accept(obj));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.function.Supplier;
* 用于构建校验 {@code Boolean} 类型属性的规则链。
*
* @param <T> 待校验对象的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class BoolPropertyValidator<T>
extends BasePropertyValidator<T, Boolean, BoolPropertyValidator<T>> {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import xyz.zhouxy.plusone.commons.util.AssertTools;
*
* @param <T> 待校验对象的类型
* @param <E> 集合元素的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class CollectionPropertyValidator<T, E>
extends BasePropertyValidator<T, Collection<E>, CollectionPropertyValidator<T, E>> {
@@ -151,36 +151,56 @@ public class CollectionPropertyValidator<T, E>
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当集合为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param condition 校验条件
* @return 当前校验器实例,用于链式调用
*/
public final CollectionPropertyValidator<T, E> allMatch(
final Predicate<E> condition) {
return withRule(c -> c.forEach(element -> {
if (!condition.test(element)) {
throw ValidationException.withMessage("All elements must match the condition.");
return withRule(c -> {
if (CollectionTools.isEmpty(c)) {
return;
}
}));
c.forEach(element -> {
if (!condition.test(element)) {
throw ValidationException.withMessage("All elements must match the condition.");
}
});
});
}
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当集合为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param condition 校验条件
* @param errorMessage 异常信息
* @return 当前校验器实例,用于链式调用
*/
public final CollectionPropertyValidator<T, E> allMatch(
final Predicate<E> condition, final String errorMessage) {
return withRule(c -> c.forEach(element -> {
if (!condition.test(element)) {
throw ValidationException.withMessage(errorMessage);
return withRule(c -> {
if (CollectionTools.isEmpty(c)) {
return;
}
}));
c.forEach(element -> {
if (!condition.test(element)) {
throw ValidationException.withMessage(errorMessage);
}
});
});
}
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当集合为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param condition 校验条件
* @param exceptionSupplier 自定义异常
@@ -188,16 +208,24 @@ public class CollectionPropertyValidator<T, E>
*/
public final <X extends RuntimeException> CollectionPropertyValidator<T, E> allMatch(
final Predicate<E> condition, final Supplier<X> exceptionSupplier) {
return withRule(c -> c.forEach(element -> {
if (!condition.test(element)) {
throw exceptionSupplier.get();
return withRule(c -> {
if (CollectionTools.isEmpty(c)) {
return;
}
}));
c.forEach(element -> {
if (!condition.test(element)) {
throw exceptionSupplier.get();
}
});
});
}
/**
* 添加一条校验属性的规则,校验是否所有元素都满足指定条件
*
* <p>
* 当集合为 {@code null} 时,视为通过。如果需要校验值不为 {@code null},请使用 {@link #notNull()} 方法。
*
* @param <X> 自定义异常类型
* @param condition 校验条件
* @param exceptionFunction 自定义异常
@@ -205,11 +233,16 @@ public class CollectionPropertyValidator<T, E>
*/
public final <X extends RuntimeException> CollectionPropertyValidator<T, E> allMatch(
final Predicate<E> condition, final Function<E, X> exceptionFunction) {
return withRule(c -> c.forEach(element -> {
if (!condition.test(element)) {
throw exceptionFunction.apply(element);
return withRule(c -> {
if (CollectionTools.isEmpty(c)) {
return;
}
}));
c.forEach(element -> {
if (!condition.test(element)) {
throw exceptionFunction.apply(element);
}
});
});
}
// ================================

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,9 +24,9 @@ import java.util.function.Function;
* @param <T> 待校验对象的类型
* @param <TProperty> 待校验属性的类型,必须实现 {@code Comparable} 接口
* @see com.google.common.collect.Range
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class ComparablePropertyValidator<T, TProperty extends Comparable<TProperty>>
public class ComparablePropertyValidator<T, TProperty extends Comparable<? super TProperty>>
extends BaseComparablePropertyValidator<T, TProperty, ComparablePropertyValidator<T, TProperty>> {
ComparablePropertyValidator(Function<T, ? extends TProperty> getter) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.function.Supplier;
* 用于构建校验 {@code Double} 类型属性的规则链。
*
* @param <T> 待校验对象的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class DoublePropertyValidator<T>
extends BaseComparablePropertyValidator<T, Double, DoublePropertyValidator<T>> {
@@ -48,7 +48,7 @@ public class DoublePropertyValidator<T>
*/
public final DoublePropertyValidator<T> gt(final double min) {
return withRule(Conditions.greaterThan(min),
"The input must be greater than '%s'.", min);
"The input must be greater than '%f'.", min);
}
/**
@@ -105,7 +105,7 @@ public class DoublePropertyValidator<T>
*/
public final DoublePropertyValidator<T> ge(final double min) {
return withRule(Conditions.greaterThanOrEqualTo(min),
"The input must be greater than or equal to '%s'.", min);
"The input must be greater than or equal to '%f'.", min);
}
/**
@@ -162,7 +162,7 @@ public class DoublePropertyValidator<T>
*/
public final DoublePropertyValidator<T> lt(final double max) {
return withRule(Conditions.lessThan(max),
"The input must be less than '%s'.", max);
"The input must be less than '%f'.", max);
}
/**
@@ -219,7 +219,7 @@ public class DoublePropertyValidator<T>
*/
public final DoublePropertyValidator<T> le(final double max) {
return withRule(Conditions.lessThanOrEqualTo(max),
"The input must be less than or equal to '%s'.", max);
"The input must be less than or equal to '%f'.", max);
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ package xyz.zhouxy.plusone.validator;
/**
* 自带校验方法,校验不通过时直接抛异常。
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*
* @see BaseValidator
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
*/
package xyz.zhouxy.plusone.validator;
import org.jspecify.annotations.Nullable;
/**
* 校验器
*
@@ -23,14 +25,14 @@ package xyz.zhouxy.plusone.validator;
* </p>
*
* @param <T> 待校验对象的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public interface IValidator<T> {
/**
* 校验指定对象是否符合预定义规则
*
* @param obj 待校验的对象实例
* @param obj 待校验的对象实例,允许为 {@code null}
*/
void validate(T obj);
void validate(@Nullable T obj);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.function.Supplier;
* 用于构建校验 {@code Integer} 类型属性的规则链。
*
* @param <T> 待校验对象的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class IntPropertyValidator<T>
extends BaseComparablePropertyValidator<T, Integer, IntPropertyValidator<T>> {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.function.Supplier;
* 用于构建校验 {@code Long} 类型属性的规则链。
*
* @param <T> 待校验对象的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class LongPropertyValidator<T>
extends BaseComparablePropertyValidator<T, Long, LongPropertyValidator<T>> {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,22 +16,22 @@
package xyz.zhouxy.plusone.validator;
import java.util.AbstractMap.SimpleImmutableEntry;
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;
import org.jspecify.annotations.Nullable;
/**
* 对 Map 进行校验的校验器
*
* <p>
* 校验后拷贝出一个新的 Map 对象,仅保留指定的 key。
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public abstract class MapValidator<K, V> extends BaseValidator<Map<K, V>> {
@@ -61,10 +61,10 @@ public abstract class MapValidator<K, V> extends BaseValidator<Map<K, V>> {
/**
* 校验并拷贝,仅保留指定 key 的属性。
*
* @param obj 待校验的 map
* @param obj 待校验的 map,允许为 {@code null}
* @return 拷贝后的 map
*/
public final Map<K, V> validateAndCopy(Map<K, V> obj) {
public final Map<K, V> validateAndCopy(@Nullable Map<K, V> obj) {
return validateAndCopyInternal(obj, this.keys);
}
@@ -91,11 +91,11 @@ public abstract class MapValidator<K, V> extends BaseValidator<Map<K, V>> {
return validateAndCopyInternal(obj, Arrays.asList(keys));
}
private final Map<K, V> validateAndCopyInternal(Map<K, V> obj, Collection<K> keys) {
private final Map<K, V> validateAndCopyInternal(@Nullable Map<K, V> obj, Collection<K> keys) {
validate(obj);
return obj.entrySet().stream()
.filter(kv -> keys.contains(kv.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));
}
// ================================
@@ -175,7 +175,8 @@ public abstract class MapValidator<K, V> extends BaseValidator<Map<K, V>> {
* @param key key
* @return 属性校验器
*/
protected final <E extends Comparable<E>> ComparablePropertyValidator<Map<K, V>, E> ruleForComparable(K key) {
protected final <E extends Comparable<? super E>> //
ComparablePropertyValidator<Map<K, V>, E> ruleForComparable(K key) {
@SuppressWarnings("unchecked")
Function<Map<K, V>, E> getter = m -> (E) m.get(key);
return ruleForComparable(getter);
@@ -196,18 +197,19 @@ public abstract class MapValidator<K, V> extends BaseValidator<Map<K, V>> {
/**
* 添加一个属性校验器,对指定的两个 key 对应的 value 进行校验
* @param <V1> 第个属性的类型
* @param <V2> 第个属性的类型
* @param k1 第个 key
* @param k2 第个 key
* @param <V1> 第1个属性的类型
* @param <V2> 第2个属性的类型
* @param k1 第1个 key
* @param k2 第2个 key
* @return 属性校验器
*/
protected final <V1 extends V, V2 extends V>
PairPropertyValidator<Map<K, V>, V1, V2> ruleForPair(K k1, K k2) {
@SuppressWarnings("unchecked")
Function<Map<K, V>, Entry<V1, V2>> getter = m ->
new SimpleImmutableEntry<>((V1) m.get(k1), (V2) m.get(k2));
return ruleForPair(getter);
final Function<Map<K, V>, V1> v1Getter = m -> (V1) m.get(k1);
@SuppressWarnings("unchecked")
final Function<Map<K, V>, V2> v2Getter = m -> (V2) m.get(k2);
return ruleFor(v1Getter, v2Getter);
}
// ================================

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,12 +23,12 @@ import java.util.function.Function;
*
* @param <T> 待校验对象的类型
* @param <TProperty> 待校验属性的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class ObjectPropertyValidator<T, TProperty>
extends BasePropertyValidator<T, TProperty, ObjectPropertyValidator<T, TProperty>> {
ObjectPropertyValidator(Function<T, TProperty> getter) {
ObjectPropertyValidator(Function<T, ? extends TProperty> getter) {
super(getter);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.function.Supplier;
* @param <T> 被验证对象类型
* @param <V1> 第一个元素的类型
* @param <V2> 第二个元素的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class PairPropertyValidator<T, V1, V2>
extends BasePropertyValidator<T, Entry<V1, V2>, PairPropertyValidator<T, V1, V2>> {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
* 用于构建校验 {@code String} 类型属性的规则链。
*
* @param <T> 待校验对象的类型
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class StringPropertyValidator<T>
extends BaseComparablePropertyValidator<T, String, StringPropertyValidator<T>> {
@@ -547,7 +547,7 @@ public class StringPropertyValidator<T>
private static Predicate<String> length(int length) {
AssertTools.checkArgument(length >= 0,
"The expected length must be non-negative.");
"The expected length must be non-negative.");
return input -> input == null || input.length() == length;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,12 @@
*/
package xyz.zhouxy.plusone.validator;
import org.jspecify.annotations.Nullable;
/**
* 校验失败异常
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
public class ValidationException extends RuntimeException {
@@ -30,11 +32,11 @@ public class ValidationException extends RuntimeException {
super(message);
}
private ValidationException(Throwable cause) {
private ValidationException(@Nullable Throwable cause) {
super(cause);
}
private ValidationException(String message, Throwable cause) {
private ValidationException(String message, @Nullable Throwable cause) {
super(message, cause);
}
@@ -71,10 +73,10 @@ public class ValidationException extends RuntimeException {
/**
* 创建 {@code ValidationException} 实例
*
* @param cause 导致校验失败的根本异常
* @param cause 导致校验失败的根本异常,允许为 {@code null}
* @return {@code ValidationException} 实例
*/
public static ValidationException withCause(Throwable cause) {
public static ValidationException withCause(@Nullable Throwable cause) {
return new ValidationException(cause);
}
@@ -82,10 +84,10 @@ public class ValidationException extends RuntimeException {
* 创建 {@code ValidationException} 实例
*
* @param message 异常信息
* @param cause 导致校验失败的根本异常
* @param cause 导致校验失败的根本异常,允许为 {@code null}
* @return {@code ValidationException} 实例
*/
public static ValidationException withMessageAndCause(String message, Throwable cause) {
public static ValidationException withMessageAndCause(String message, @Nullable Throwable cause) {
return new ValidationException(message, cause);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import java.util.function.Function;
/**
* Function&lt;T, Boolean&gt;
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@FunctionalInterface
public interface ToBoolObjectFunction<T> extends Function<T, Boolean>, Serializable {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import java.util.function.Function;
/**
* Function&lt;T, Double&gt;
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@FunctionalInterface
public interface ToDoubleObjectFunction<T> extends Function<T, Double>, Serializable {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import java.util.function.Function;
/**
* Function&lt;T, Integer&gt;
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@FunctionalInterface
public interface ToIntegerFunction<T> extends Function<T, Integer>, Serializable {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import java.util.function.Function;
/**
* Function&lt;T, Long&gt;
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@FunctionalInterface
public interface ToLongObjectFunction<T> extends Function<T, Long>, Serializable {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import java.util.function.Function;
/**
* Function&lt;T, String&gt;
*
* @author ZhouXY
* @author ZhouXY108 <luquanlion@outlook.com>
*/
@FunctionalInterface
public interface ToStringFunction<T> extends Function<T, String>, Serializable {

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025-present ZhouXY
*
* 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.
*/
/**
* 函数式接口包,提供带序列化能力的特殊 {@code Function} 子类型。
*
* <p>
* 本包中的接口均继承自 {@link java.util.function.Function},同时实现
* {@link java.io.Serializable},使 lambda 表达式或方法引用可以被序列化。
* 主要用作 {@link xyz.zhouxy.plusone.validator.BaseValidator} 中
* {@code ruleFor} 方法族的参数类型,以支持特定类型的属性取值。
*
* <ul>
* <li>{@link xyz.zhouxy.plusone.validator.function.ToStringFunction ToStringFunction} — Function&lt;T, String&gt;</li>
* <li>{@link xyz.zhouxy.plusone.validator.function.ToIntegerFunction ToIntegerFunction} — Function&lt;T, Integer&gt;</li>
* <li>{@link xyz.zhouxy.plusone.validator.function.ToLongObjectFunction ToLongObjectFunction} — Function&lt;T, Long&gt;</li>
* <li>{@link xyz.zhouxy.plusone.validator.function.ToDoubleObjectFunction ToDoubleObjectFunction} — Function&lt;T, Double&gt;</li>
* <li>{@link xyz.zhouxy.plusone.validator.function.ToBoolObjectFunction ToBoolObjectFunction} — Function&lt;T, Boolean&gt;</li>
* </ul>
*/
@NullMarked
package xyz.zhouxy.plusone.validator.function;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2025-present ZhouXY
*
* 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.
*/
/**
* Plusone Validator 核心包,提供流式校验 API。
*
* <p>
* 使用 lambda 表达式(包括方法引用)和流式 API 构建校验规则,对对象进行校验。
* 支持校验普通对象POJO和 {@code Map} 对象。
*
* <h3>快速开始</h3>
* <pre>{@code
* public class PersonValidator extends BaseValidator<Person> {
* public PersonValidator() {
* ruleFor(Person::getName)
* .notNull()
* .notBlank();
* ruleForInt(Person::getAge)
* .gt(0);
* }
* }
* }</pre>
*
* <h3>核心概念</h3>
* <ul>
* <li>{@link xyz.zhouxy.plusone.validator.IValidator IValidator} — 校验器接口</li>
* <li>{@link xyz.zhouxy.plusone.validator.BaseValidator BaseValidator} — 校验器基类,通过继承并添加规则构建校验器</li>
* <li>{@link xyz.zhouxy.plusone.validator.BasePropertyValidator BasePropertyValidator} — 属性校验器基类</li>
* <li>{@link xyz.zhouxy.plusone.validator.MapValidator MapValidator} — Map 校验器</li>
* <li>{@link xyz.zhouxy.plusone.validator.ValidationException ValidationException} — 校验失败异常</li>
* </ul>
*
* @see xyz.zhouxy.plusone.validator.BaseValidator
* @see xyz.zhouxy.plusone.validator.MapValidator
*/
@NullMarked
package xyz.zhouxy.plusone.validator;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -293,8 +293,18 @@ public class ArrayPropertyValidatorTests {
}
};
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "1234", "12345", "123456" });
assertDoesNotThrow(() -> validator.validate(command));
{
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[] { "1234", "12345", "123456" });
assertDoesNotThrow(() -> validator.validate(command));
}
{
ExampleCommand command = exampleCommandWithStringArrayProperty(new String[0]);
assertDoesNotThrow(() -> validator.validate(command));
}
{
ExampleCommand command = exampleCommandWithStringArrayProperty(null);
assertDoesNotThrow(() -> validator.validate(command));
}
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023-2025 the original author or authors.
* Copyright 2023-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -298,8 +298,14 @@ public class CollectionPropertyValidatorTests {
}
};
ExampleCommand command = exampleCommandWithStringListProperty(Lists.newArrayList("1234", "12345", "123456"));
assertDoesNotThrow(() -> validator.validate(command));
{
ExampleCommand command = exampleCommandWithStringListProperty(Lists.newArrayList("1234", "12345", "123456"));
assertDoesNotThrow(() -> validator.validate(command));
}
{
ExampleCommand command = exampleCommandWithStringListProperty(null);
assertDoesNotThrow(() -> validator.validate(command));
}
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,7 +87,7 @@ public class DoublePropertyValidatorTests {
ValidationException e = assertThrows(
ValidationException.class, () -> validator.validate(command));
assertEquals(String.format("The input must be greater than '%s'.", MIN), e.getMessage());
assertEquals(String.format("The input must be greater than '%f'.", MIN), e.getMessage());
}
@ParameterizedTest
@@ -196,7 +196,7 @@ public class DoublePropertyValidatorTests {
ValidationException e = assertThrows(
ValidationException.class, () -> validator.validate(command));
assertEquals(String.format("The input must be greater than or equal to '%s'.", MIN), e.getMessage());
assertEquals(String.format("The input must be greater than or equal to '%f'.", MIN), e.getMessage());
}
@ParameterizedTest
@@ -305,7 +305,7 @@ public class DoublePropertyValidatorTests {
ValidationException e = assertThrows(
ValidationException.class, () -> validator.validate(command));
assertEquals(String.format("The input must be less than '%s'.", MAX), e.getMessage());
assertEquals(String.format("The input must be less than '%f'.", MAX), e.getMessage());
}
@ParameterizedTest
@@ -414,7 +414,7 @@ public class DoublePropertyValidatorTests {
ValidationException e = assertThrows(
ValidationException.class, () -> validator.validate(command));
assertEquals(String.format("The input must be less than or equal to '%s'.", MAX), e.getMessage());
assertEquals(String.format("The input must be less than or equal to '%f'.", MAX), e.getMessage());
}
@ParameterizedTest

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import xyz.zhouxy.plusone.validator.BaseValidator;
import xyz.zhouxy.plusone.validator.IValidator;
import xyz.zhouxy.plusone.validator.ValidationException;
@SuppressWarnings("deprecation")
public class PairPropertyValidatorTests {
static final String MESSAGE = "Validation failed.";
@@ -41,7 +42,9 @@ public class PairPropertyValidatorTests {
@Test
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()))
.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));
}
};
assertDoesNotThrow(() -> validator1.validate(command));
ExampleCommand command = exampleCommandWithIntAndStringListProperty(100, "100");
assertDoesNotThrow(() -> validator.validate(command));
IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
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
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()))
.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, "");
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals("The specified condition was not met for the input.", e.getMessage());
IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getStringProperty, ExampleCommand::getIntProperty)
.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
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()))
.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, "");
ValidationException e = assertThrows(ValidationException.class, () -> validator.validate(command));
assertEquals(MESSAGE, e.getMessage());
IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
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
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()))
.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, "");
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals(MESSAGE, e.getMessage());
IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getStringProperty, ExampleCommand::getIntProperty)
.must((str, intValue) -> Objects.equals(str, intValue.toString()), () -> ExampleException.withMessage(MESSAGE));
}
};
ExampleException e2 = assertThrows(ExampleException.class, () -> validator2.validate(command));
assertEquals(MESSAGE, e2.getMessage());
}
@Test
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()))
.must((str, intValue) -> Objects.equals(str, intValue.toString()),
(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, "");
ExampleException e = assertThrows(ExampleException.class, () -> validator.validate(command));
assertEquals("Validation failed: ('', 100).", e.getMessage());
IValidator<ExampleCommand> validator2 = new BaseValidator<ExampleCommand>() {
{
ruleFor(ExampleCommand::getStringProperty, ExampleCommand::getIntProperty)
.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

@@ -1,5 +1,5 @@
/*
* Copyright 2025 the original author or authors.
* Copyright 2025-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* Copyright 2024-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -122,14 +122,14 @@ class ParamsValidator extends MapValidator<String, Object> {
.notNull(d -> ExampleException.withMessage("The doubleProperty cannot be null, but it was %s", d));
ruleForString(STRING_PROPERTY)
.notNull();
ruleForComparable(DATE_TIME_PROPERTY)
this.<LocalDateTime>ruleForComparable(DATE_TIME_PROPERTY)
.notNull("The dateTimeProperty cannot be null");
ruleFor(OBJECT_PROPERTY)
.notNull(() -> ExampleException.withMessage("The objectProperty cannot be null"));
ruleForCollection(STRING_LIST_PROPERTY)
this.<String>ruleForCollection(STRING_LIST_PROPERTY)
.notNull(d -> ExampleException.withMessage("The stringListProperty cannot be null, but it was %s", d));
ruleForPair(STRING_PROPERTY, STRING_PROPERTY2)
this.<String, String>ruleForPair(STRING_PROPERTY, STRING_PROPERTY2)
.must((str1, str2) -> str1 != null && str1.equals(str2),
"'stringProperty' must be equal to 'stringProperty2'.");
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2025-present ZhouXY
*
* 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 static org.junit.jupiter.api.Assertions.*;

36
pom.xml
View File

@@ -6,10 +6,10 @@
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-validator-parent</artifactId>
<version>1.0.0-RC1</version>
<version>1.0.0-SNAPSHOT</version>
<name>plusone-validator-parent</name>
<url>http://zhouxy.xyz</url>
<url>https://gitea.zhouxy.xyz/plusone/plusone-validator</url>
<packaging>pom</packaging>
@@ -21,8 +21,33 @@
Plusone Validator 是一个校验库,使用 lambda 表达式(包括方法引用)和流式 API 构建校验规则,对对象进行校验。
</description>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<name>Zhou Xingyi (周兴毅)</name>
<url>https://gitea.zhouxy.xyz/plusone</url>
</developer>
</developers>
<scm>
<connection>scm:git:https://gitea.zhouxy.xyz/plusone/plusone-validator.git</connection>
<developerConnection>scm:git:ssh://gitea.zhouxy.xyz/plusone/plusone-validator.git</developerConnection>
<url>https://gitea.zhouxy.xyz/plusone/plusone-validator</url>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<plusone-commons.version>1.1.0-SNAPSHOT</plusone-commons.version>
</properties>
<dependencyManagement>
@@ -30,10 +55,15 @@
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-dependencies</artifactId>
<version>1.1.0-RC1</version>
<version>${plusone-commons.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-commons</artifactId>
<version>${plusone-commons.version}</version>
</dependency>
</dependencies>
</dependencyManagement>