Skip to main content

xyz_rust/spec/
validate.rs

1// 库内 validator:go-playground/validator 语法的兼容子集,零第三方依赖。
2// 不支持的规则在注册期(xyz_meta 解析)报错——与「注册期即报错」原则
3// 一致,而不是运行时悄悄忽略。
4
5use crate::errors;
6use crate::spec::XyzField;
7use crate::spec::field::FieldMeta;
8
9/// 一条已解析的校验规则;num 是数值参数(-1 表示非数值规则)。
10#[derive(Debug, Clone, PartialEq)]
11pub struct VRule {
12    pub key: String,
13    pub args: Vec<String>,
14    pub num: f64,
15}
16
17/// 带一个数值参数的规则集合。
18pub const NUMERIC_RULE_KEYS: &[&str] = &["min", "max", "len", "gt", "gte", "lt", "lte"];
19
20/// 本实现支持的规则全集。
21pub const SUPPORTED_RULE_KEYS: &[&str] = &[
22    "required",
23    "omitempty",
24    "min",
25    "max",
26    "len",
27    "gt",
28    "gte",
29    "lt",
30    "lte",
31    "oneof",
32    "email",
33];
34
35/// 解析 validate tag。规则逗号分隔,参数用 = 或空格给出
36/// (与 go-playground/validator 一致:"required,min=2,oneof=fast slow")。
37pub fn parse_validate_tag(v: &str) -> errors::Result<Vec<VRule>> {
38    if v.trim().is_empty() {
39        return Ok(Vec::new());
40    }
41    let mut out = Vec::new();
42    for part in v.split(',').map(str::trim) {
43        if part.is_empty() {
44            continue;
45        }
46        let (key, rest) = match part.split_once('=') {
47            Some((k, r)) => (k.trim(), r),
48            None => (part, ""),
49        };
50        if !SUPPORTED_RULE_KEYS.contains(&key) {
51            return Err(errors::Error::new(
52                errors::Kind::Internal,
53                format!(
54                    "unsupported validate rule {key:?} (supported: required, omitempty, min, max, len, gt, gte, lt, lte, oneof, email)"
55                ),
56            ));
57        }
58        let mut rule = VRule {
59            key: key.to_string(),
60            args: rest.split_whitespace().map(str::to_string).collect(),
61            num: -1.0,
62        };
63        if NUMERIC_RULE_KEYS.contains(&key) {
64            if rule.args.len() != 1 {
65                return Err(errors::Error::new(
66                    errors::Kind::Internal,
67                    format!(
68                        "validate rule {key:?} needs exactly one numeric argument, got {rest:?}"
69                    ),
70                ));
71            }
72            let n = rule.args[0].parse::<f64>().map_err(|_| {
73                errors::Error::new(
74                    errors::Kind::Internal,
75                    format!("validate rule {key:?}: {:?} is not a number", rule.args[0]),
76                )
77            })?;
78            rule.num = n;
79            rule.args.clear(); // 数值参数不进 args(oneof 专用)
80        }
81        out.push(rule);
82    }
83    Ok(out)
84}
85
86/// 构造字段规则错误(Go fieldRuleError 的对应物)。
87pub fn field_rule_error(f: &FieldMeta, rule: &str) -> errors::Error {
88    let name = if f.json_name.is_empty() {
89        f.name.as_str()
90    } else {
91        f.json_name.as_str()
92    };
93    errors::Error::new(
94        errors::Kind::InvalidInput,
95        format!("invalid value for field {name:?}: {rule}"),
96    )
97}
98
99/// 对单值字段跑规则集(omitempty 空的短路、required 的零值拒绝)。
100/// 宏生成的 xyz_validate 对每个叶子字段调用;嵌套与容器由宏生成代码
101/// 自行递归。
102pub fn check_rules<T: XyzField>(f: &FieldMeta, v: &T) -> errors::Result<()> {
103    if f.rules.is_empty() {
104        return Ok(());
105    }
106    let zero = v.xyz_is_zero();
107    for r in &f.rules {
108        match r.key.as_str() {
109            "omitempty" => {
110                if zero {
111                    break; // 空值:跳过本字段全部校验
112                }
113                continue;
114            }
115            "required" => {
116                if zero {
117                    return Err(field_rule_error(f, "required"));
118                }
119                continue;
120            }
121            _ => {
122                if !v.xyz_rule_ok(r) {
123                    return Err(field_rule_error(f, &r.key));
124                }
125            }
126        }
127    }
128    Ok(())
129}
130
131/// Option<T> 字段的规则检查(Go 指针解引用语义:nil 对 min/max/len
132/// 判假、required 判零)。
133pub fn check_opt_rules<T: XyzField>(f: &FieldMeta, v: Option<&T>) -> errors::Result<()> {
134    if f.rules.is_empty() {
135        return Ok(());
136    }
137    let zero = v.is_none();
138    for r in &f.rules {
139        match r.key.as_str() {
140            "omitempty" => {
141                if zero {
142                    break;
143                }
144                continue;
145            }
146            "required" => {
147                if zero {
148                    return Err(field_rule_error(f, "required"));
149                }
150                continue;
151            }
152            _ => match v {
153                Some(inner) => {
154                    if !inner.xyz_rule_ok(r) {
155                        return Err(field_rule_error(f, &r.key));
156                    }
157                }
158                None => {
159                    return Err(field_rule_error(f, &r.key));
160                }
161            },
162        }
163    }
164    Ok(())
165}
166
167/// Vec<T> 字段的规则检查(min/max/len 按长度;oneof 按元素 %v 形态)。
168pub fn check_vec_rules<T: XyzField>(f: &FieldMeta, v: &[T]) -> errors::Result<()> {
169    if f.rules.is_empty() {
170        return Ok(());
171    }
172    let zero = v.is_empty();
173    let fmt = format!(
174        "[{}]",
175        v.iter().map(|x| x.xyz_fmt()).collect::<Vec<_>>().join(" ")
176    );
177    for r in &f.rules {
178        match r.key.as_str() {
179            "omitempty" => {
180                if zero {
181                    break;
182                }
183                continue;
184            }
185            "required" => {
186                if zero {
187                    return Err(field_rule_error(f, "required"));
188                }
189                continue;
190            }
191            "min" | "max" | "len" => {
192                let n = v.len() as f64;
193                let ok = match r.key.as_str() {
194                    "min" => n >= r.num,
195                    "max" => n <= r.num,
196                    _ => n == r.num,
197                };
198                if !ok {
199                    return Err(field_rule_error(f, &r.key));
200                }
201            }
202            "oneof" => {
203                if !r.args.iter().any(|a| a == &fmt) {
204                    return Err(field_rule_error(f, &r.key));
205                }
206            }
207            _ => {
208                return Err(field_rule_error(f, &r.key));
209            }
210        }
211    }
212    Ok(())
213}
214/// 容器的元素元数据路径(Vec<T>/Option<T> 嵌套校验用);无元素时 None。
215pub fn elem_ref(f: &FieldMeta) -> Option<&FieldMeta> {
216    f.elem.as_deref()
217}