1use std::collections::HashSet;
4
5use regex::Regex;
6use serde_json::Value;
7
8use crate::functions::Function;
9use crate::interpreter::SearchResult;
10use crate::registry::register_if_enabled;
11use crate::{Context, Runtime, arg, defn};
12
13pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
15 register_if_enabled(runtime, "is_email", enabled, Box::new(IsEmailFn::new()));
16 register_if_enabled(runtime, "is_url", enabled, Box::new(IsUrlFn::new()));
17 register_if_enabled(runtime, "is_uuid", enabled, Box::new(IsUuidFn::new()));
18 register_if_enabled(runtime, "is_phone", enabled, Box::new(IsPhoneFn::new()));
19 register_if_enabled(runtime, "is_ipv4", enabled, Box::new(IsIpv4Fn::new()));
20 register_if_enabled(runtime, "is_ipv6", enabled, Box::new(IsIpv6Fn::new()));
21 register_if_enabled(runtime, "luhn_check", enabled, Box::new(LuhnCheckFn::new()));
22 register_if_enabled(
23 runtime,
24 "is_credit_card",
25 enabled,
26 Box::new(IsCreditCardFn::new()),
27 );
28 register_if_enabled(runtime, "is_jwt", enabled, Box::new(IsJwtFn::new()));
29 register_if_enabled(
30 runtime,
31 "is_iso_date",
32 enabled,
33 Box::new(IsIsoDateFn::new()),
34 );
35 register_if_enabled(runtime, "is_json", enabled, Box::new(IsJsonFn::new()));
36 register_if_enabled(runtime, "is_base64", enabled, Box::new(IsBase64Fn::new()));
37 register_if_enabled(runtime, "is_hex", enabled, Box::new(IsHexFn::new()));
38}
39
40defn!(IsEmailFn, vec![arg!(string)], None);
45
46impl Function for IsEmailFn {
47 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
48 self.signature.validate(args, ctx)?;
49
50 let s = args[0].as_str().unwrap();
51
52 let email_re = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
53 Ok(Value::Bool(email_re.is_match(s)))
54 }
55}
56
57defn!(IsUrlFn, vec![arg!(string)], None);
62
63impl Function for IsUrlFn {
64 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
65 self.signature.validate(args, ctx)?;
66
67 let s = args[0].as_str().unwrap();
68
69 let url_re = Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap();
70 Ok(Value::Bool(url_re.is_match(s)))
71 }
72}
73
74defn!(IsUuidFn, vec![arg!(string)], None);
79
80impl Function for IsUuidFn {
81 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
82 self.signature.validate(args, ctx)?;
83
84 let s = args[0].as_str().unwrap();
85
86 let uuid_re = Regex::new(
87 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}$",
88 )
89 .unwrap();
90 Ok(Value::Bool(uuid_re.is_match(s)))
91 }
92}
93
94defn!(IsIpv4Fn, vec![arg!(string)], None);
99
100impl Function for IsIpv4Fn {
101 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
102 self.signature.validate(args, ctx)?;
103
104 let s = args[0].as_str().unwrap();
105
106 let is_valid = s.parse::<std::net::Ipv4Addr>().is_ok();
107 Ok(Value::Bool(is_valid))
108 }
109}
110
111defn!(IsIpv6Fn, vec![arg!(string)], None);
116
117impl Function for IsIpv6Fn {
118 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
119 self.signature.validate(args, ctx)?;
120
121 let s = args[0].as_str().unwrap();
122
123 let is_valid = s.parse::<std::net::Ipv6Addr>().is_ok();
124 Ok(Value::Bool(is_valid))
125 }
126}
127
128defn!(LuhnCheckFn, vec![arg!(string)], None);
133
134impl Function for LuhnCheckFn {
135 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
136 self.signature.validate(args, ctx)?;
137
138 let s = args[0].as_str().unwrap();
139
140 Ok(Value::Bool(luhn_validate(s)))
141 }
142}
143
144fn luhn_validate(s: &str) -> bool {
145 let digits: String = s.chars().filter(|c| c.is_ascii_digit()).collect();
147
148 if digits.is_empty() {
149 return false;
150 }
151
152 let mut sum = 0;
153 let mut double = false;
154
155 for c in digits.chars().rev() {
156 if let Some(digit) = c.to_digit(10) {
157 let mut d = digit;
158 if double {
159 d *= 2;
160 if d > 9 {
161 d -= 9;
162 }
163 }
164 sum += d;
165 double = !double;
166 } else {
167 return false;
168 }
169 }
170
171 sum % 10 == 0
172}
173
174defn!(IsCreditCardFn, vec![arg!(string)], None);
179
180impl Function for IsCreditCardFn {
181 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
182 self.signature.validate(args, ctx)?;
183
184 let s = args[0].as_str().unwrap();
185
186 let digits: String = s.chars().filter(|c| c.is_ascii_digit()).collect();
188
189 if digits.len() < 13 || digits.len() > 19 {
191 return Ok(Value::Bool(false));
192 }
193
194 Ok(Value::Bool(luhn_validate(&digits)))
196 }
197}
198
199defn!(IsPhoneFn, vec![arg!(string)], None);
204
205impl Function for IsPhoneFn {
206 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
207 self.signature.validate(args, ctx)?;
208
209 let s = args[0].as_str().unwrap();
210
211 let phone_re = Regex::new(r"^\+?[\d\s\-\(\)\.]{7,}$").unwrap();
214 if !phone_re.is_match(s) {
215 return Ok(Value::Bool(false));
216 }
217
218 let digit_count = s.chars().filter(|c| c.is_ascii_digit()).count();
220 Ok(Value::Bool((7..=15).contains(&digit_count)))
221 }
222}
223
224defn!(IsJwtFn, vec![arg!(string)], None);
229
230impl Function for IsJwtFn {
231 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
232 self.signature.validate(args, ctx)?;
233
234 let s = args[0].as_str().unwrap();
235
236 let parts: Vec<&str> = s.split('.').collect();
238 if parts.len() != 3 {
239 return Ok(Value::Bool(false));
240 }
241
242 let is_valid = parts.iter().all(|part| {
244 !part.is_empty()
245 && part
246 .chars()
247 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '=')
248 });
249
250 Ok(Value::Bool(is_valid))
251 }
252}
253
254defn!(IsIsoDateFn, vec![arg!(string)], None);
259
260impl Function for IsIsoDateFn {
261 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
262 self.signature.validate(args, ctx)?;
263
264 let s = args[0].as_str().unwrap();
265
266 if chrono::DateTime::parse_from_rfc3339(s).is_ok() {
268 return Ok(Value::Bool(true));
269 }
270
271 if chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").is_ok() {
273 return Ok(Value::Bool(true));
274 }
275
276 if chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").is_ok() {
278 return Ok(Value::Bool(true));
279 }
280
281 Ok(Value::Bool(false))
282 }
283}
284
285defn!(IsJsonFn, vec![arg!(string)], None);
290
291impl Function for IsJsonFn {
292 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
293 self.signature.validate(args, ctx)?;
294
295 let s = args[0].as_str().unwrap();
296
297 let is_valid = serde_json::from_str::<serde_json::Value>(s).is_ok();
298 Ok(Value::Bool(is_valid))
299 }
300}
301
302defn!(IsBase64Fn, vec![arg!(string)], None);
307
308impl Function for IsBase64Fn {
309 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
310 self.signature.validate(args, ctx)?;
311
312 let s = args[0].as_str().unwrap();
313
314 use base64::{Engine, engine::general_purpose::STANDARD};
315 let is_valid = STANDARD.decode(s).is_ok();
316 Ok(Value::Bool(is_valid))
317 }
318}
319
320defn!(IsHexFn, vec![arg!(string)], None);
325
326impl Function for IsHexFn {
327 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
328 self.signature.validate(args, ctx)?;
329
330 let s = args[0].as_str().unwrap();
331
332 let is_valid = !s.is_empty() && s.chars().all(|c| c.is_ascii_hexdigit());
334 Ok(Value::Bool(is_valid))
335 }
336}