1use regex::Regex;
7use std::collections::HashMap;
8use std::sync::OnceLock;
9
10static PHONE_PATTERNS: OnceLock<HashMap<&'static str, Regex>> = OnceLock::new();
11
12fn get_phone_patterns() -> &'static HashMap<&'static str, Regex> {
13 PHONE_PATTERNS.get_or_init(|| {
14 let mut map = HashMap::new();
15
16 macro_rules! add_pattern {
18 ($locale:expr, $pattern:expr) => {
19 map.insert($locale, Regex::new($pattern).expect("Invalid regex pattern"));
20 };
21 }
22
23 add_pattern!("am-AM", r"^(\+?374|0)(33|4[134]|55|77|88|9[13-689])\d{6}$");
24 add_pattern!("ar-AE", r"^((\+?971)|0)?5[024568]\d{7}$");
25 add_pattern!("ar-BH", r"^(\+?973)?(3|6)\d{7}$");
26 add_pattern!("ar-DZ", r"^(\+?213|0)(5|6|7)\d{8}$");
27 add_pattern!("ar-LB", r"^(\+?961)?((3|81)\d{6}|7\d{7})$");
28 add_pattern!("ar-EG", r"^((\+?20)|0)?1[0125]\d{8}$");
29 add_pattern!("ar-IQ", r"^(\+?964|0)?7[0-9]\d{8}$");
30 add_pattern!("ar-JO", r"^(\+?962|0)?7[789]\d{7}$");
31 add_pattern!("ar-KW", r"^(\+?965)([569]\d{7}|41\d{6})$");
32 add_pattern!("ar-LY", r"^((\+?218)|0)?(9[1-6]\d{7}|[1-8]\d{7,9})$");
33 add_pattern!("ar-MA", r"^(?:(?:\+|00)212|0)[5-7]\d{8}$");
34 add_pattern!("ar-OM", r"^((\+|00)968)?([79][1-9])\d{6}$");
35 add_pattern!("ar-PS", r"^(\+?970|0)5[6|9](\d{7})$");
36 add_pattern!("ar-SA", r"^(!?(\+?966)|0)?5\d{8}$");
37 add_pattern!("ar-SD", r"^((\+?249)|0)?(9[012369]|1[012])\d{7}$");
38 add_pattern!("ar-SY", r"^(!?(\+?963)|0)?9\d{8}$");
39 add_pattern!("ar-TN", r"^(\+?216)?[2459]\d{7}$");
40 add_pattern!("az-AZ", r"^(\+994|0)(10|5[015]|7[07]|99)\d{7}$");
41 add_pattern!("ar-QA", r"^(\+?974|0)?([3567]\d{7})$");
42 add_pattern!("ar-YE", r"^(((\+|00)9677|0?7)[0137]\d{7}|((\+|00)967|0)[1-7]\d{6})$");
43 add_pattern!("ar-EH", r"^(\+?212|0)[\s\-]?(5288|5289)[\s\-]?\d{5}$");
44 add_pattern!("bs-BA", r"^((((\+|00)3876)|06))((([0-3]|[5-6])\d{6})|(4\d{7}))$");
45 add_pattern!("be-BY", r"^(\+?375)?(24|25|29|33|44)\d{7}$");
46 add_pattern!("bg-BG", r"^(\+?359|0)?8[789]\d{7}$");
47 add_pattern!("bn-BD", r"^(\+?880|0)1[13456789][0-9]{8}$");
48 add_pattern!("ca-AD", r"^(\+376)?[346]\d{5}$");
49 add_pattern!("cs-CZ", r"^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$");
50 add_pattern!("da-DK", r"^(\+?45)?\s?\d{2}\s?\d{2}\s?\d{2}\s?\d{2}$");
51 add_pattern!("de-DE", r"^((\+49|0)1)(5[0-25-9]\d|6([23]|0\d?)|7([0-57-9]|6\d))\d{7,9}$");
52 add_pattern!("de-AT", r"^(\+43|0)\d{1,4}\d{3,12}$");
53 add_pattern!("de-CH", r"^(\+41|0)([1-9])\d{1,9}$");
54 add_pattern!("de-LU", r"^(\+352)?((6\d1)\d{6})$");
55 add_pattern!("dv-MV", r"^(\+?960)?(7[2-9]|9[1-9])\d{5}$");
56 add_pattern!("el-GR", r"^(\+?30|0)?6(8[5-9]|9[013-57-9])\d{7}$");
57 add_pattern!("el-CY", r"^(\+?357?)?(9(9|7|6|5|4)\d{6})$");
58 add_pattern!("en-AI", r"^(\+?1|0)264(?:2(35|92)|4(?:6[1-2]|76|97)|5(?:3[6-9]|8[1-4])|7(?:2(4|9)|72))\d{4}$");
59 add_pattern!("en-AU", r"^(\+?61|0)4\d{8}$");
60 add_pattern!("en-AG", r"^(?:\+1|1)268(?:464|7(?:1[3-9]|[28]\d|3[0246]|64|7[0-689]))\d{4}$");
61 add_pattern!("en-BM", r"^(\+?1)?441(((3|7)\d{6}$)|(5[0-3][0-9]\d{4}$)|(59\d{5}$))");
62 add_pattern!("en-BS", r"^(\+?1[-\s]?|0)?\(?242\)?[-\s]?\d{3}[-\s]?\d{4}$");
63 add_pattern!("en-GB", r"^(\+?44|0)7[1-9]\d{8}$");
64 add_pattern!("en-GG", r"^(\+?44|0)1481\d{6}$");
65 add_pattern!("en-GH", r"^(\+233|0)(20|50|24|54|27|57|26|56|23|53|28|55|59)\d{7}$");
66 add_pattern!("en-GY", r"^(\+592|0)6\d{6}$");
67 add_pattern!("en-HK", r"^(\+?852[-\s]?)?[456789]\d{3}[-\s]?\d{4}$");
68 add_pattern!("en-MO", r"^(\+?853[-\s]?)?[6]\d{3}[-\s]?\d{4}$");
69 add_pattern!("en-IE", r"^(\+?353|0)8[356789]\d{7}$");
70 add_pattern!("en-IN", r"^(\+?91|0)?[6789]\d{9}$");
71 add_pattern!("en-JM", r"^(\+?876)?\d{7}$");
72 add_pattern!("en-KE", r"^(\+?254|0)(7|1)\d{8}$");
73 add_pattern!("fr-CF", r"^(\+?236| ?)(70|75|77|72|21|22)\d{6}$");
74 add_pattern!("en-SS", r"^(\+?211|0)(9[1257])\d{7}$");
75 add_pattern!("en-KI", r"^((\+686|686)?)?( )?((6|7)(2|3|8)[0-9]{6})$");
76 add_pattern!("en-KN", r"^(?:\+1|1)869(?:46\d|48[89]|55[6-8]|66\d|76[02-7])\d{4}$");
77 add_pattern!("en-LS", r"^(\+?266)(22|28|57|58|59|27|52)\d{6}$");
78 add_pattern!("en-MT", r"^(\+?356|0)?(99|79|77|21|27|22|25)[0-9]{6}$");
79 add_pattern!("en-MU", r"^(\+?230|0)?\d{8}$");
80 add_pattern!("en-MW", r"^(\+?265|0)(((77|88|31|99|98|21)\d{7})|(((111)|1)\d{6})|(32000\d{4}))$");
81 add_pattern!("en-NA", r"^(\+?264|0)(6|8)\d{7}$");
82 add_pattern!("en-NG", r"^(\+?234|0)?[789]\d{9}$");
83 add_pattern!("en-NZ", r"^(\+?64|0)[28]\d{7,9}$");
84 add_pattern!("en-PG", r"^(\+?675|0)?(7\d|8[18])\d{6}$");
85 add_pattern!("en-PK", r"^((00|\+)?92|0)3[0-6]\d{8}$");
86 add_pattern!("en-PH", r"^(09|\+639)\d{9}$");
87 add_pattern!("en-RW", r"^(\+?250|0)?[7]\d{8}$");
88 add_pattern!("en-SG", r"^(\+65)?[3689]\d{7}$");
89 add_pattern!("en-SL", r"^(\+?232|0)\d{8}$");
90 add_pattern!("en-TZ", r"^(\+?255|0)?[67]\d{8}$");
91 add_pattern!("en-UG", r"^(\+?256|0)?[7]\d{8}$");
92 add_pattern!("en-US", r"^((\+1|1)?( |-)?)?(\([2-9][0-9]{2}\)|[2-9][0-9]{2})( |-)?([2-9][0-9]{2}( |-)?[0-9]{4})$");
93 add_pattern!("en-ZA", r"^(\+?27|0)\d{9}$");
94 add_pattern!("en-ZM", r"^(\+?26)?0[79][567]\d{7}$");
95 add_pattern!("en-ZW", r"^(\+263)[0-9]{9}$");
96 add_pattern!("en-BW", r"^(\+?267)?(7[1-8]{1})\d{6}$");
97 add_pattern!("es-AR", r"^\+?549(11|[2368]\d)\d{8}$");
98 add_pattern!("es-BO", r"^(\+?591)?(6|7)\d{7}$");
99 add_pattern!("es-CO", r"^(\+?57)?3(0(0|1|2|4|5)|1\d|2[0-4]|5(0|1))\d{7}$");
100 add_pattern!("es-CL", r"^(\+?56|0)[2-9]\d{1}\d{7}$");
101 add_pattern!("es-CR", r"^(\+506)?[2-8]\d{7}$");
102 add_pattern!("es-CU", r"^(\+53|0053)?5\d{7}$");
103 add_pattern!("es-DO", r"^(\+?1)?8[024]9\d{7}$");
104 add_pattern!("es-HN", r"^(\+?504)?[9|8|3|2]\d{7}$");
105 add_pattern!("es-EC", r"^(\+?593|0)([2-7]|9[2-9])\d{7}$");
106 add_pattern!("es-ES", r"^(\+?34)?[6|7]\d{8}$");
107 add_pattern!("es-GT", r"^(\+?502)?[2|6|7]\d{7}$");
108 add_pattern!("es-PE", r"^(\+?51)?9\d{8}$");
109 add_pattern!("es-MX", r"^(\+?52)?(1|01)?\d{10,11}$");
110 add_pattern!("es-NI", r"^(\+?505)\d{7,8}$");
111 add_pattern!("es-PA", r"^(\+?507)\d{7,8}$");
112 add_pattern!("es-PY", r"^(\+?595|0)9[9876]\d{7}$");
113 add_pattern!("es-SV", r"^(\+?503)?[67]\d{7}$");
114 add_pattern!("es-UY", r"^(\+598|0)9[1-9][\d]{6}$");
115 add_pattern!("es-VE", r"^(\+?58)?(2|4)\d{9}$");
116 add_pattern!("et-EE", r"^(\+?372)?\s?(5|8[1-4])\s?([0-9]\s?){6,7}$");
117 add_pattern!("fa-IR", r"^(\+?98[\-\s]?|0)9[0-39]\d[\-\s]?\d{3}[\-\s]?\d{4}$");
118 add_pattern!("fa-AF", r"^(\+93|0)?(2{1}[0-8]{1}|[3-5]{1}[0-4]{1})(\d{7})$");
119 add_pattern!("fi-FI", r"^(\+?358|0)\s?(4[0-6]|50)\s?(\d\s?){4,8}$");
120 add_pattern!("fj-FJ", r"^(\+?679)?\s?\d{3}\s?\d{4}$");
121 add_pattern!("fo-FO", r"^(\+?298)?\s?\d{2}\s?\d{2}\s?\d{2}$");
122 add_pattern!("fr-BF", r"^(\+226|0)[67]\d{7}$");
123 add_pattern!("fr-BJ", r"^(\+229)\d{8}$");
124 add_pattern!("fr-CD", r"^(\+?243|0)?(8|9)\d{8}$");
125 add_pattern!("fr-CM", r"^(\+?237)6[0-9]{8}$");
126 add_pattern!("fr-FR", r"^(\+?33|0)[67]\d{8}$");
127 add_pattern!("fr-GF", r"^(\+?594|0|00594)[67]\d{8}$");
128 add_pattern!("fr-GP", r"^(\+?590|0|00590)[67]\d{8}$");
129 add_pattern!("fr-MQ", r"^(\+?596|0|00596)[67]\d{8}$");
130 add_pattern!("fr-PF", r"^(\+?689)?8[789]\d{6}$");
131 add_pattern!("fr-RE", r"^(\+?262|0|00262)[67]\d{8}$");
132 add_pattern!("fr-WF", r"^(\+681)?\d{6}$");
133 add_pattern!("he-IL", r"^(\+972|0)([23489]|5[012345689]|77)[1-9]\d{6}$");
134 add_pattern!("hu-HU", r"^(\+?36|06)(20|30|31|50|70)\d{7}$");
135 add_pattern!("id-ID", r"^(\+?62|0)8(1[123456789]|2[1238]|3[1238]|5[12356789]|7[78]|9[56789]|8[123456789])([\s?|\d]{5,11})$");
136 add_pattern!("ir-IR", r"^(\+98|0)?9\d{9}$");
137 add_pattern!("it-IT", r"^(\+?39)?\s?3\d{2} ?\d{6,7}$");
138 add_pattern!("it-SM", r"^((\+378)|(0549)|(\+390549)|(\+3780549))?6\d{5,9}$");
139 add_pattern!("ja-JP", r"^(\+81[ \-]?(\(0\))?|0)[6789]0[ \-]?\d{4}[ \-]?\d{4}$");
140 add_pattern!("ka-GE", r"^(\+?995)?(79\d{7}|5\d{8})$");
141 add_pattern!("kk-KZ", r"^(\+?7|8)?7\d{9}$");
142 add_pattern!("kl-GL", r"^(\+?299)?\s?\d{2}\s?\d{2}\s?\d{2}$");
143 add_pattern!("ko-KR", r"^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$");
144 add_pattern!("ky-KG", r"^(\+996\s?)?(22[0-9]|50[0-9]|55[0-9]|70[0-9]|75[0-9]|77[0-9]|880|990|995|996|997|998)\s?\d{3}\s?\d{3}$");
145 add_pattern!("lt-LT", r"^(\+370|8)\d{8}$");
146 add_pattern!("lv-LV", r"^(\+?371)2\d{7}$");
147 add_pattern!("mg-MG", r"^((\+?261|0)(2|3)\d)?\d{7}$");
148 add_pattern!("mk-MK", r"^(\+?389|0)?((?:2[2-9]\d{6}|(?:3[1-4]|4[2-8])\d{6}|500\d{5}|5[2-9]\d{6}|7[0-9][2-9]\d{5}|8[1-9]\d{6}|800\d{5}|8009\d{4}))$");
149 add_pattern!("mn-MN", r"^(\+|00|011)?976(77|81|88|91|94|95|96|99)\d{6}$");
150 add_pattern!("my-MM", r"^(\+?959|09|9)(2[5-7]|3[1-2]|4[0-5]|6[6-9]|7[5-9]|9[6-9])[0-9]{7}$");
151 add_pattern!("ms-MY", r"^(\+?60|0)1(([0145](-|\s)?\d{7,8})|([236-9](-|\s)?\d{7}))$");
152 add_pattern!("mz-MZ", r"^(\+?258)?8[234567]\d{7}$");
153 add_pattern!("nb-NO", r"^(\+?47)?[49]\d{7}$");
154 add_pattern!("ne-NP", r"^(\+?977)?9[78]\d{8}$");
155 add_pattern!("nl-BE", r"^(\+?32|0)4\d{8}$");
156 add_pattern!("nl-NL", r"^(((\+|00)?31\(0\))|((\+|00)?31)|0)6{1}\d{8}$");
157 add_pattern!("nl-AW", r"^(\+)?297(56|59|64|73|74|99)\d{5}$");
158 add_pattern!("nn-NO", r"^(\+?47)?[49]\d{7}$");
159 add_pattern!("pl-PL", r"^(\+?48)? ?([5-8]\d|45) ?\d{3} ?\d{2} ?\d{2}$");
160 add_pattern!("pt-BR", r"^((\+?55\ ?[1-9]{2}\ ?)|(\+?55\ ?\([1-9]{2}\)\ ?)|(0[1-9]{2}\ ?)|(\([1-9]{2}\)\ ?)|([1-9]{2}\ ?))((\d{4}\-?\d{4})|(9[1-9]{1}\d{3}\-?\d{4}))$");
161 add_pattern!("pt-PT", r"^(\+?351)?9[1236]\d{7}$");
162 add_pattern!("pt-AO", r"^(\+?244)?9\d{8}$");
163 add_pattern!("ro-MD", r"^(\+?373|0)((6(0|1|2|6|7|8|9))|(7(6|7|8|9)))\d{6}$");
164 add_pattern!("ro-RO", r"^(\+?40|0)\s?7\d{2}(\/|\s|\.|-)?\d{3}(\s|\.|-)?\d{3}$");
165 add_pattern!("ru-RU", r"^(\+?7|8)?9\d{9}$");
166 add_pattern!("si-LK", r"^(?:0|94|\+94)?(7(0|1|2|4|5|6|7|8)( |-)?)\d{7}$");
167 add_pattern!("sl-SI", r"^(\+386\s?|0)(\d{1}\s?\d{3}\s?\d{2}\s?\d{2}|\d{2}\s?\d{3}\s?\d{3})$");
168 add_pattern!("sk-SK", r"^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$");
169 add_pattern!("so-SO", r"^(\+?252|0)((6[0-9])\d{7}|(7[1-9])\d{7})$");
170 add_pattern!("sq-AL", r"^(\+355|0)6[2-9]\d{7}$");
171 add_pattern!("sr-RS", r"^(\+3816|06)[- \d]{5,9}$");
172 add_pattern!("sv-SE", r"^(\+?46|0)[\s\-]?7[\s\-]?[02369]([\s\-]?\d){7}$");
173 add_pattern!("tg-TJ", r"^(\+?992)?[5][5]\d{7}$");
174 add_pattern!("th-TH", r"^(\+66|66|0)\d{9}$");
175 add_pattern!("tr-TR", r"^(\+?90|0)?5\d{9}$");
176 add_pattern!("tk-TM", r"^(\+993|993|8)\d{8}$");
177 add_pattern!("uk-UA", r"^(\+?38)?0(50|6[36-8]|7[357]|9[1-9])\d{7}$");
178 add_pattern!("uz-UZ", r"^(\+?998)?(6[125-79]|7[1-69]|88|9\d)\d{7}$");
179 add_pattern!("vi-VN", r"^((\+?84)|0)((3([2-9]))|(5([25689]))|(7([0|6-9]))|(8([1-9]))|(9([0-9])))([0-9]{7})$");
180 add_pattern!("zh-CN", r"^((\+|00)86)?(1[3-9]|9[28])\d{9}$");
181 add_pattern!("zh-TW", r"^(\+?886\-?|0)?9\d{8}$");
182 add_pattern!("dz-BT", r"^(\+?975|0)?(17|16|77|02)\d{6}$");
183
184 let en_us = map.get("en-US").expect("en-US pattern must exist").clone();
186 map.insert("en-CA", en_us.clone());
187 map.insert("fr-CA", en_us);
188
189 let nl_be = map.get("nl-BE").expect("nl-BE pattern must exist").clone();
190 map.insert("fr-BE", nl_be);
191
192 let en_hk = map.get("en-HK").expect("en-HK pattern must exist").clone();
193 map.insert("zh-HK", en_hk);
194
195 let en_mo = map.get("en-MO").expect("en-MO pattern must exist").clone();
196 map.insert("zh-MO", en_mo);
197
198 let en_ie = map.get("en-IE").expect("en-IE pattern must exist").clone();
199 map.insert("ga-IE", en_ie);
200
201 let de_ch = map.get("de-CH").expect("de-CH pattern must exist").clone();
202 map.insert("fr-CH", de_ch.clone());
203 map.insert("it-CH", de_ch);
204
205 map
206 })
207}
208
209#[derive(Debug, Clone, Default)]
211pub struct MobileOptions {
212 pub strict_mode: bool,
214}
215
216#[derive(Debug, Clone)]
218pub enum Locale {
219 Specific(String),
221 Multiple(Vec<String>),
223 Any,
225}
226
227impl From<&str> for Locale {
228 fn from(s: &str) -> Self {
229 if s.is_empty() || s == "any" {
230 Locale::Any
231 } else {
232 Locale::Specific(s.to_string())
233 }
234 }
235}
236
237impl From<String> for Locale {
238 fn from(s: String) -> Self {
239 if s.is_empty() || s == "any" {
240 Locale::Any
241 } else {
242 Locale::Specific(s)
243 }
244 }
245}
246
247impl From<Vec<String>> for Locale {
248 fn from(v: Vec<String>) -> Self {
249 if v.is_empty() {
250 Locale::Any
251 } else {
252 Locale::Multiple(v)
253 }
254 }
255}
256
257impl From<Vec<&str>> for Locale {
258 fn from(v: Vec<&str>) -> Self {
259 if v.is_empty() {
260 Locale::Any
261 } else {
262 Locale::Multiple(v.iter().map(|s| s.to_string()).collect())
263 }
264 }
265}
266
267pub fn is_mobile_phone(
285 phone: &str,
286 locale: Locale,
287 options: Option<MobileOptions>,
288) -> Result<bool, String> {
289 if phone.is_empty() {
290 return Ok(false);
291 }
292
293 let opts = options.unwrap_or_default();
294
295 if opts.strict_mode && !phone.starts_with('+') {
297 return Ok(false);
298 }
299
300 let patterns = get_phone_patterns();
301
302 match locale {
303 Locale::Specific(ref loc) => {
304 if let Some(pattern) = patterns.get(loc.as_str()) {
305 Ok(pattern.is_match(phone))
306 } else {
307 Err(format!("Invalid locale '{}'", loc))
308 }
309 }
310 Locale::Multiple(ref locales) => {
311 for loc in locales {
312 if let Some(pattern) = patterns.get(loc.as_str()) {
313 if pattern.is_match(phone) {
314 return Ok(true);
315 }
316 }
317 }
318 Ok(false)
319 }
320 Locale::Any => {
321 for pattern in patterns.values() {
322 if pattern.is_match(phone) {
323 return Ok(true);
324 }
325 }
326 Ok(false)
327 }
328 }
329}
330
331pub fn is_valid_phone(phone: &str) -> bool {
343 is_mobile_phone(phone, Locale::Any, None).unwrap_or(false)
344}
345
346pub fn get_supported_locales() -> Vec<&'static str> {
348 let mut locales: Vec<&str> = get_phone_patterns().keys().copied().collect();
349 locales.sort_unstable();
350 locales
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_us_phones() {
359 let locale = Locale::from("en-US");
360 assert!(is_mobile_phone("+14155552671", locale.clone(), None).unwrap());
362 assert!(is_mobile_phone("4155552671", locale.clone(), None).unwrap());
363 assert!(is_mobile_phone("415 555 2671", locale.clone(), None).unwrap());
364 assert!(is_mobile_phone("(415) 555-2671", locale.clone(), None).unwrap());
365 assert!(!is_mobile_phone("123", locale, None).unwrap());
366 }
367
368 #[test]
369 fn test_uk_phones() {
370 let locale = Locale::from("en-GB");
371 assert!(is_mobile_phone("+447911123456", locale.clone(), None).unwrap());
372 assert!(is_mobile_phone("07911123456", locale.clone(), None).unwrap());
373 assert!(!is_mobile_phone("0123456789", locale, None).unwrap());
374 }
375
376 #[test]
377 fn test_strict_mode() {
378 let locale = Locale::from("en-US");
379 let options = Some(MobileOptions { strict_mode: true });
380
381 assert!(is_mobile_phone("+14155552671", locale.clone(), options.clone()).unwrap());
382 assert!(!is_mobile_phone("4155552671", locale, options).unwrap());
383 }
384
385 #[test]
386 fn test_multiple_locales() {
387 let locale = Locale::from(vec!["en-US", "en-GB"]);
388
389 assert!(is_mobile_phone("+14155552671", locale.clone(), None).unwrap());
390 assert!(is_mobile_phone("+447911123456", locale.clone(), None).unwrap());
391 assert!(!is_mobile_phone("+331234567", locale, None).unwrap()); }
393
394 #[test]
395 fn test_any_locale() {
396 assert!(is_mobile_phone("+14155552671", Locale::Any, None).unwrap()); assert!(is_mobile_phone("+447911123456", Locale::Any, None).unwrap()); assert!(is_mobile_phone("+33612345678", Locale::Any, None).unwrap()); assert!(is_mobile_phone("+81 90 1234 5678", Locale::Any, None).unwrap()); assert!(!is_mobile_phone("abc123", Locale::Any, None).unwrap());
401 }
402
403 #[test]
404 fn test_invalid_locale() {
405 let result = is_mobile_phone("+14155552671", Locale::from("xx-XX"), None);
406 assert!(result.is_err());
407 assert!(result.unwrap_err().contains("Invalid locale"));
408 }
409
410 #[test]
411 fn test_convenience_function() {
412 assert!(is_valid_phone("+14155552671"));
413 assert!(is_valid_phone("+447911123456"));
414 assert!(!is_valid_phone("abc123"));
415 assert!(!is_valid_phone(""));
416 }
417
418 #[test]
419 fn test_various_countries() {
420 assert!(is_mobile_phone("+33612345678", Locale::from("fr-FR"), None).unwrap());
422
423 assert!(is_mobile_phone("+4915123456789", Locale::from("de-DE"), None).unwrap());
425
426 assert!(is_mobile_phone("+919876543210", Locale::from("en-IN"), None).unwrap());
428
429 assert!(is_mobile_phone("+61412345678", Locale::from("en-AU"), None).unwrap());
431
432 assert!(is_mobile_phone("+5511912345678", Locale::from("pt-BR"), None).unwrap());
434 }
435
436 #[test]
437 fn test_get_supported_locales() {
438 let locales = get_supported_locales();
439 assert!(!locales.is_empty());
440 assert!(locales.contains(&"en-US"));
441 assert!(locales.contains(&"en-GB"));
442 assert!(locales.contains(&"fr-FR"));
443 }
444
445 #[test]
446 fn test_aliases() {
447 assert!(is_mobile_phone("+14155552671", Locale::from("en-CA"), None).unwrap());
449 assert!(is_mobile_phone("+14155552671", Locale::from("fr-CA"), None).unwrap());
450 }
451
452 #[test]
453 fn test_am_am() {
454 let locale = Locale::from("am-AM");
455 assert!(is_mobile_phone("+37433123456", locale.clone(), None).unwrap());
456 assert!(is_mobile_phone("+37441123456", locale.clone(), None).unwrap());
457 assert!(is_mobile_phone("055123456", locale.clone(), None).unwrap());
458 assert!(!is_mobile_phone("+37403498855", locale.clone(), None).unwrap());
459 assert!(!is_mobile_phone("+37416498123", locale, None).unwrap());
460 }
461
462 #[test]
463 fn test_ar_ae() {
464 let locale = Locale::from("ar-AE");
465 assert!(is_mobile_phone("+971502674453", locale.clone(), None).unwrap());
466 assert!(is_mobile_phone("0585215778", locale.clone(), None).unwrap());
467 assert!(is_mobile_phone("585215778", locale.clone(), None).unwrap());
468 assert!(!is_mobile_phone("+971511498855", locale, None).unwrap());
469 }
470
471 #[test]
472 fn test_ar_sa() {
473 let locale = Locale::from("ar-SA");
474 assert!(is_mobile_phone("0556578654", locale.clone(), None).unwrap());
475 assert!(is_mobile_phone("+966556578654", locale.clone(), None).unwrap());
476 assert!(is_mobile_phone("596578654", locale.clone(), None).unwrap());
477 assert!(!is_mobile_phone("+9665626626262", locale, None).unwrap());
478 }
479
480 #[test]
481 fn test_zh_cn() {
482 let locale = Locale::from("zh-CN");
483 assert!(is_mobile_phone("13523333233", locale.clone(), None).unwrap());
484 assert!(is_mobile_phone("+8616238234822", locale.clone(), None).unwrap());
485 assert!(is_mobile_phone("008616238234822", locale.clone(), None).unwrap());
486 assert!(!is_mobile_phone("12345", locale.clone(), None).unwrap());
487 assert!(!is_mobile_phone("010-38238383", locale, None).unwrap());
488 }
489
490 #[test]
491 fn test_zh_tw() {
492 let locale = Locale::from("zh-TW");
493 assert!(is_mobile_phone("0987123456", locale.clone(), None).unwrap());
494 assert!(is_mobile_phone("+886987123456", locale.clone(), None).unwrap());
495 assert!(is_mobile_phone("886-987123456", locale.clone(), None).unwrap());
496 assert!(!is_mobile_phone("0-987123456", locale, None).unwrap());
497 }
498
499 #[test]
500 fn test_ja_jp() {
501 let locale = Locale::from("ja-JP");
502 assert!(is_mobile_phone("09012345678", locale.clone(), None).unwrap());
503 assert!(is_mobile_phone("080 1234 5678", locale.clone(), None).unwrap());
504 assert!(is_mobile_phone("+8190-1234-5678", locale.clone(), None).unwrap());
505 assert!(!is_mobile_phone("0312345678", locale, None).unwrap());
506 }
507
508 #[test]
509 fn test_ko_kr() {
510 let locale = Locale::from("ko-KR");
511 assert!(is_mobile_phone("+82-010-1234-5678", locale.clone(), None).unwrap());
512 assert!(is_mobile_phone("010-123-5678", locale.clone(), None).unwrap());
513 assert!(is_mobile_phone("01012345678", locale.clone(), None).unwrap());
514 assert!(!is_mobile_phone("+82 10 1234 567", locale, None).unwrap());
515 }
516
517 #[test]
518 fn test_es_es() {
519 let locale = Locale::from("es-ES");
520 assert!(is_mobile_phone("+34654789321", locale.clone(), None).unwrap());
521 assert!(is_mobile_phone("654789321", locale.clone(), None).unwrap());
522 assert!(is_mobile_phone("+34714789321", locale.clone(), None).unwrap());
523 assert!(!is_mobile_phone("65478932", locale, None).unwrap());
524 }
525
526 #[test]
527 fn test_pt_br() {
528 let locale = Locale::from("pt-BR");
529 assert!(is_mobile_phone("+55 12 996551215", locale.clone(), None).unwrap());
530 assert!(is_mobile_phone("5511914314567", locale.clone(), None).unwrap());
531 assert!(is_mobile_phone("(11) 94123-4567", locale.clone(), None).unwrap());
532 assert!(!is_mobile_phone("+55 11 90431-4567", locale, None).unwrap());
533 }
534
535 #[test]
536 fn test_it_it() {
537 let locale = Locale::from("it-IT");
538 assert!(is_mobile_phone("370 3175423", locale.clone(), None).unwrap());
539 assert!(is_mobile_phone("+39 310 7688449", locale.clone(), None).unwrap());
540 assert!(!is_mobile_phone("011 7387545", locale, None).unwrap());
541 }
542
543 #[test]
544 fn test_pl_pl() {
545 let locale = Locale::from("pl-PL");
546 assert!(is_mobile_phone("+48512689767", locale.clone(), None).unwrap());
547 assert!(is_mobile_phone("657562855", locale.clone(), None).unwrap());
548 assert!(is_mobile_phone("+48 56 376 87 47", locale.clone(), None).unwrap());
549 assert!(!is_mobile_phone("3454535", locale, None).unwrap());
550 }
551
552 #[test]
553 fn test_tr_tr() {
554 let locale = Locale::from("tr-TR");
555 assert!(is_mobile_phone("+905321234567", locale.clone(), None).unwrap());
556 assert!(is_mobile_phone("05321234567", locale.clone(), None).unwrap());
557 assert!(!is_mobile_phone("12345", locale, None).unwrap());
558 }
559
560 #[test]
561 fn test_en_in() {
562 let locale = Locale::from("en-IN");
563 assert!(is_mobile_phone("+919876543210", locale.clone(), None).unwrap());
564 assert!(is_mobile_phone("919876543210", locale.clone(), None).unwrap());
565 assert!(is_mobile_phone("09876543210", locale.clone(), None).unwrap());
566 assert!(!is_mobile_phone("12345", locale, None).unwrap());
567 }
568
569 #[test]
570 fn test_vi_vn() {
571 let locale = Locale::from("vi-VN");
572 assert!(is_mobile_phone("0336012403", locale.clone(), None).unwrap());
573 assert!(is_mobile_phone("+84586012403", locale.clone(), None).unwrap());
574 assert!(is_mobile_phone("84981577798", locale.clone(), None).unwrap());
575 assert!(!is_mobile_phone("01678912345", locale, None).unwrap());
576 }
577
578 #[test]
579 fn test_en_sg() {
580 let locale = Locale::from("en-SG");
581 assert!(is_mobile_phone("87654321", locale.clone(), None).unwrap());
582 assert!(is_mobile_phone("+6587654321", locale.clone(), None).unwrap());
583 assert!(!is_mobile_phone("12345678", locale, None).unwrap());
584 }
585
586 #[test]
587 fn test_ar_eg() {
588 let locale = Locale::from("ar-EG");
589 assert!(is_mobile_phone("+201004513789", locale.clone(), None).unwrap());
590 assert!(is_mobile_phone("01090124576", locale.clone(), None).unwrap());
591 assert!(is_mobile_phone("1090124576", locale.clone(), None).unwrap());
592 assert!(!is_mobile_phone("+201404513789", locale, None).unwrap());
593 }
594
595 #[test]
596 fn test_id_id() {
597 let locale = Locale::from("id-ID");
598 assert!(is_mobile_phone("0811 778 998", locale.clone(), None).unwrap());
599 assert!(is_mobile_phone("+62811778998", locale.clone(), None).unwrap());
600 assert!(is_mobile_phone("628993123618190", locale.clone(), None).unwrap());
601 assert!(!is_mobile_phone("0217123456", locale, None).unwrap());
602 }
603
604 #[test]
605 fn test_th_th() {
606 let locale = Locale::from("th-TH");
607 assert!(is_mobile_phone("0912345678", locale.clone(), None).unwrap());
608 assert!(is_mobile_phone("+66912345678", locale.clone(), None).unwrap());
609 assert!(is_mobile_phone("66912345678", locale.clone(), None).unwrap());
610 assert!(!is_mobile_phone("67812345623", locale, None).unwrap());
611 }
612
613 #[test]
614 fn test_ru_ru() {
615 let locale = Locale::from("ru-RU");
616 assert!(is_mobile_phone("+79676338855", locale.clone(), None).unwrap());
617 assert!(is_mobile_phone("79676338855", locale.clone(), None).unwrap());
618 assert!(is_mobile_phone("89676338855", locale.clone(), None).unwrap());
619 assert!(!is_mobile_phone("+9676338855", locale, None).unwrap());
620 }
621
622 #[test]
623 fn test_nb_no() {
624 let locale = Locale::from("nb-NO");
625 assert!(is_mobile_phone("+4796338855", locale.clone(), None).unwrap());
626 assert!(is_mobile_phone("4796338855", locale.clone(), None).unwrap());
627 assert!(is_mobile_phone("46338855", locale.clone(), None).unwrap());
628 assert!(!is_mobile_phone("+4676338855", locale, None).unwrap());
629 }
630
631 #[test]
632 fn test_da_dk() {
633 let locale = Locale::from("da-DK");
634 assert!(is_mobile_phone("12345678", locale.clone(), None).unwrap());
635 assert!(is_mobile_phone("+45 12 34 56 78", locale.clone(), None).unwrap());
636 assert!(!is_mobile_phone("12 34 56", locale, None).unwrap());
637 }
638
639 #[test]
640 fn test_sv_se() {
641 let locale = Locale::from("sv-SE");
642 assert!(is_mobile_phone("+46701234567", locale.clone(), None).unwrap());
643 assert!(is_mobile_phone("0721234567", locale.clone(), None).unwrap());
644 assert!(!is_mobile_phone("+46301234567", locale, None).unwrap());
645 }
646
647 #[test]
648 fn test_fi_fi() {
649 let locale = Locale::from("fi-FI");
650 assert!(is_mobile_phone("+358505557171", locale.clone(), None).unwrap());
651 assert!(is_mobile_phone("0455571", locale.clone(), None).unwrap());
652 assert!(!is_mobile_phone("045557", locale, None).unwrap());
653 }
654
655 #[test]
656 fn test_strict_mode_comprehensive() {
657 let options = Some(MobileOptions { strict_mode: true });
658
659 assert!(is_mobile_phone("+254728530234", Locale::Any, options.clone()).unwrap());
661 assert!(is_mobile_phone("+299 12 34 56", Locale::Any, options.clone()).unwrap());
662 assert!(is_mobile_phone("+94766660206", Locale::Any, options.clone()).unwrap());
663
664 assert!(!is_mobile_phone("254728530234", Locale::Any, options.clone()).unwrap());
666 assert!(!is_mobile_phone("0728530234", Locale::Any, options.clone()).unwrap());
667 assert!(!is_mobile_phone("766667206", Locale::Any, options).unwrap());
668 }
669
670 #[test]
671 fn test_any_locale_comprehensive() {
672 let valid_numbers = vec![
674 "+14155552671", "+447911123456", "+33612345678", "+4915123456789", "+919876543210", "+61412345678", "+5511912345678", "+81 90 1234 5678", "+8613523333233", "+82 10 1234 5678", "+34654789321", "+39 310 7688449", "+48512689767", "+79676338855", "+66912345678", "+84586012403", "+6587654321", ];
692
693 for number in valid_numbers {
694 assert!(is_mobile_phone(number, Locale::Any, None).unwrap(),
695 "Failed for: {}", number);
696 }
697
698 let invalid_numbers = vec!["", "asdf", "1", "12345"];
700 for number in invalid_numbers {
701 assert!(!is_mobile_phone(number, Locale::Any, None).unwrap(),
702 "Should have failed for: {}", number);
703 }
704 }
705
706 #[test]
707 fn test_empty_locale_defaults_to_any() {
708 assert!(is_mobile_phone("+14155552671", Locale::from(""), None).unwrap());
710 assert!(is_mobile_phone("+447911123456", Locale::from(""), None).unwrap());
711 }
712
713 #[test]
714 fn test_ar_jo() {
715 let locale = Locale::from("ar-JO");
716 assert!(is_mobile_phone("0796477263", locale.clone(), None).unwrap());
717 assert!(is_mobile_phone("+962796477263", locale.clone(), None).unwrap());
718 assert!(!is_mobile_phone("00962786725261", locale, None).unwrap());
719 }
720
721 #[test]
722 fn test_es_mx() {
723 let locale = Locale::from("es-MX");
724 assert!(is_mobile_phone("+52019654789321", locale.clone(), None).unwrap());
725 assert!(is_mobile_phone("5219654789321", locale.clone(), None).unwrap());
726 assert!(!is_mobile_phone("65478932", locale, None).unwrap());
727 }
728
729 #[test]
730 fn test_es_ar() {
731 let locale = Locale::from("es-AR");
732 assert!(is_mobile_phone("5491143214321", locale.clone(), None).unwrap());
733 assert!(is_mobile_phone("+5491143214321", locale.clone(), None).unwrap());
734 assert!(!is_mobile_phone("1143214321", locale, None).unwrap());
735 }
736
737 #[test]
738 fn test_nl_nl() {
739 let locale = Locale::from("nl-NL");
740 assert!(is_mobile_phone("0670123456", locale.clone(), None).unwrap());
741 assert!(is_mobile_phone("+31612345678", locale.clone(), None).unwrap());
742 assert!(!is_mobile_phone("012345678", locale, None).unwrap());
743 }
744
745 #[test]
746 fn test_el_gr() {
747 let locale = Locale::from("el-GR");
748 assert!(is_mobile_phone("+306944848966", locale.clone(), None).unwrap());
749 assert!(is_mobile_phone("6944848966", locale.clone(), None).unwrap());
750 assert!(!is_mobile_phone("6924567890", locale, None).unwrap());
751 }
752}
753