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}