rlibphonenumber/phonenumberutil/phonenumberutil.rs
1// Copyright (C) 2009 The Libphonenumber Authors
2// Copyright (C) 2025 Kashin Vladislav (Rust adaptation author)
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! This module provides the main entry point for interacting with the phone number handling library.
17//!
18//! It exposes the `PhoneNumberUtil` struct, which contains a comprehensive set of methods
19//! for parsing, formatting, validating, and analyzing phone numbers from various regions
20//! around the world. This utility is designed to handle the complexities of international
21//! phone number formats, country codes, and numbering plans.
22
23use std::borrow::Cow;
24
25use crate::{
26 generated::proto::phonenumber::PhoneNumber,
27};
28
29use super::{
30 errors::{ParseError, ValidationError, GetExampleNumberError},
31 enums::{PhoneNumberFormat, PhoneNumberType, MatchType, NumberLengthType},
32 phonenumberutil_internal::PhoneNumberUtilInternal,
33};
34
35
36/// The main struct for all phone number-related operations.
37///
38/// It encapsulates the library's core logic and provides a public API for parsing,
39/// formatting, and validating phone numbers. An instance of this struct is the
40/// primary entry point for using the library's features.
41pub struct PhoneNumberUtil {
42 util_internal: PhoneNumberUtilInternal
43}
44
45impl PhoneNumberUtil {
46
47 /// Creates new `PhoneNumberUtil` instance
48 pub fn new() -> Self {
49 Self { util_internal:
50 PhoneNumberUtilInternal::new()
51 .expect("Metadata should be valid and all regex should compile")
52 }
53 }
54
55 /// Checks if a `PhoneNumber` can be dialed internationally.
56 ///
57 /// # Parameters
58 ///
59 /// * `phone_number`: A reference to the `PhoneNumber` object to be checked.
60 ///
61 /// # Returns
62 ///
63 /// `true` if the number can be dialed from another country, `false` otherwise.
64 ///
65 /// # Panics
66 ///
67 /// This method panics if the underlying metadata contains an invalid regular expression,
68 /// which indicates a critical library bug.
69 pub fn can_be_internationally_dialled(&self, phone_number: &PhoneNumber) -> bool {
70 self.util_internal
71 .can_be_internationally_dialled(phone_number)
72 // This should not never happen
73 .expect("A valid regex is expected in metadata; this indicates a library bug.")
74 }
75
76 /// Converts all alpha characters in a phone number string to their corresponding digits.
77 ///
78 /// For example, an input of "1-800-FLOWERS" will be converted to "1-800-3569377".
79 ///
80
81 /// # Parameters
82 ///
83 /// * `number`: A string slice or `String` representing the phone number.
84 ///
85 /// # Returns
86 ///
87 /// A `String` containing the phone number with all alphabetic characters converted to digits.
88 pub fn convert_alpha_characters_in_number<'a>(&self, number: impl AsRef<str>) -> String {
89 self.util_internal.convert_alpha_characters_in_number(number.as_ref())
90 }
91
92 /// Formats a `PhoneNumber` into a standardized format.
93 ///
94 /// # Parameters
95 ///
96 /// * `phone_number`: The `PhoneNumber` to be formatted.
97 /// * `number_format`: The `PhoneNumberFormat` to be applied (e.g., E164, INTERNATIONAL, NATIONAL).
98 ///
99 /// # Returns
100 ///
101 /// A `Cow<'a, str>` which is either a borrowed reference to a pre-formatted string or a
102 /// newly allocated `String` with the formatted number.
103 ///
104 /// # Panics
105 ///
106 /// This method panics if the underlying metadata contains an invalid regular expression,
107 /// indicating a library bug.
108 pub fn format<'a>(&self, phone_number: &'a PhoneNumber, number_format: PhoneNumberFormat) -> Cow<'a, str> {
109 self.util_internal
110 .format(phone_number, number_format)
111 // This should not never happen
112 .expect("A valid regex is expected in metadata; this indicates a library bug.")
113 }
114
115 /// Formats a `PhoneNumber`, attempting to preserve original formatting and punctuation.
116 ///
117 /// The number is formatted in the national format of the region it is from.
118 ///
119 /// # Parameters
120 ///
121 /// * `phone_number`: The `PhoneNumber` to be formatted.
122 /// * `region_calling_from`: The two-letter region code (ISO 3166-1) from where the call is being made.
123 ///
124 /// # Returns
125 ///
126 /// A `Cow<'a, str>` containing the formatted number.
127 ///
128 /// # Panics
129 ///
130 /// This method panics if metadata is invalid, which indicates a library bug.
131 pub fn format_in_original_format<'a>(
132 &self, phone_number: &'a PhoneNumber, region_calling_from: impl AsRef<str>
133 ) -> Cow<'a, str> {
134 self.util_internal
135 .format_in_original_format(phone_number, region_calling_from.as_ref())
136 // This should not never happen
137 .expect("A valid regex and region is expected in metadata; this indicates a library bug.")
138 }
139
140 /// Formats a national number with a specified carrier code.
141 ///
142 /// # Parameters
143 ///
144 /// * `phone_number`: The `PhoneNumber` to format.
145 /// * `carrier_code`: The carrier code to prepend to the number.
146 ///
147 /// # Returns
148 ///
149 /// A `String` containing the formatted number.
150 ///
151 /// # Panics
152 ///
153 /// Panics if metadata is invalid, indicating a library bug.
154 pub fn format_national_number_with_carrier_code<'a>(
155 &self,
156 phone_number: &'a PhoneNumber,
157 carrier_code: impl AsRef<str>,
158 ) -> String {
159 self.util_internal
160 .format_national_number_with_carrier_code(phone_number, carrier_code.as_ref())
161 .expect("A valid regex is expected in metadata; this indicates a library bug.")
162 }
163
164 /// Formats a `PhoneNumber` for dialing from a mobile device.
165 ///
166 /// # Parameters
167 ///
168 /// * `phone_number`: The `PhoneNumber` to format.
169 /// * `region_calling_from`: The two-letter region code (ISO 3166-1) where the user is.
170 /// * `with_formatting`: If `true`, the number is formatted with punctuation; otherwise, only digits are returned.
171 ///
172 /// # Returns
173 ///
174 /// A `Cow<'a, str>` with the dialable number.
175 ///
176 /// # Panics
177 ///
178 /// Panics if formatting fails due to a library bug.
179 pub fn format_number_for_mobile_dialing<'a>(
180 &self,
181 phone_number: &'a PhoneNumber,
182 region_calling_from: impl AsRef<str>,
183 with_formatting: bool,
184 ) -> Cow<'a, str> {
185 self.util_internal
186 .format_number_for_mobile_dialing(phone_number, region_calling_from.as_ref(), with_formatting)
187 .expect("Formatting failed; this indicates a library bug.")
188 }
189
190 /// Formats a `PhoneNumber` for out-of-country calling.
191 ///
192 /// # Parameters
193 ///
194 /// * `phone_number`: The `PhoneNumber` to format.
195 /// * `region_calling_from`: The two-letter region code (ISO 3166-1) of the calling location.
196 ///
197 /// # Returns
198 ///
199 /// A `Cow<'a, str>` representing the number formatted for international dialing.
200 ///
201 /// # Panics
202 ///
203 /// Panics on invalid metadata, indicating a library bug.
204 pub fn format_out_of_country_calling_number<'a>(
205 &self, phone_number: &'a PhoneNumber, region_calling_from: impl AsRef<str>
206 ) -> Cow<'a, str> {
207 self.util_internal
208 .format_out_of_country_calling_number(phone_number, region_calling_from.as_ref())
209 // This should not never happen
210 .expect("A valid regex is expected in metadata; this indicates a library bug.")
211 }
212
213 /// Formats a `PhoneNumber` for out-of-country calling while preserving any alphabetic characters.
214 ///
215 /// # Parameters
216 ///
217 /// * `phone_number`: The `PhoneNumber` to format.
218 /// * `region_calling_from`: The two-letter region code (ISO 3166-1) of the calling location.
219 ///
220 /// # Returns
221 ///
222 /// A `Cow<'a, str>` with the formatted number.
223 ///
224 /// # Panics
225 ///
226 /// Panics on invalid metadata, indicating a library bug.
227 pub fn format_out_of_country_keeping_alpha_chars<'a>(
228 &self,
229 phone_number: &'a PhoneNumber,
230 region_calling_from: impl AsRef<str>,
231 ) -> Cow<'a, str> {
232 self.util_internal
233 .format_out_of_country_keeping_alpha_chars(phone_number, region_calling_from.as_ref())
234 .expect("Formatting failed; this indicates a library bug.")
235 }
236
237 /// Retrieves the country calling code for a given region.
238 ///
239 /// # Parameters
240 ///
241 /// * `region_code`: The two-letter region code (ISO 3166-1).
242 ///
243 /// # Returns
244 ///
245 /// An `Option<i32>` containing the country code, or `None` if the region code is invalid.
246 pub fn get_country_code_for_region(&self, region_code: impl AsRef<str>) -> Option<i32> {
247 self.util_internal
248 .get_country_code_for_region(region_code.as_ref())
249 }
250
251 /// Gets a valid example `PhoneNumber` for a specific region.
252 ///
253 /// # Parameters
254 ///
255 /// * `region_code`: The two-letter region code (ISO 3166-1).
256 ///
257 /// # Returns
258 ///
259 /// A `Result` containing a valid `PhoneNumber` on success, or a `GetExampleNumberError` on failure.
260 pub fn get_example_number(&self, region_code: impl AsRef<str>) -> Result<PhoneNumber, GetExampleNumberError> {
261 self.util_internal.get_example_number(region_code.as_ref())
262 .map_err(|err| err.into_public())
263 }
264
265 /// Gets a valid example `PhoneNumber` for a specific number type.
266 ///
267 /// # Parameters
268 ///
269 /// * `number_type`: The desired `PhoneNumberType` (e.g., MOBILE, TOLL_FREE).
270 ///
271 /// # Returns
272 ///
273 /// A `Result` containing a `PhoneNumber` on success, or `GetExampleNumberError` if no example exists.
274 pub fn get_example_number_for_type(
275 &self,
276 number_type: PhoneNumberType,
277 ) -> Result<PhoneNumber, GetExampleNumberError> {
278 self.util_internal.get_example_number_for_type(number_type)
279 .map_err(|err| err.into_public())
280 }
281
282 /// Gets an invalid but plausible example `PhoneNumber` for a specific region.
283 ///
284 /// # Parameters
285 ///
286 /// * `region_code`: The two-letter region code (ISO 3166-1).
287 ///
288 /// # Returns
289 ///
290 /// A `Result` containing an invalid `PhoneNumber` on success, or a `GetExampleNumberError` on failure.
291 pub fn get_invalid_example_number(&self, region_code: impl AsRef<str>) -> Result<PhoneNumber, GetExampleNumberError> {
292 self.util_internal.get_invalid_example_number(region_code.as_ref())
293 .map_err(|err| err.into_public())
294 }
295
296 /// Gets the length of the geographical area code from a `PhoneNumber`.
297 ///
298 /// # Parameters
299 ///
300 /// * `phone_number`: The `PhoneNumber` to examine.
301 ///
302 /// # Returns
303 ///
304 /// The length of the area code, or `0` if it cannot be determined.
305 ///
306 /// # Panics
307 ///
308 /// Panics on invalid metadata, indicating a library bug.
309 pub fn get_length_of_geographical_area_code(&self, phone_number: &PhoneNumber) -> usize {
310 self.util_internal
311 .get_length_of_geographical_area_code(phone_number)
312 .expect("A valid regex is expected in metadata; this indicates a library bug.")
313 }
314
315 /// Gets the length of the national destination code from a `PhoneNumber`.
316 ///
317 /// # Parameters
318 ///
319 /// * `phone_number`: The `PhoneNumber` to examine.
320 ///
321 /// # Returns
322 ///
323 /// The length of the national destination code.
324 ///
325 /// # Panics
326 ///
327 /// Panics on invalid metadata, indicating a library bug.
328 pub fn get_length_of_national_destination_code(&self, phone_number: &PhoneNumber) -> usize {
329 self.util_internal
330 .get_length_of_national_destination_code(phone_number)
331 .expect("A valid regex is expected in metadata; this indicates a library bug.")
332 }
333
334 /// Gets the National Significant Number (NSN) from a `PhoneNumber`.
335 ///
336 /// The NSN is the part of the number that follows the country code.
337 ///
338 /// # Parameters
339 ///
340 /// * `phone_number`: The `PhoneNumber` from which to extract the NSN.
341 ///
342 /// # Returns
343 ///
344 /// A `String` containing the NSN.
345 pub fn get_national_significant_number<'a>(&self, phone_number: &'a PhoneNumber) -> String {
346 self.util_internal.get_national_significant_number(phone_number)
347 }
348
349 /// Determines the `PhoneNumberType` of a given `PhoneNumber`.
350 ///
351 /// # Parameters
352 ///
353 /// * `phone_number`: The `PhoneNumber` to be categorized.
354 ///
355 /// # Returns
356 ///
357 /// The `PhoneNumberType` (e.g., MOBILE, FIXED_LINE, UNKNOWN).
358 ///
359 /// # Panics
360 ///
361 /// Panics on invalid metadata, indicating a library bug.
362 pub fn get_number_type(&self, phone_number: &PhoneNumber) -> PhoneNumberType {
363 self
364 .util_internal
365 .get_number_type(phone_number)
366 // This should not never happen
367 .expect("A valid regex and region is expected in metadata; this indicates a library bug.")
368 }
369
370 /// Gets the primary region code for a given country calling code.
371 ///
372 /// Note: Some country codes are shared by multiple regions (e.g., +1 for USA, Canada).
373 /// This returns the main region for that code (e.g., "US" for +1).
374 ///
375 /// # Parameters
376 ///
377 /// * `country_code`: The country calling code.
378 ///
379 /// # Returns
380 ///
381 /// A string slice with the corresponding two-letter region code. Returns "ZZ" for invalid codes.
382 pub fn get_region_code_for_country_code(&self, country_code: i32) -> &str {
383 self.util_internal.get_region_code_for_country_code(country_code)
384 }
385
386 /// Gets the region code for a `PhoneNumber`.
387 ///
388 /// # Parameters
389 ///
390 /// * `phone_number`: The `PhoneNumber` to identify.
391 ///
392 /// # Returns
393 ///
394 /// A string slice with the two-letter region code.
395 ///
396 /// # Panics
397 ///
398 /// Panics on invalid metadata, indicating a library bug.
399 pub fn get_region_code_for_number(&self, phone_number: &PhoneNumber) -> &str {
400 self
401 .util_internal
402 .get_region_code_for_number(phone_number)
403 // This should not never happen
404 .expect("A valid regex is expected in metadata; this indicates a library bug.")
405 }
406
407 /// Gets all region codes associated with a country calling code.
408 ///
409 /// # Parameters
410 ///
411 /// * `country_code`: The country calling code.
412 ///
413 /// # Returns
414 ///
415 /// An `Option` containing an iterator over all associated region codes, or `None` if the
416 /// country code is invalid.
417 pub fn get_region_codes_for_country_code(&self, country_code: i32) -> Option<impl ExactSizeIterator<Item=&str>> {
418 self.util_internal.get_region_codes_for_country_calling_code(country_code)
419 }
420
421 /// Gets an iterator over all supported two-letter region codes.
422 ///
423 /// # Returns
424 ///
425 /// An `ExactSizeIterator` that yields string slices of all supported region codes.
426 pub fn get_supported_regions(&self) -> impl ExactSizeIterator<Item=&str> {
427 self.util_internal.get_supported_regions()
428 }
429
430 /// Checks if a number string contains alphabetic characters.
431 ///
432 /// # Parameters
433 ///
434 /// * `number`: The phone number string to check.
435 ///
436 /// # Returns
437 ///
438 /// `true` if the string contains letters, `false` otherwise.
439 pub fn is_alpha_number(&self, number: impl AsRef<str>) -> bool {
440 self.util_internal.is_alpha_number(number.as_ref())
441 }
442
443 /// Checks if a region is part of the North American Numbering Plan (NANPA).
444 ///
445 /// # Parameters
446 ///
447 /// * `region_code`: The two-letter region code (ISO 3166-1) to check.
448 ///
449 /// # Returns
450 ///
451 /// `true` if the region is a NANPA country, `false` otherwise.
452 pub fn is_nanpa_country(&self, region_code: impl AsRef<str>) -> bool {
453 self.util_internal.is_nanpa_country(region_code.as_ref())
454 }
455
456 /// Checks if a `PhoneNumber` is geographical.
457 ///
458 /// # Parameters
459 ///
460 /// * `phone_number`: The `PhoneNumber` to check.
461 ///
462 /// # Returns
463 ///
464 /// `true` if the number corresponds to a specific geographic area.
465 ///
466 /// # Panics
467 ///
468 /// Panics on invalid metadata, indicating a library bug.
469 pub fn is_number_geographical(&self, phone_number: &PhoneNumber) -> bool {
470 self.util_internal.is_number_geographical(phone_number)
471 .expect("A valid regex is expected in metadata; this indicates a library bug.")
472 }
473
474 /// Compares two phone numbers and returns their `MatchType`.
475 ///
476 /// # Parameters
477 ///
478 /// * `first_number`: The first `PhoneNumber` to compare.
479 /// * `second_number`: The second `PhoneNumber` to compare.
480 ///
481 /// # Returns
482 ///
483 /// The `MatchType` indicating the level of similarity (e.g., EXACT_MATCH, NSN_MATCH).
484 pub fn is_number_match(
485 &self,
486 first_number: &PhoneNumber,
487 second_number: &PhoneNumber,
488 ) -> MatchType {
489 self.util_internal
490 .is_number_match(first_number, second_number)
491 }
492
493 /// Performs a fast check to determine if a `PhoneNumber` is possibly valid.
494 ///
495 /// This method is less strict than `is_valid_number`.
496 ///
497 /// # Parameters
498 ///
499 /// * `phone_number`: The `PhoneNumber` to check.
500 ///
501 /// # Returns
502 ///
503 /// `true` if the number has a valid length, `false` otherwise.
504 pub fn is_possible_number(&self, phone_number: &PhoneNumber) -> bool {
505 self.util_internal.is_possible_number(phone_number)
506 }
507
508 /// Checks if a `PhoneNumber` is possibly valid and provides a reason if not.
509 ///
510 /// # Parameters
511 ///
512 /// * `phone_number`: The `PhoneNumber` to check.
513 ///
514 /// # Returns
515 ///
516 /// A `Result` which is `Ok(NumberLengthType)` on success or a `ValidationError` on failure.
517 pub fn is_possible_number_with_reason(&self, phone_number: &PhoneNumber) -> Result<NumberLengthType, ValidationError> {
518 self.util_internal.is_possible_number_with_reason(phone_number)
519 }
520
521 /// Performs a full validation of a `PhoneNumber`.
522 ///
523 /// This is a more comprehensive check than `is_possible_number`.
524 ///
525 /// # Parameters
526 ///
527 /// * `phone_number`: The `PhoneNumber` to validate.
528 ///
529 /// # Returns
530 ///
531 /// `true` if the number is valid, `false` otherwise.
532 ///
533 /// # Panics
534 ///
535 /// Panics on invalid metadata, indicating a library bug.
536 pub fn is_valid_number(&self, phone_number: &PhoneNumber) -> bool {
537 self
538 .util_internal
539 .is_valid_number(phone_number)
540 // This should not never happen
541 .expect("A valid regex is expected in metadata; this indicates a library bug.")
542 }
543
544 /// Validates a `PhoneNumber` for a specific region.
545 ///
546 /// # Parameters
547 ///
548 /// * `phone_number`: The `PhoneNumber` to validate.
549 /// * `region`: The two-letter region code (ISO 3166-1) to validate against.
550 ///
551 /// # Returns
552 ///
553 /// `true` if the number is valid for the given region, `false` otherwise.
554 pub fn is_valid_number_for_region(&self, phone_number: &PhoneNumber, region: impl AsRef<str>) -> bool {
555 self.util_internal.is_valid_number_for_region(phone_number, region.as_ref())
556 }
557
558 /// Parses a string into a `PhoneNumber`, keeping the raw input string.
559 ///
560 /// # Parameters
561 ///
562 /// * `number_to_parse`: The phone number string.
563 /// * `default_region`: The two-letter region code (ISO 3166-1) to use if the number is not in international format.
564 ///
565 /// # Returns
566 ///
567 /// A `Result` containing the parsed `PhoneNumber` on success, or a `ParseError` on failure.
568 pub fn parse_and_keep_raw_input(
569 &self,
570 number_to_parse: impl AsRef<str>,
571 default_region: impl AsRef<str>,
572 ) -> Result<PhoneNumber, ParseError> {
573 self.util_internal
574 .parse_and_keep_raw_input(number_to_parse.as_ref(), default_region.as_ref())
575 .map_err(| err | err.into_public())
576 }
577
578 /// Parses a string into a `PhoneNumber`.
579 ///
580 /// This is the primary method for converting a string representation of a number
581 /// into a structured `PhoneNumber` object.
582 ///
583 /// # Parameters
584 ///
585 /// * `number_to_parse`: The phone number string.
586 /// * `default_region`: The two-letter region code (ISO 3166-1) to use if the number is not in international format.
587 ///
588 /// # Returns
589 ///
590 /// A `Result` containing the parsed `PhoneNumber` on success, or a `ParseError` on failure.
591 pub fn parse(
592 &self,
593 number_to_parse: impl AsRef<str>,
594 default_region: impl AsRef<str>,
595 ) -> Result<PhoneNumber, ParseError> {
596 self.util_internal
597 .parse(number_to_parse.as_ref(), default_region.as_ref())
598 .map_err(| err | err.into_public())
599 }
600
601 /// Truncates a `PhoneNumber` that is too long to a valid length.
602 ///
603 /// # Parameters
604 ///
605 /// * `phone_number`: A mutable reference to the `PhoneNumber` to truncate.
606 ///
607 /// # Returns
608 ///
609 /// `true` if the number was truncated, `false` otherwise.
610 ///
611 /// # Panics
612 ///
613 /// Panics on invalid metadata, indicating a library bug.
614 pub fn truncate_too_long_number(&self, phone_number: &mut PhoneNumber) -> bool {
615 self.util_internal.truncate_too_long_number(phone_number)
616 // This should not never happen
617 .expect("A valid regex is expected in metadata; this indicates a library bug.")
618 }
619}
620