001/** 002 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.gnu.org/licenses/lgpl-3.0.txt 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package dev.tinyflow.core.chain; 017 018import java.io.Serializable; 019import java.util.Collections; 020import java.util.Map; 021import java.util.Objects; 022 023/** 024 * 表示一个链式节点校验结果。 025 * 包含校验是否成功、消息说明以及附加的详细信息(如失败字段、原因等)。 026 * <p> 027 * 实例是不可变的(immutable),线程安全。 028 */ 029public class NodeValidResult implements Serializable { 030 031 private static final long serialVersionUID = 1L; 032 033 public static final NodeValidResult SUCCESS = new NodeValidResult(true, null, null); 034 public static final NodeValidResult FAILURE = new NodeValidResult(false, null, null); 035 036 private final boolean success; 037 private final String message; 038 private final Map<String, Object> details; 039 040 /** 041 * 私有构造器,确保通过工厂方法创建实例。 042 */ 043 private NodeValidResult(boolean success, String message, Map<String, Object> details) { 044 this.success = success; 045 this.message = message; 046 // 防御性拷贝,防止外部修改 047 this.details = details != null ? Collections.unmodifiableMap(new java.util.HashMap<>(details)) : null; 048 } 049 050 /** 051 * 获取校验是否成功。 052 */ 053 public boolean isSuccess() { 054 return success; 055 } 056 057 /** 058 * 获取结果消息(可为 null)。 059 */ 060 public String getMessage() { 061 return message; 062 } 063 064 /** 065 * 获取详细信息(如校验失败的字段、原因等),不可变 Map。 066 * 如果无详情,则返回 null。 067 */ 068 public Map<String, Object> getDetails() { 069 return details; 070 } 071 072 // ------------------ 静态工厂方法 ------------------ 073 074 /** 075 * 创建一个成功的校验结果(无消息、无详情)。 076 */ 077 public static NodeValidResult ok() { 078 return SUCCESS; 079 } 080 081 /** 082 * 创建一个成功的校验结果,附带消息。 083 */ 084 public static NodeValidResult ok(String message) { 085 return new NodeValidResult(true, message, null); 086 } 087 088 /** 089 * 创建一个成功的校验结果,附带消息和详情。 090 */ 091 public static NodeValidResult ok(String message, Map<String, Object> details) { 092 return new NodeValidResult(true, message, details); 093 } 094 095 /** 096 * 创建一个成功的校验结果,支持键值对形式传入 details。 097 * <p> 098 * 示例:success("验证通过", "userId", 123, "role", "admin") 099 * 100 * @param message 消息 101 * @param kvPairs 键值对(必须成对:key1, value1, key2, value2...) 102 * @return ChainNodeValidResult 103 * @throws IllegalArgumentException 如果 kvPairs 数量为奇数 104 */ 105 public static NodeValidResult ok(String message, Object... kvPairs) { 106 Map<String, Object> details = toMapFromPairs(kvPairs); 107 return new NodeValidResult(true, message, details); 108 } 109 110 111 /** 112 * 创建一个失败的校验结果(无消息、无详情)。 113 */ 114 public static NodeValidResult fail() { 115 return FAILURE; 116 } 117 118 /** 119 * 创建一个失败的校验结果,仅包含消息。 120 */ 121 public static NodeValidResult fail(String message) { 122 return new NodeValidResult(false, message, null); 123 } 124 125 /** 126 * 创建一个失败的校验结果,包含消息和详情。 127 */ 128 public static NodeValidResult fail(String message, Map<String, Object> details) { 129 return new NodeValidResult(false, message, details); 130 } 131 132 /** 133 * 创建一个失败的校验结果,支持键值对形式传入 details。 134 * <p> 135 * 示例:fail("验证失败", "field", "email", "reason", "格式错误") 136 */ 137 public static NodeValidResult fail(String message, Object... kvPairs) { 138 Map<String, Object> details = toMapFromPairs(kvPairs); 139 return new NodeValidResult(false, message, details); 140 } 141 142 /** 143 * 快捷方法:创建包含字段错误的失败结果。 144 * 适用于表单/参数校验场景。 145 * 146 * @param field 错误字段名 147 * @param reason 错误原因 148 * @return 失败结果 149 */ 150 public static NodeValidResult failOnField(String field, String reason) { 151 Map<String, Object> details = Collections.singletonMap("fieldError", field + ": " + reason); 152 return fail(reason, details); 153 } 154 155 /** 156 * 快捷方法:基于布尔值返回成功或失败结果。 157 * 158 * @param condition 条件 159 * @param messageIfFail 条件不满足时的消息 160 * @return 根据条件返回对应结果 161 */ 162 public static NodeValidResult require(boolean condition, String messageIfFail) { 163 return condition ? ok() : fail(messageIfFail); 164 } 165 166 167 private static Map<String, Object> toMapFromPairs(Object... kvPairs) { 168 if (kvPairs == null || kvPairs.length == 0) { 169 return null; 170 } 171 172 if (kvPairs.length % 2 != 0) { 173 throw new IllegalArgumentException("kvPairs must be even-sized: key1, value1, key2, value2..."); 174 } 175 176 Map<String, Object> map = new java.util.HashMap<>(); 177 for (int i = 0; i < kvPairs.length; i += 2) { 178 Object key = kvPairs[i]; 179 Object value = kvPairs[i + 1]; 180 181 if (!(key instanceof String)) { 182 throw new IllegalArgumentException("Key must be a String, but got: " + key); 183 } 184 185 map.put((String) key, value); 186 } 187 return Collections.unmodifiableMap(map); 188 } 189 190 @Override 191 public boolean equals(Object o) { 192 if (this == o) return true; 193 if (!(o instanceof NodeValidResult)) return false; 194 NodeValidResult that = (NodeValidResult) o; 195 return success == that.success && 196 Objects.equals(message, that.message) && 197 Objects.equals(details, that.details); 198 } 199 200 @Override 201 public int hashCode() { 202 return Objects.hash(success, message, details); 203 } 204 205 @Override 206 public String toString() { 207 return "ChainNodeValidResult{" + 208 "success=" + success + 209 ", message='" + message + '\'' + 210 ", details=" + details + 211 '}'; 212 } 213}