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