Skip to main content

zz_validator/
validator.rs

1use std::collections::HashMap;
2
3use crate::{
4    ast::{Constraint, FieldRule, FieldType, Value},
5    parser::Parser,
6    token::tokenize,
7};
8use regex::Regex;
9
10/// -----------------------------
11/// Validator
12/// -----------------------------
13pub fn validate_field(value: &mut Value, rule: &FieldRule) -> Result<(), String> {
14    // 对对象,先填充默认值
15    if let Value::Object(obj) = value {
16        if !obj.contains_key(&rule.field) {
17            if let Some(d) = &rule.default {
18                obj.insert(rule.field.clone(), d.clone());
19            }
20        }
21    }
22
23    // 获取值
24    let val_opt = match value {
25        Value::Object(obj) => obj.get_mut(&rule.field),
26        _ => Some(value),
27    };
28
29    if val_opt.is_none() {
30        if rule.required {
31            return Err(format!("Missing required field {}", rule.field));
32        } else {
33            return Ok(());
34        }
35    }
36
37    let val = val_opt.unwrap();
38    if !rule.required {
39        if let Value::String(s) = val {
40            if s.is_empty() {
41                return Ok(());
42            }
43        }
44    }
45    // union types 验证
46    if let Some(types) = &rule.union_types {
47        let mut ok = false;
48        for t in types {
49            if validate_type(val, t).is_ok() {
50                ok = true;
51                break;
52            }
53        }
54        if !ok {
55            return Err(format!(
56                "{} value {:?} does not match union types {:?}",
57                rule.field, val, types
58            ));
59        }
60    } else {
61        // validate_type(val, &rule.field_type)?;
62        validate_type(val, &rule.field_type)
63            .map_err(|e| format!("{} value {:?}: {}", rule.field, val, e))?;
64    }
65
66    // enum 验证
67    if let Some(enum_vals) = &rule.enum_values {
68        if !enum_vals.contains(val) {
69            return Err(format!(
70                "{} value {:?} not in enum {:?}",
71                rule.field, val, enum_vals
72            ));
73        }
74    }
75
76    // constraints 验证
77    if let Some(c) = &rule.constraints {
78        for con in &c.items {
79            match con {
80                Constraint::Range {
81                    min,
82                    max,
83                    min_inclusive,
84                    max_inclusive,
85                } => {
86                    match val {
87                        Value::Int(i) => {
88                            let n = *i as f64;
89                            // --- 逻辑修正:最小值向上取整,最大值向下取整 ---
90                            let min_v = match min {
91                                Value::Int(mi) => *mi as f64,
92                                Value::Float(mf) => mf.ceil(), // 1.2 -> 2.0
93                                _ => {
94                                    return Err(format!(
95                                        "Invalid min value type for {}",
96                                        rule.field
97                                    ));
98                                }
99                            };
100                            let max_v = match max {
101                                Value::Int(mi) => *mi as f64,
102                                Value::Float(mf) => mf.floor(), // 5.8 -> 5.0
103                                _ => {
104                                    return Err(format!(
105                                        "Invalid max value type for {}",
106                                        rule.field
107                                    ));
108                                }
109                            };
110                            // ------------------------------------------
111
112                            let min_ok = if *min_inclusive {
113                                n >= min_v
114                            } else {
115                                n > min_v
116                            };
117                            let max_ok = if *max_inclusive {
118                                n <= max_v
119                            } else {
120                                n < max_v
121                            };
122
123                            if !min_ok || !max_ok {
124                                return Err(format!(
125                                    "{} value {} out of range [{}, {}]",
126                                    rule.field, i, min_v, max_v
127                                ));
128                            }
129                        }
130                        Value::Float(f) => {
131                            let n = *f;
132                            let min_v = match min {
133                                Value::Int(mi) => *mi as f64,
134                                Value::Float(mf) => *mf,
135                                _ => {
136                                    return Err(format!(
137                                        "Invalid min value type in range for {}",
138                                        rule.field
139                                    ));
140                                }
141                            };
142                            let max_v = match max {
143                                Value::Int(mi) => *mi as f64,
144                                Value::Float(mf) => *mf,
145                                _ => {
146                                    return Err(format!(
147                                        "Invalid max value type in range for {}",
148                                        rule.field
149                                    ));
150                                }
151                            };
152                            let min_ok = if *min_inclusive {
153                                n >= min_v
154                            } else {
155                                n > min_v
156                            };
157                            let max_ok = if *max_inclusive {
158                                n <= max_v
159                            } else {
160                                n < max_v
161                            };
162                            if !min_ok || !max_ok {
163                                return Err(format!(
164                                    "{} value {:?} out of range [{:?}, {:?}]",
165                                    rule.field, val, min, max
166                                ));
167                            }
168                        }
169                        Value::String(s) => {
170                            let n = s.len();
171                            // min/max 可以是 Value::Int 或 Value::String
172                            let min_v = match min {
173                                Value::Int(mi) => *mi as usize,
174                                Value::String(s) => s
175                                    .parse::<usize>()
176                                    .map_err(|_| format!("Failed to parse '{}' as usize", s))?,
177                                _ => {
178                                    return Err(format!(
179                                        "Invalid min value type in range for {}",
180                                        rule.field
181                                    ));
182                                }
183                            };
184                            let max_v = match max {
185                                Value::Int(mi) => *mi as usize,
186                                Value::String(s) => s
187                                    .parse::<usize>()
188                                    .map_err(|_| format!("Failed to parse '{}' as usize", s))?,
189                                _ => {
190                                    return Err(format!(
191                                        "Invalid max value type in range for {}",
192                                        rule.field
193                                    ));
194                                }
195                            };
196                            let min_ok = if *min_inclusive {
197                                n >= min_v
198                            } else {
199                                n > min_v
200                            };
201                            let max_ok = if *max_inclusive {
202                                n <= max_v
203                            } else {
204                                n < max_v
205                            };
206                            if !min_ok || !max_ok {
207                                return Err(format!(
208                                    "{} length {} out of range [{:?}, {:?}]",
209                                    rule.field, n, min, max
210                                ));
211                            }
212                        }
213                        _ => {
214                            return Err(format!(
215                                "{} cannot apply range constraint to {:?}",
216                                rule.field, val
217                            ));
218                        }
219                    }
220                }
221                Constraint::Regex(pattern) => {
222                    let s = val
223                        .as_str()
224                        .ok_or(format!("{} not string for regex", rule.field))?;
225                    let re = Regex::new(pattern).map_err(|e| format!("Invalid regex: {}", e))?;
226                    if !re.is_match(s) {
227                        return Err(format!("{} regex mismatch: {}", rule.field, pattern));
228                    }
229                }
230            }
231        }
232    }
233
234    // sub_rule / array / object 递归验证
235    if let Some(sub_rule) = &rule.rule {
236        match val {
237            Value::Object(_) => validate_field(val, sub_rule)?,
238            Value::Array(arr) => {
239                for v in arr.iter_mut() {
240                    validate_field(v, sub_rule)?;
241                }
242            }
243            _ => {}
244        }
245    }
246
247    if let Some(children) = &rule.children {
248        if let Value::Object(_) = val {
249            for child_rule in children {
250                validate_field(val, child_rule)?;
251            }
252        } else {
253            return Err(format!("{} is not object but has children", rule.field));
254        }
255    }
256
257    Ok(())
258}
259
260pub fn validate_type(value: &Value, t: &FieldType) -> Result<(), String> {
261    match t {
262        FieldType::String => {
263            if value.as_str().is_some() {
264                Ok(())
265            } else {
266                Err("Not string".into())
267            }
268        }
269        FieldType::Int => {
270            if value.as_int().is_some() {
271                Ok(())
272            } else {
273                Err("Not int".into())
274            }
275        }
276        FieldType::Float => {
277            if value.as_float().is_some() {
278                Ok(())
279            } else {
280                Err("Not float".into())
281            }
282        }
283        FieldType::Bool => {
284            if value.as_bool().is_some() {
285                Ok(())
286            } else {
287                Err("Not bool".into())
288            }
289        }
290        FieldType::Object => {
291            if value.as_object().is_some() {
292                Ok(())
293            } else {
294                Err("Not object".into())
295            }
296        }
297        FieldType::Array => {
298            if value.as_array().is_some() {
299                Ok(())
300            } else {
301                Err("Not array".into())
302            }
303        }
304        FieldType::Email => {
305            let s = value.as_str().ok_or("Not string for email")?;
306            let re = Regex::new(r"^[^@\s]+@[^@\s]+\.[^@\s]+$").unwrap();
307            if !re.is_match(s) {
308                return Err(format!("{:?} is not a valid email", value));
309            }
310            Ok(())
311        }
312        FieldType::Uri => {
313            let s = value.as_str().ok_or("Not string for uri")?;
314            let url = url::Url::parse(s).map_err(|_| format!("{} is not a valid URI", s))?;
315            Ok(())
316        }
317        FieldType::Uuid => {
318            let s = value.as_str().ok_or("Not string for uuid")?;
319            let re = Regex::new(
320                r"^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}$"
321            ).unwrap();
322            if !re.is_match(s) {
323                return Err(format!("{} is not a valid UUID", s));
324            }
325            Ok(())
326        }
327
328        FieldType::Ip => {
329            let s = value.as_str().ok_or("Not string for ip")?;
330            let re =
331                Regex::new(r"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$")
332                    .unwrap();
333            if re.is_match(s) {
334                Ok(())
335            } else {
336                Err(format!("Invalid ip: {}", s))
337            }
338        }
339        FieldType::Mac => {
340            let s = value.as_str().ok_or("Not string for mac")?;
341            let re = Regex::new(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$").unwrap();
342            if re.is_match(s) {
343                Ok(())
344            } else {
345                Err(format!("Invalid mac: {}", s))
346            }
347        }
348        FieldType::Date => {
349            let s = value.as_str().ok_or("Not string for date")?;
350            let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
351            if re.is_match(s) {
352                Ok(())
353            } else {
354                Err(format!("Invalid date: {}", s))
355            }
356        }
357        FieldType::DateTime => {
358            let s = value.as_str().ok_or("Not string for datetime")?;
359            let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?$").unwrap();
360            if re.is_match(s) {
361                Ok(())
362            } else {
363                Err(format!("Invalid datetime: {}", s))
364            }
365        }
366        FieldType::Time => {
367            let s = value.as_str().ok_or("Not string for time")?;
368            let re = Regex::new(r"^\d{2}:\d{2}:\d{2}$").unwrap();
369            if re.is_match(s) {
370                Ok(())
371            } else {
372                Err(format!("Invalid time: {}", s))
373            }
374        }
375        FieldType::Timestamp => {
376            value.as_int().ok_or("Not number for timestamp")?;
377            Ok(())
378        }
379        FieldType::Color => {
380            let s = value.as_str().ok_or("Not string for color")?;
381            let re = Regex::new(r"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$").unwrap();
382            if re.is_match(s) {
383                Ok(())
384            } else {
385                Err(format!("Invalid color: {}", s))
386            }
387        }
388        FieldType::Hostname => {
389            let s = value.as_str().ok_or("Not string for hostname")?;
390            // 1. 手动检查总长度 (对应原正则中的 (?=.{1,253}$) )
391            if s.is_empty() || s.len() > 253 {
392                return Err(format!("Invalid hostname length: {}", s));
393            }
394            // 2. 使用不含断言的兼容正则
395            let re = Regex::new(
396                r"^(?:[a-zA-Z0-9_](?:[a-zA-Z0-9_-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$",
397            )
398            .unwrap();
399
400            if re.is_match(s) {
401                Ok(())
402            } else {
403                Err(format!("Invalid hostname: {}", s))
404            }
405        }
406        FieldType::Slug => {
407            let s = value.as_str().ok_or("Not string for slug")?;
408            let re = Regex::new(r"^[a-z0-9]+(?:-[a-z0-9]+)*$").unwrap();
409            if re.is_match(s) {
410                Ok(())
411            } else {
412                Err(format!("Invalid slug: {}", s))
413            }
414        }
415        FieldType::Hex => {
416            let s = value.as_str().ok_or("Not string for hex")?;
417            let re = Regex::new(r"^[0-9a-fA-F]+$").unwrap();
418            if re.is_match(s) {
419                Ok(())
420            } else {
421                Err(format!("Invalid hex: {}", s))
422            }
423        }
424        FieldType::Base64 => {
425            let s = value.as_str().ok_or("Not string for base64")?;
426            let re = Regex::new(r"^[A-Za-z0-9+/]+={0,2}$").unwrap();
427            if re.is_match(s) {
428                Ok(())
429            } else {
430                Err(format!("Invalid base64: {}", s))
431            }
432        }
433        FieldType::Password => {
434            if value.as_str().is_some() {
435                Ok(())
436            } else {
437                Err("Not string for password".into())
438            }
439        }
440        FieldType::Token => {
441            if value.as_str().is_some() {
442                Ok(())
443            } else {
444                Err("Not string for token".into())
445            }
446        }
447    }
448}
449
450pub fn validate_object(value: &mut Value, rules: &[FieldRule]) -> Result<(), String> {
451    if let Value::Object(_) = value {
452        for rule in rules {
453            validate_field(value, rule)?;
454        }
455        Ok(())
456    } else {
457        Err("Value is not object".into())
458    }
459}
460
461pub fn validate_rule(rule_str: &str, value_str: &str) -> bool {
462    // 1. 词法分析
463    let tokens = match tokenize(rule_str) {
464        Ok(t) => t,
465        Err(_) => {
466            return false;
467        }
468    };
469
470    // 2. 解析完整规则 (nameless = false)
471    let mut parser = Parser::new(tokens);
472    let rule_ast = match parser.parse_field(false) {
473        Ok(r) => r,
474        Err(_) => {
475            return false;
476        }
477    };
478
479    // 3. 将输入字符串转换为对应的 Value 枚举
480    let val_enum = match convert_input_to_value(value_str, &rule_ast.field_type) {
481        Ok(v) => v,
482        Err(_) => {
483            return false;
484        }
485    };
486
487    // 4. 构造上下文对象供 Validator 查找对应字段
488    let mut map = HashMap::new();
489    map.insert(rule_ast.field.clone(), val_enum);
490    let mut wrapped_value = Value::Object(map);
491
492    // 5. 执行验证
493    validate_field(&mut wrapped_value, &rule_ast).is_ok()
494}
495
496fn convert_input_to_value(input: &str, target_type: &FieldType) -> Result<Value, String> {
497    match target_type {
498        FieldType::Int => input
499            .parse::<i64>()
500            .map(Value::Int)
501            .map_err(|e| e.to_string()),
502        FieldType::Float => input
503            .parse::<f64>()
504            .map(Value::Float)
505            .map_err(|e| e.to_string()),
506        FieldType::Bool => {
507            // 统一转为小写进行不区分大小写的匹配
508            match input.to_lowercase().as_str() {
509                "true" | "1" => Ok(Value::Bool(true)),
510                "false" | "0" => Ok(Value::Bool(false)),
511                _ => Err("Invalid boolean value".into()),
512            }
513        }
514        _ => Ok(Value::String(input.to_string())),
515    }
516}