Skip to main content

robinpath_modules/modules/
email_mod.rs

1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4    // email.isValid email → bool
5    rp.register_builtin("email.isValid", |args, _| {
6        let email = args.first().map(|v| v.to_display_string()).unwrap_or_default();
7        let re = regex::Regex::new(r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$").unwrap();
8        Ok(Value::Bool(re.is_match(&email)))
9    });
10
11    // email.parseAddress "Name <email>" → {name, address}
12    rp.register_builtin("email.parseAddress", |args, _| {
13        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
14        let mut obj = indexmap::IndexMap::new();
15        if let Some(lt) = input.find('<') {
16            if let Some(gt) = input.find('>') {
17                let name = input[..lt].trim().trim_matches('"').to_string();
18                let address = input[lt + 1..gt].trim().to_string();
19                obj.insert("name".to_string(), Value::String(name));
20                obj.insert("address".to_string(), Value::String(address));
21            }
22        } else {
23            obj.insert("name".to_string(), Value::String(String::new()));
24            obj.insert("address".to_string(), Value::String(input.trim().to_string()));
25        }
26        Ok(Value::Object(obj))
27    });
28
29    // email.parseAddressList "a@b.c, d@e.f" → array of {name, address}
30    rp.register_builtin("email.parseAddressList", |args, _| {
31        let input = args.first().map(|v| v.to_display_string()).unwrap_or_default();
32        let addresses: Vec<Value> = input.split(',').map(|part| {
33            let part = part.trim();
34            let mut obj = indexmap::IndexMap::new();
35            if let Some(lt) = part.find('<') {
36                if let Some(gt) = part.find('>') {
37                    obj.insert("name".to_string(), Value::String(part[..lt].trim().trim_matches('"').to_string()));
38                    obj.insert("address".to_string(), Value::String(part[lt + 1..gt].trim().to_string()));
39                }
40            } else {
41                obj.insert("name".to_string(), Value::String(String::new()));
42                obj.insert("address".to_string(), Value::String(part.to_string()));
43            }
44            Value::Object(obj)
45        }).collect();
46        Ok(Value::Array(addresses))
47    });
48
49    // email.extractDomain email → domain string
50    rp.register_builtin("email.extractDomain", |args, _| {
51        let email = args.first().map(|v| v.to_display_string()).unwrap_or_default();
52        let domain = email.split('@').last().unwrap_or("").to_string();
53        Ok(Value::String(domain))
54    });
55
56    // email.buildAddress name email → "Name <email>"
57    rp.register_builtin("email.buildAddress", |args, _| {
58        let name = args.first().map(|v| v.to_display_string()).unwrap_or_default();
59        let email = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
60        if name.is_empty() {
61            Ok(Value::String(email))
62        } else {
63            Ok(Value::String(format!("{} <{}>", name, email)))
64        }
65    });
66
67    // email.normalize email → lowercase trimmed email
68    rp.register_builtin("email.normalize", |args, _| {
69        let email = args.first().map(|v| v.to_display_string()).unwrap_or_default();
70        Ok(Value::String(email.trim().to_lowercase()))
71    });
72
73    // email.mask email → masked email
74    rp.register_builtin("email.mask", |args, _| {
75        let email = args.first().map(|v| v.to_display_string()).unwrap_or_default();
76        if let Some(at) = email.find('@') {
77            let local = &email[..at];
78            let domain = &email[at..];
79            let masked = if local.len() <= 2 {
80                format!("{}***{}", &local[..1], domain)
81            } else {
82                format!("{}***{}{}", &local[..1], &local[local.len() - 1..], domain)
83            };
84            Ok(Value::String(masked))
85        } else {
86            Ok(Value::String(email))
87        }
88    });
89
90    // email.send transport options → (requires reqwest, simplified SMTP-over-HTTP for testing)
91    // For real SMTP we'd need lettre, but keep this lightweight
92    // email.buildMime to subject body from? → MIME formatted string
93    rp.register_builtin("email.buildMime", |args, _| {
94        let to = args.first().map(|v| v.to_display_string()).unwrap_or_default();
95        let subject = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
96        let body = args.get(2).map(|v| v.to_display_string()).unwrap_or_default();
97        let from = args.get(3).map(|v| v.to_display_string()).unwrap_or_else(|| "noreply@example.com".to_string());
98        let mime = format!(
99            "From: {}\r\nTo: {}\r\nSubject: {}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n{}",
100            from, to, subject, body
101        );
102        Ok(Value::String(mime))
103    });
104
105    // email.buildHtmlMime to subject htmlBody from? → MIME formatted HTML email
106    rp.register_builtin("email.buildHtmlMime", |args, _| {
107        let to = args.first().map(|v| v.to_display_string()).unwrap_or_default();
108        let subject = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
109        let body = args.get(2).map(|v| v.to_display_string()).unwrap_or_default();
110        let from = args.get(3).map(|v| v.to_display_string()).unwrap_or_else(|| "noreply@example.com".to_string());
111        let mime = format!(
112            "From: {}\r\nTo: {}\r\nSubject: {}\r\nContent-Type: text/html; charset=utf-8\r\n\r\n{}",
113            from, to, subject, body
114        );
115        Ok(Value::String(mime))
116    });
117}