robinpath_modules/modules/
validate_mod.rs1use 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 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 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 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}