Skip to main content

robinpath_modules/modules/
validate_mod.rs

1use regex::Regex;
2use robinpath::{RobinPath, Value};
3
4pub fn register(rp: &mut RobinPath) {
5    rp.register_builtin("validate.isEmail", |args, _| {
6        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
7        let re = Regex::new(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$").unwrap();
8        Ok(Value::Bool(re.is_match(&s)))
9    });
10
11    rp.register_builtin("validate.isUrl", |args, _| {
12        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
13        let re = Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap();
14        Ok(Value::Bool(re.is_match(&s)))
15    });
16
17    rp.register_builtin("validate.isIP", |args, _| {
18        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
19        // IPv4
20        let ipv4 = Regex::new(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$").unwrap();
21        if let Some(caps) = ipv4.captures(&s) {
22            let valid = (1..=4).all(|i| {
23                caps.get(i)
24                    .and_then(|m| m.as_str().parse::<u32>().ok())
25                    .is_some_and(|n| n <= 255)
26            });
27            if valid {
28                return Ok(Value::Bool(true));
29            }
30        }
31        // Simple IPv6 check
32        let ipv6 = Regex::new(r"^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$").unwrap();
33        Ok(Value::Bool(ipv6.is_match(&s)))
34    });
35
36    rp.register_builtin("validate.isUUID", |args, _| {
37        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
38        let re = Regex::new(
39            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}$",
40        )
41        .unwrap();
42        Ok(Value::Bool(re.is_match(&s)))
43    });
44
45    rp.register_builtin("validate.isDate", |args, _| {
46        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
47        // Accept YYYY-MM-DD, MM/DD/YYYY, and ISO 8601
48        let iso = Regex::new(r"^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?").unwrap();
49        let us = Regex::new(r"^\d{1,2}/\d{1,2}/\d{2,4}$").unwrap();
50        Ok(Value::Bool(iso.is_match(&s) || us.is_match(&s)))
51    });
52
53    rp.register_builtin("validate.isNumeric", |args, _| {
54        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
55        let re = Regex::new(r"^-?\d+(\.\d+)?$").unwrap();
56        Ok(Value::Bool(re.is_match(&s)))
57    });
58
59    rp.register_builtin("validate.isAlpha", |args, _| {
60        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
61        Ok(Value::Bool(!s.is_empty() && s.chars().all(|c| c.is_alphabetic())))
62    });
63
64    rp.register_builtin("validate.isAlphanumeric", |args, _| {
65        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
66        Ok(Value::Bool(!s.is_empty() && s.chars().all(|c| c.is_alphanumeric())))
67    });
68
69    rp.register_builtin("validate.minLength", |args, _| {
70        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
71        let min = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0);
72        Ok(Value::Bool(s.len() >= min))
73    });
74
75    rp.register_builtin("validate.maxLength", |args, _| {
76        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
77        let max = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0);
78        Ok(Value::Bool(s.len() <= max))
79    });
80
81    rp.register_builtin("validate.inRange", |args, _| {
82        let n = args.first().map(|v| v.to_number()).unwrap_or(0.0);
83        let min = args.get(1).map(|v| v.to_number()).unwrap_or(f64::NEG_INFINITY);
84        let max = args.get(2).map(|v| v.to_number()).unwrap_or(f64::INFINITY);
85        Ok(Value::Bool(n >= min && n <= max))
86    });
87
88    rp.register_builtin("validate.isEmpty", |args, _| {
89        let val = args.first().cloned().unwrap_or(Value::Null);
90        let empty = match &val {
91            Value::Null | Value::Undefined => true,
92            Value::String(s) => s.trim().is_empty(),
93            Value::Array(a) => a.is_empty(),
94            Value::Object(o) => o.is_empty(),
95            _ => false,
96        };
97        Ok(Value::Bool(empty))
98    });
99
100    rp.register_builtin("validate.isJSON", |args, _| {
101        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
102        Ok(Value::Bool(serde_json::from_str::<serde_json::Value>(&s).is_ok()))
103    });
104}