Skip to main content

robinpath_modules/modules/
phone_mod.rs

1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4    rp.register_builtin("phone.parse", |args, _| {
5        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
6        let digits = normalize_phone(&raw);
7        let mut obj = indexmap::IndexMap::new();
8        obj.insert("raw".to_string(), Value::String(raw));
9        obj.insert("digits".to_string(), Value::String(digits.clone()));
10        obj.insert("isValid".to_string(), Value::Bool(digits.len() >= 7 && digits.len() <= 15));
11        Ok(Value::Object(obj))
12    });
13
14    rp.register_builtin("phone.format", |args, _| {
15        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
16        let digits = normalize_phone(&raw);
17        if digits.len() == 10 {
18            Ok(Value::String(format!(
19                "({}) {}-{}",
20                &digits[0..3],
21                &digits[3..6],
22                &digits[6..10]
23            )))
24        } else if digits.len() == 11 && digits.starts_with('1') {
25            Ok(Value::String(format!(
26                "+1 ({}) {}-{}",
27                &digits[1..4],
28                &digits[4..7],
29                &digits[7..11]
30            )))
31        } else {
32            Ok(Value::String(raw))
33        }
34    });
35
36    rp.register_builtin("phone.formatE164", |args, _| {
37        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
38        let country = args.get(1).map(|v| v.to_display_string()).unwrap_or_else(|| "US".to_string());
39        let digits = normalize_phone(&raw);
40        let dial_code = get_dial_code(&country);
41        if digits.starts_with(&dial_code) {
42            Ok(Value::String(format!("+{}", digits)))
43        } else {
44            Ok(Value::String(format!("+{}{}", dial_code, digits)))
45        }
46    });
47
48    rp.register_builtin("phone.formatInternational", |args, _| {
49        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
50        let country = args.get(1).map(|v| v.to_display_string()).unwrap_or_else(|| "US".to_string());
51        let digits = normalize_phone(&raw);
52        let dial_code = get_dial_code(&country);
53        let national = if digits.starts_with(&dial_code) {
54            &digits[dial_code.len()..]
55        } else {
56            &digits
57        };
58        Ok(Value::String(format!("+{} {}", dial_code, national)))
59    });
60
61    rp.register_builtin("phone.validate", |args, _| {
62        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
63        let digits = normalize_phone(&raw);
64        Ok(Value::Bool(digits.len() >= 7 && digits.len() <= 15))
65    });
66
67    rp.register_builtin("phone.getCountry", |args, _| {
68        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
69        let digits = normalize_phone(&raw);
70        for entry in COUNTRY_CODES {
71            if digits.starts_with(entry.dial_code) {
72                return Ok(Value::String(entry.code.to_string()));
73            }
74        }
75        Ok(Value::Null)
76    });
77
78    rp.register_builtin("phone.getType", |args, _| {
79        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
80        let digits = normalize_phone(&raw);
81        // Simple heuristic
82        let phone_type = if digits.len() == 10 || digits.len() == 11 {
83            "mobile"
84        } else {
85            "unknown"
86        };
87        Ok(Value::String(phone_type.to_string()))
88    });
89
90    rp.register_builtin("phone.normalize", |args, _| {
91        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
92        Ok(Value::String(normalize_phone(&raw)))
93    });
94
95    rp.register_builtin("phone.mask", |args, _| {
96        let raw = args.first().map(|v| v.to_display_string()).unwrap_or_default();
97        let digits = normalize_phone(&raw);
98        if digits.len() >= 4 {
99            let visible = &digits[digits.len() - 4..];
100            let masked = "*".repeat(digits.len() - 4);
101            Ok(Value::String(format!("{}{}", masked, visible)))
102        } else {
103            Ok(Value::String("****".to_string()))
104        }
105    });
106
107    rp.register_builtin("phone.dialCode", |args, _| {
108        let country = args.first().map(|v| v.to_display_string()).unwrap_or_else(|| "US".to_string());
109        Ok(Value::String(format!("+{}", get_dial_code(&country))))
110    });
111
112    rp.register_builtin("phone.countryInfo", |args, _| {
113        let code = args.first().map(|v| v.to_display_string()).unwrap_or_else(|| "US".to_string());
114        let upper = code.to_uppercase();
115        if let Some(entry) = COUNTRY_CODES.iter().find(|c| c.code == upper) {
116            let mut obj = indexmap::IndexMap::new();
117            obj.insert("code".to_string(), Value::String(entry.code.to_string()));
118            obj.insert("name".to_string(), Value::String(entry.name.to_string()));
119            obj.insert("dialCode".to_string(), Value::String(format!("+{}", entry.dial_code)));
120            Ok(Value::Object(obj))
121        } else {
122            Ok(Value::Null)
123        }
124    });
125
126    rp.register_builtin("phone.listCountries", |_args, _| {
127        let countries: Vec<Value> = COUNTRY_CODES
128            .iter()
129            .map(|c| Value::String(c.code.to_string()))
130            .collect();
131        Ok(Value::Array(countries))
132    });
133
134    rp.register_builtin("phone.compare", |args, _| {
135        let a = args.first().map(|v| v.to_display_string()).unwrap_or_default();
136        let b = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
137        Ok(Value::Bool(normalize_phone(&a) == normalize_phone(&b)))
138    });
139}
140
141fn normalize_phone(raw: &str) -> String {
142    raw.chars().filter(|c| c.is_ascii_digit()).collect()
143}
144
145fn get_dial_code(country: &str) -> String {
146    let upper = country.to_uppercase();
147    COUNTRY_CODES
148        .iter()
149        .find(|c| c.code == upper)
150        .map(|c| c.dial_code.to_string())
151        .unwrap_or_else(|| "1".to_string())
152}
153
154struct CountryEntry {
155    code: &'static str,
156    name: &'static str,
157    dial_code: &'static str,
158}
159
160const COUNTRY_CODES: &[CountryEntry] = &[
161    CountryEntry { code: "US", name: "United States", dial_code: "1" },
162    CountryEntry { code: "CA", name: "Canada", dial_code: "1" },
163    CountryEntry { code: "GB", name: "United Kingdom", dial_code: "44" },
164    CountryEntry { code: "DE", name: "Germany", dial_code: "49" },
165    CountryEntry { code: "FR", name: "France", dial_code: "33" },
166    CountryEntry { code: "IT", name: "Italy", dial_code: "39" },
167    CountryEntry { code: "ES", name: "Spain", dial_code: "34" },
168    CountryEntry { code: "JP", name: "Japan", dial_code: "81" },
169    CountryEntry { code: "CN", name: "China", dial_code: "86" },
170    CountryEntry { code: "KR", name: "South Korea", dial_code: "82" },
171    CountryEntry { code: "IN", name: "India", dial_code: "91" },
172    CountryEntry { code: "AU", name: "Australia", dial_code: "61" },
173    CountryEntry { code: "NZ", name: "New Zealand", dial_code: "64" },
174    CountryEntry { code: "BR", name: "Brazil", dial_code: "55" },
175    CountryEntry { code: "MX", name: "Mexico", dial_code: "52" },
176    CountryEntry { code: "RU", name: "Russia", dial_code: "7" },
177    CountryEntry { code: "SE", name: "Sweden", dial_code: "46" },
178    CountryEntry { code: "NO", name: "Norway", dial_code: "47" },
179    CountryEntry { code: "DK", name: "Denmark", dial_code: "45" },
180    CountryEntry { code: "FI", name: "Finland", dial_code: "358" },
181    CountryEntry { code: "NL", name: "Netherlands", dial_code: "31" },
182    CountryEntry { code: "CH", name: "Switzerland", dial_code: "41" },
183    CountryEntry { code: "PL", name: "Poland", dial_code: "48" },
184    CountryEntry { code: "TR", name: "Turkey", dial_code: "90" },
185    CountryEntry { code: "ZA", name: "South Africa", dial_code: "27" },
186    CountryEntry { code: "SG", name: "Singapore", dial_code: "65" },
187    CountryEntry { code: "HK", name: "Hong Kong", dial_code: "852" },
188    CountryEntry { code: "TH", name: "Thailand", dial_code: "66" },
189    CountryEntry { code: "AR", name: "Argentina", dial_code: "54" },
190    CountryEntry { code: "EG", name: "Egypt", dial_code: "20" },
191];