diff --git a/agents-flex-core/src/main/java/com/agentsflex/core/chain/ChainNodeValidResult.java b/agents-flex-core/src/main/java/com/agentsflex/core/chain/ChainNodeValidResult.java new file mode 100644 index 0000000..2f4e233 --- /dev/null +++ b/agents-flex-core/src/main/java/com/agentsflex/core/chain/ChainNodeValidResult.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2023-2025, Agents-Flex (fuhai999@gmail.com). + *

+ * 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 + *

+ * http://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 com.agentsflex.core.chain; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; + +/** + * 表示一个链式节点校验结果。 + * 包含校验是否成功、消息说明以及附加的详细信息(如失败字段、原因等)。 + *

+ * 实例是不可变的(immutable),线程安全。 + */ +public class ChainNodeValidResult implements Serializable { + + private static final long serialVersionUID = 1L; + + public static final ChainNodeValidResult SUCCESS = new ChainNodeValidResult(true, null, null); + public static final ChainNodeValidResult FAILURE = new ChainNodeValidResult(false, null, null); + + private final boolean success; + private final String message; + private final Map details; + + /** + * 私有构造器,确保通过工厂方法创建实例。 + */ + private ChainNodeValidResult(boolean success, String message, Map details) { + this.success = success; + this.message = message; + // 防御性拷贝,防止外部修改 + this.details = details != null ? Collections.unmodifiableMap(new java.util.HashMap<>(details)) : null; + } + + /** + * 获取校验是否成功。 + */ + public boolean isSuccess() { + return success; + } + + /** + * 获取结果消息(可为 null)。 + */ + public String getMessage() { + return message; + } + + /** + * 获取详细信息(如校验失败的字段、原因等),不可变 Map。 + * 如果无详情,则返回 null。 + */ + public Map getDetails() { + return details; + } + + // ------------------ 静态工厂方法 ------------------ + + /** + * 创建一个成功的校验结果(无消息、无详情)。 + */ + public static ChainNodeValidResult ok() { + return SUCCESS; + } + + /** + * 创建一个成功的校验结果,附带消息。 + */ + public static ChainNodeValidResult ok(String message) { + return new ChainNodeValidResult(true, message, null); + } + + /** + * 创建一个成功的校验结果,附带消息和详情。 + */ + public static ChainNodeValidResult ok(String message, Map details) { + return new ChainNodeValidResult(true, message, details); + } + + /** + * 创建一个成功的校验结果,支持键值对形式传入 details。 + *

+ * 示例:success("验证通过", "userId", 123, "role", "admin") + * + * @param message 消息 + * @param kvPairs 键值对(必须成对:key1, value1, key2, value2...) + * @return ChainNodeValidResult + * @throws IllegalArgumentException 如果 kvPairs 数量为奇数 + */ + public static ChainNodeValidResult ok(String message, Object... kvPairs) { + Map details = toMapFromPairs(kvPairs); + return new ChainNodeValidResult(true, message, details); + } + + + /** + * 创建一个失败的校验结果(无消息、无详情)。 + */ + public static ChainNodeValidResult fail() { + return FAILURE; + } + + /** + * 创建一个失败的校验结果,仅包含消息。 + */ + public static ChainNodeValidResult fail(String message) { + return new ChainNodeValidResult(false, message, null); + } + + /** + * 创建一个失败的校验结果,包含消息和详情。 + */ + public static ChainNodeValidResult fail(String message, Map details) { + return new ChainNodeValidResult(false, message, details); + } + + /** + * 创建一个失败的校验结果,支持键值对形式传入 details。 + *

+ * 示例:fail("验证失败", "field", "email", "reason", "格式错误") + */ + public static ChainNodeValidResult fail(String message, Object... kvPairs) { + Map details = toMapFromPairs(kvPairs); + return new ChainNodeValidResult(false, message, details); + } + + /** + * 快捷方法:创建包含字段错误的失败结果。 + * 适用于表单/参数校验场景。 + * + * @param field 错误字段名 + * @param reason 错误原因 + * @return 失败结果 + */ + public static ChainNodeValidResult failOnField(String field, String reason) { + Map details = Collections.singletonMap("fieldError", field + ": " + reason); + return fail(reason, details); + } + + /** + * 快捷方法:基于布尔值返回成功或失败结果。 + * + * @param condition 条件 + * @param messageIfFail 条件不满足时的消息 + * @return 根据条件返回对应结果 + */ + public static ChainNodeValidResult require(boolean condition, String messageIfFail) { + return condition ? ok() : fail(messageIfFail); + } + + + private static Map toMapFromPairs(Object... kvPairs) { + if (kvPairs == null || kvPairs.length == 0) { + return null; + } + + if (kvPairs.length % 2 != 0) { + throw new IllegalArgumentException("kvPairs must be even-sized: key1, value1, key2, value2..."); + } + + Map map = new java.util.HashMap<>(); + for (int i = 0; i < kvPairs.length; i += 2) { + Object key = kvPairs[i]; + Object value = kvPairs[i + 1]; + + if (!(key instanceof String)) { + throw new IllegalArgumentException("Key must be a String, but got: " + key); + } + + map.put((String) key, value); + } + return Collections.unmodifiableMap(map); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ChainNodeValidResult)) return false; + ChainNodeValidResult that = (ChainNodeValidResult) o; + return success == that.success && + Objects.equals(message, that.message) && + Objects.equals(details, that.details); + } + + @Override + public int hashCode() { + return Objects.hash(success, message, details); + } + + @Override + public String toString() { + return "ChainNodeValidResult{" + + "success=" + success + + ", message='" + message + '\'' + + ", details=" + details + + '}'; + } +}