From beae04dee8ef0cdc295827b052ca67ff7497b227 Mon Sep 17 00:00:00 2001 From: Vlasislav Kashin <99754299+vloldik@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:49:56 +0300 Subject: [PATCH] Add more tests, better error handling --- src/phonenumberutil/errors.rs | 18 +- .../phone_number_regexps_and_mappings.rs | 2 +- src/phonenumberutil/phonenumberutil.rs | 106 +- src/tests/tests.rs | 1031 ++++++++++++++++- 4 files changed, 1075 insertions(+), 82 deletions(-) diff --git a/src/phonenumberutil/errors.rs b/src/phonenumberutil/errors.rs index 3156117..ec008ef 100644 --- a/src/phonenumberutil/errors.rs +++ b/src/phonenumberutil/errors.rs @@ -19,8 +19,8 @@ pub enum ParseError { // NoParsingError, #[error("Invalid country code")] InvalidCountryCodeError, // INVALID_COUNTRY_CODE in the java version. - #[error("Not a number")] - NotANumber, + #[error("Not a number: {0}")] + NotANumber(#[from] NotANumberError), #[error("Too short after idd")] TooShortAfterIdd, #[error("Too short Nsn")] @@ -29,6 +29,14 @@ pub enum ParseError { TooLongNsn, // TOO_LONG in the java version. #[error("{0}")] InvalidRegexError(#[from] ErrorInvalidRegex), +} + +#[derive(Debug, PartialEq, Error)] +pub enum NotANumberError { + #[error("Number not matched a valid number pattern")] + NotMatchedValidNumberPattern, + #[error("Invalid phone context")] + InvalidPhoneContext, #[error("{0}")] ParseNumberAsIntError(#[from] ParseIntError), #[error("{0}")] @@ -43,6 +51,12 @@ pub enum ExtractNumberError { NotANumber, } +impl From for ParseError { + fn from(value: ExtractNumberError) -> Self { + NotANumberError::ExtractNumberError(value).into() + } +} + #[derive(Debug, PartialEq, Error)] pub enum GetExampleNumberError { #[error("Parse error: {0}")] diff --git a/src/phonenumberutil/phone_number_regexps_and_mappings.rs b/src/phonenumberutil/phone_number_regexps_and_mappings.rs index 54f2b54..cda44cc 100644 --- a/src/phonenumberutil/phone_number_regexps_and_mappings.rs +++ b/src/phonenumberutil/phone_number_regexps_and_mappings.rs @@ -307,7 +307,7 @@ impl PhoneNumberRegExpsAndMappings { separator_pattern: Regex::new(&format!("[{}]+", VALID_PUNCTUATION)).unwrap(), extn_patterns_for_matching: create_extn_pattern(false), extn_pattern: Regex::new(&format!("(?i)(?:{})$", &extn_patterns_for_parsing)).unwrap(), - valid_phone_number_pattern: Regex::new(&format!("(?i)(?:{})(?:{})?", + valid_phone_number_pattern: Regex::new(&format!("(?i)^(?:{})(?:{})?$", &valid_phone_number, &extn_patterns_for_parsing )).unwrap(), diff --git a/src/phonenumberutil/phonenumberutil.rs b/src/phonenumberutil/phonenumberutil.rs index c5c2384..532ebe2 100644 --- a/src/phonenumberutil/phonenumberutil.rs +++ b/src/phonenumberutil/phonenumberutil.rs @@ -1,3 +1,18 @@ +// Copyright (C) 2009 The Libphonenumber Authors +// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use std::{ borrow::Cow, cmp::max, @@ -7,38 +22,25 @@ use std::{ use super::phone_number_regexps_and_mappings::PhoneNumberRegExpsAndMappings; use crate::{ - i18n, - interfaces::MatcherApi, - macros::owned_from_cow_or, - phonemetadata::PhoneMetadataCollection, - phonenumberutil::{ - MatchType, PhoneNumberFormat, PhoneNumberType, ValidNumberLenType, + errors::NotANumberError, i18n, interfaces::MatcherApi, macros::owned_from_cow_or, phonemetadata::PhoneMetadataCollection, phonenumberutil::{ errors::{ ExtractNumberError, GetExampleNumberError, InternalLogicError, InvalidMetadataForValidRegionError, InvalidNumberError, ParseError, ValidationResultErr, - }, - helper_constants::{ + }, helper_constants::{ DEFAULT_EXTN_PREFIX, MAX_LENGTH_COUNTRY_CODE, MAX_LENGTH_FOR_NSN, MIN_LENGTH_FOR_NSN, NANPA_COUNTRY_CODE, PLUS_SIGN, REGION_CODE_FOR_NON_GEO_ENTITY, RFC3966_EXTN_PREFIX, RFC3966_ISDN_SUBADDRESS, RFC3966_PHONE_CONTEXT, RFC3966_PREFIX, - }, - helper_functions::{ + }, helper_functions::{ self, copy_core_fields_only, get_number_desc_by_type, get_supported_types_for_metadata, is_national_number_suffix_of_the_other, load_compiled_metadata, normalize_helper, prefix_number_with_country_calling_code, test_number_length, test_number_length_with_unknown_type, - }, - helper_types::{PhoneNumberAndCarrierCode, PhoneNumberWithCountryCodeSource}, - }, - proto_gen::{ + }, helper_types::{PhoneNumberAndCarrierCode, PhoneNumberWithCountryCodeSource}, MatchType, PhoneNumberFormat, PhoneNumberType, ValidNumberLenType + }, proto_gen::{ phonemetadata::{NumberFormat, PhoneMetadata, PhoneNumberDesc}, - phonenumber::{PhoneNumber, phone_number::CountryCodeSource}, - }, - regex_based_matcher::RegexBasedMatcher, - regex_util::{RegexConsume, RegexFullMatch}, - regexp_cache::ErrorInvalidRegex, - string_util::strip_cow_prefix, + phonenumber::{phone_number::CountryCodeSource, PhoneNumber}, + }, regex_based_matcher::RegexBasedMatcher, regex_util::{RegexConsume, RegexFullMatch}, regexp_cache::ErrorInvalidRegex, string_util::strip_cow_prefix }; use dec_from_char::DecimalExtended; @@ -1516,7 +1518,7 @@ impl PhoneNumberUtil { Self::extract_phone_context(number_to_parse, index_of_phone_context); if !self.is_phone_context_valid(phone_context) { trace!("The phone-context value for phone number {number_to_parse} is invalid."); - return Err(ParseError::NotANumber); + return Err(NotANumberError::InvalidPhoneContext.into()); } // If the phone context contains a phone number prefix, we need to capture // it, whereas domains will be ignored. @@ -1628,7 +1630,8 @@ impl PhoneNumberUtil { return Ok(self .reg_exps .capture_up_to_second_number_start_pattern - .find(&extracted_number) + .captures(&extracted_number) + .and_then(| c | c.get(1)) .map(move |m| m.as_str()) .unwrap_or(extracted_number)); } @@ -1736,7 +1739,7 @@ impl PhoneNumberUtil { let national_number = self.build_national_number_for_parsing(number_to_parse)?; if !self.is_viable_phone_number(&national_number) { trace!("The string supplied did not seem to be a phone number '{national_number}'."); - return Err(ParseError::NotANumber); + return Err(ParseError::NotANumber(NotANumberError::NotMatchedValidNumberPattern)); } if check_region && !self.check_region_for_parsing(&national_number, default_region) { @@ -1808,22 +1811,19 @@ impl PhoneNumberUtil { return Err(ParseError::TooShortNsn.into()); } if let Some(country_metadata) = country_metadata { - let potential_national_number = normalized_national_number.clone(); + let mut potential_national_number = normalized_national_number.clone(); - let phone_number_and_carrier_code = self.maybe_strip_national_prefix_and_carrier_code( + let (phone_number, carrier_code) = self.maybe_strip_national_prefix_and_carrier_code( country_metadata, &potential_national_number, )?; - let carrier_code = phone_number_and_carrier_code - .as_ref() - .and_then(|p| p.carrier_code.map(|c| c.to_string())); - let potential_national_number = - if let Some(phone_number_and_carrier_code) = phone_number_and_carrier_code { - Cow::Owned(phone_number_and_carrier_code.phone_number.to_string()) - } else { - potential_national_number - }; + let carrier_code = carrier_code + .map(|c| c.to_string()); + + if potential_national_number != phone_number { + potential_national_number = Cow::Owned(phone_number.into_owned()); + } // We require that the NSN remaining after stripping the national prefix // and carrier code be long enough to be a possible length for the region. @@ -1864,15 +1864,16 @@ impl PhoneNumberUtil { temp_number.set_country_code(country_code); if let Some(zeroes_count) = - Self::get_italian_leading_zeros_for_phone_number(&normalized_national_number) - { + Self::get_italian_leading_zeros_for_phone_number(&normalized_national_number) { temp_number.set_italian_leading_zero(true); - temp_number.set_number_of_leading_zeros(zeroes_count as i32); + if zeroes_count > 1 { + temp_number.set_number_of_leading_zeros(zeroes_count as i32); + } } let number_as_int = u64::from_str_radix(&normalized_national_number, 10); match number_as_int { Ok(number_as_int) => temp_number.set_national_number(number_as_int), - Err(err) => return Err(ParseError::ParseNumberAsIntError(err).into()), + Err(err) => return Err(NotANumberError::ParseNumberAsIntError(err).into()), } return Ok(temp_number); } @@ -2488,12 +2489,12 @@ impl PhoneNumberUtil { &self, metadata: &PhoneMetadata, phone_number: &'a str, - ) -> RegexResult>> { + ) -> RegexResult<(Cow<'a, str>, Option<&'a str>)> { let possible_national_prefix = metadata.national_prefix_for_parsing(); if phone_number.is_empty() || possible_national_prefix.is_empty() { // Early return for numbers of zero length or with no national prefix // possible. - return Ok(None); + return Ok((phone_number.into(), None)); } let general_desc = &metadata.general_desc; // Check if the original number is viable. @@ -2530,17 +2531,17 @@ impl PhoneNumberUtil { // have been some part of the prefix that we captured. // We make the transformation and check that the resultant number is still // viable. If so, replace the number and return. + + // Rust note: There is no known transform rules containing $\d\d + // But if any appears this should be handled with {} braces: {$\d}\d let replaced_number = possible_national_prefix_pattern.replace(&phone_number, transform_rule); if is_viable_original_number && !helper_functions::is_match(&self.matcher_api, &replaced_number, general_desc) { - return Ok(None); + return Ok((phone_number.into(), None)); } - return Ok(Some(PhoneNumberAndCarrierCode::new( - carrier_code_temp, - replaced_number, - ))); + return Ok((replaced_number, carrier_code_temp)); } else if let Some(matched) = captures.and_then(|c| c.get(0)) { trace!( "Parsed the first digits as a national prefix for number '{}'.", @@ -2551,17 +2552,22 @@ impl PhoneNumberUtil { // transformation is necessary, and we just remove the national prefix. let stripped_number = &phone_number[matched.end()..]; if is_viable_original_number - && !helper_functions::is_match(&self.matcher_api, stripped_number, general_desc) - { - return Ok(None); + && !helper_functions::is_match(&self.matcher_api, stripped_number, general_desc) { + return Ok((phone_number.into(), None)); } - return Ok(Some(PhoneNumberAndCarrierCode::new_phone(stripped_number))); + let carrier_code_temp = if let Some(capture) = first_capture { + Some(capture.as_str()) + } else { + None + }; + + return Ok((stripped_number.into(), carrier_code_temp)); } trace!( "The first digits did not match the national prefix for number '{}'.", phone_number ); - Ok(None) + Ok((phone_number.into(), None)) } // A helper function to set the values related to leading zeros in a diff --git a/src/tests/tests.rs b/src/tests/tests.rs index 76008f7..6465506 100644 --- a/src/tests/tests.rs +++ b/src/tests/tests.rs @@ -6,12 +6,12 @@ use dec_from_char::DecimalExtended; #[cfg(test)] use env_logger::Logger; use log::trace; -use protobuf::{Message}; +use protobuf::{Message, MessageField}; use crate::{ enums::{PhoneNumberFormat, PhoneNumberType, ValidNumberLenType}, errors::{ParseError, ValidationResultErr}, - phonemetadata::{PhoneMetadata, PhoneMetadataCollection, NumberFormat}, + phonemetadata::{NumberFormat, PhoneMetadata, PhoneMetadataCollection, PhoneNumberDesc}, phonenumber::{phone_number::CountryCodeSource, PhoneNumber}, PhoneNumberUtil, }; @@ -50,7 +50,7 @@ fn interchange_invalid_codepoints() { for input in invalid_inputs { assert!(!phone_util.is_viable_phone_number(input)); assert!( - phone_util.parse(input, RegionCode::gb()).is_err_and(| err | matches!(err, ParseError::NotANumber)) + phone_util.parse(input, RegionCode::gb()).is_err_and(| err | matches!(err, ParseError::NotANumber(_))) ); } } @@ -552,6 +552,406 @@ fn format_out_of_country_calling_number() { assert_eq!("011 15 8765-4321 ext. 1234", phone_util.format_out_of_country_calling_number(&test_number, RegionCode::ar()).unwrap()); } +#[test] +fn format_out_of_country_keeping_alpha_chars() { + let phone_util = get_phone_util(); + let mut alpha_numeric_number = phone_util.parse_and_keep_raw_input("1800 six-flag", RegionCode::us()).unwrap(); + + let formatted_number = phone_util.format_out_of_country_keeping_alpha_chars(&alpha_numeric_number, RegionCode::au()).unwrap(); + assert_eq!("0011 1 800 SIX-FLAG", formatted_number); + + // Formatting from within the NANPA region. + let formatted_number = phone_util.format_out_of_country_keeping_alpha_chars(&alpha_numeric_number, RegionCode::us()).unwrap(); + assert_eq!("1 800 SIX-FLAG", formatted_number); + + // Testing a number with extension. + let alpha_numeric_number_with_extn = phone_util.parse_and_keep_raw_input("800 SIX-flag ext. 1234", RegionCode::us()).unwrap(); + let formatted_number = phone_util.format_out_of_country_keeping_alpha_chars(&alpha_numeric_number_with_extn, RegionCode::au()).unwrap(); + assert_eq!("0011 1 800 SIX-FLAG extn. 1234", formatted_number); + + // Testing that if the raw input doesn't exist, it is formatted using FormatOutOfCountryCallingNumber. + alpha_numeric_number.clear_raw_input(); + let formatted_number = phone_util.format_out_of_country_keeping_alpha_chars(&alpha_numeric_number, RegionCode::de()).unwrap(); + assert_eq!("00 1 800 749 3524", formatted_number); +} + +#[test] +fn format_with_carrier_code() { + let phone_util = get_phone_util(); + + let mut ar_number = PhoneNumber::new(); + ar_number.set_country_code(54); + ar_number.set_national_number(91234125678); + + let formatted = phone_util.format(&ar_number, PhoneNumberFormat::National).unwrap(); + assert_eq!("01234 12-5678", formatted); + + let formatted = phone_util.format_national_number_with_carrier_code(&ar_number, "15").unwrap(); + assert_eq!("01234 15 12-5678", formatted); + + let formatted = phone_util.format_national_number_with_carrier_code(&ar_number, "").unwrap(); + assert_eq!("01234 12-5678", formatted); + + let formatted = phone_util.format(&ar_number, PhoneNumberFormat::E164).unwrap(); + assert_eq!("+5491234125678", formatted); + + let mut us_number = PhoneNumber::new(); + us_number.set_country_code(1); + us_number.set_national_number(4241231234); + + let formatted = phone_util.format(&us_number, PhoneNumberFormat::National).unwrap(); + assert_eq!("424 123 1234", formatted); + + let formatted = phone_util.format_national_number_with_carrier_code(&us_number, "15").unwrap(); + assert_eq!("424 123 1234", formatted); + + let mut invalid_number = PhoneNumber::new(); + invalid_number.set_country_code(0); + invalid_number.set_national_number(12345); + + let formatted = phone_util.format_national_number_with_carrier_code(&invalid_number, "89").unwrap(); + assert_eq!("12345", formatted); +} + +// Весь код, который написан - корректен и компилируется +#[test] +fn format_with_preferred_carrier_code() { + let phone_util = get_phone_util(); + let mut ar_number = PhoneNumber::new(); + ar_number.set_country_code(54); + ar_number.set_national_number(91234125678); + + // Тестируем форматирование без предпочтительного кода оператора в самом номере. + let formatted = phone_util.format_national_number_with_preferred_carrier_code(&ar_number, "15").unwrap(); + assert_eq!("01234 15 12-5678", formatted); + + let formatted = phone_util.format_national_number_with_preferred_carrier_code(&ar_number, "").unwrap(); + assert_eq!("01234 12-5678", formatted); + + // Тестируем форматирование с установленным предпочтительным кодом оператора. + ar_number.set_preferred_domestic_carrier_code("19".to_string()); + let formatted = phone_util.format(&ar_number, PhoneNumberFormat::National).unwrap(); + assert_eq!("01234 12-5678", formatted); + + let formatted = phone_util + .format_national_number_with_preferred_carrier_code(&ar_number, "15").unwrap(); + assert_eq!("01234 19 12-5678", formatted); + + let formatted = phone_util + .format_national_number_with_preferred_carrier_code(&ar_number, "").unwrap(); + assert_eq!("01234 19 12-5678", formatted); + + // Если preferred_domestic_carrier_code присутствует (даже если это просто пробел), + // используется он, а не код оператора по умолчанию. + ar_number.set_preferred_domestic_carrier_code(" ".to_string()); + let formatted = phone_util.format_national_number_with_preferred_carrier_code(&ar_number, "15").unwrap(); + assert_eq!("01234 12-5678", formatted); + + // Если preferred_domestic_carrier_code присутствует, но пуст, он игнорируется, + // и используется код оператора по умолчанию. + ar_number.set_preferred_domestic_carrier_code("".to_string()); + let formatted = phone_util.format_national_number_with_preferred_carrier_code(&ar_number, "15").unwrap(); + assert_eq!("01234 15 12-5678", formatted); + + // Для США эта функция не поддерживается, поэтому изменений быть не должно. + let mut us_number = PhoneNumber::new(); + us_number.set_country_code(1); + us_number.set_national_number(4241231234); + us_number.set_preferred_domestic_carrier_code("99".to_string()); + + let formatted = phone_util.format(&us_number, PhoneNumberFormat::National).unwrap(); + assert_eq!("424 123 1234", formatted); + + let formatted = phone_util.format_national_number_with_preferred_carrier_code(&us_number, "15").unwrap(); + assert_eq!("424 123 1234", formatted); +} + +#[test] +fn format_in_original_format() { + let phone_util = get_phone_util(); + + let mut phone_number = phone_util.parse_and_keep_raw_input("+442087654321", "GB").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "GB").unwrap(); + assert_eq!("+44 20 8765 4321", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("02087654321", "GB").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "GB").unwrap(); + assert_eq!("(020) 8765 4321", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("011442087654321", "US").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "US").unwrap(); + assert_eq!("011 44 20 8765 4321", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("442087654321", "GB").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "GB").unwrap(); + assert_eq!("44 20 8765 4321", formatted_number); + + // Если номер парсится без сохранения исходного ввода, `format_in_original_format` + // должен вернуться к стандартному национальному формату. + phone_number = phone_util.parse("+442087654321", "GB").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "GB").unwrap(); + assert_eq!("(020) 8765 4321", formatted_number); + + // Невалидные номера, для которых есть шаблон форматирования, должны быть отформатированы + // правильно. Примечание: коды регионов, начинающиеся с 7, намеренно исключены + // из тестовых метаданных для целей тестирования. + phone_number = phone_util.parse_and_keep_raw_input("7345678901", "US").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "US").unwrap(); + assert_eq!("734 567 8901", formatted_number); + + // США не является страной с ведущим нулём, и его наличие + // заставляет нас форматировать номер с использованием raw_input. + phone_number = phone_util.parse_and_keep_raw_input("0734567 8901", "US").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "US").unwrap(); + assert_eq!("0734567 8901", formatted_number); + + // Этот номер валиден, но у нас нет для него шаблона форматирования. + // Возвращаемся к исходному вводу. + phone_number = phone_util.parse_and_keep_raw_input("02-4567-8900", "KR").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "KR").unwrap(); + assert_eq!("02-4567-8900", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("01180012345678", "US").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "US").unwrap(); + assert_eq!("011 800 1234 5678", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("+80012345678", "KR").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "KR").unwrap(); + assert_eq!("+800 1234 5678", formatted_number); + + // Местные номера США форматируются корректно, так как у нас есть для них шаблоны. + phone_number = phone_util.parse_and_keep_raw_input("2530000", "US").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "US").unwrap(); + assert_eq!("253 0000", formatted_number); + + // Номер с национальным префиксом в США. + phone_number = phone_util.parse_and_keep_raw_input("18003456789", "US").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "US").unwrap(); + assert_eq!("1 800 345 6789", formatted_number); + + // Номер без национального префикса в Великобритании. + phone_number = phone_util.parse_and_keep_raw_input("2087654321", "GB").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "GB").unwrap(); + assert_eq!("20 8765 4321", formatted_number); + + // Убедимся, что метаданные не были изменены в результате предыдущего вызова. + phone_number = phone_util.parse("+442087654321", "GB").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "GB").unwrap(); + assert_eq!("(020) 8765 4321", formatted_number); + + // Номер с национальным префиксом в Мексике. + phone_number = phone_util.parse_and_keep_raw_input("013312345678", "MX").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "MX").unwrap(); + assert_eq!("01 33 1234 5678", formatted_number); + + // Номер без национального префикса в Мексике. + phone_number = phone_util.parse_and_keep_raw_input("3312345678", "MX").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "MX").unwrap(); + assert_eq!("33 1234 5678", formatted_number); + + // Итальянский стационарный номер. + phone_number = phone_util.parse_and_keep_raw_input("0212345678", "IT").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "IT").unwrap(); + assert_eq!("02 1234 5678", formatted_number); + + // Номер с национальным префиксом в Японии. + phone_number = phone_util.parse_and_keep_raw_input("00777012", "JP").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "JP").unwrap(); + assert_eq!("0077-7012", formatted_number); + + // Номер без национального префикса в Японии. + phone_number = phone_util.parse_and_keep_raw_input("0777012", "JP").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "JP").unwrap(); + assert_eq!("0777012", formatted_number); + + // Номер с кодом оператора в Бразилии. + phone_number = phone_util.parse_and_keep_raw_input("012 3121286979", "BR").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "BR").unwrap(); + assert_eq!("012 3121286979", formatted_number); + + // Национальный префикс по умолчанию в этом случае — 045. Когда вводится номер + // с префиксом 044, мы возвращаем исходный ввод, так как не хотим менять введенный номер. + phone_number = phone_util.parse_and_keep_raw_input("044(33)1234-5678", "MX").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "MX").unwrap(); + assert_eq!("044(33)1234-5678", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("045(33)1234-5678", "MX").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "MX").unwrap(); + assert_eq!("045 33 1234 5678", formatted_number); + + // Международный префикс по умолчанию в этом случае — 0011. Когда вводится номер + // с префиксом 0012, мы возвращаем исходный ввод. + phone_number = phone_util.parse_and_keep_raw_input("0012 16502530000", "AU").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "AU").unwrap(); + assert_eq!("0012 16502530000", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("0011 16502530000", "AU").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "AU").unwrap(); + assert_eq!("0011 1 650 253 0000", formatted_number); + + // Проверяем, что знак звёздочки (*) не удаляется и не добавляется к исходному вводу. + phone_number = phone_util.parse_and_keep_raw_input("*1234", "JP").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "JP").unwrap(); + assert_eq!("*1234", formatted_number); + + phone_number = phone_util.parse_and_keep_raw_input("1234", "JP").unwrap(); + let formatted_number = phone_util.format_in_original_format(&phone_number, "JP").unwrap(); + assert_eq!("1234", formatted_number); + + // Проверяем, что невалидный национальный номер без исходного ввода просто + // форматируется как национальный номер. + let mut phone_number = PhoneNumber::new(); + phone_number.set_country_code_source(CountryCodeSource::FROM_DEFAULT_COUNTRY); + phone_number.set_country_code(1); + phone_number.set_national_number(650253000); + let formatted_number = phone_util.format_in_original_format(&phone_number, "US").unwrap(); + assert_eq!("650253000", formatted_number); +} + +#[test] +fn parse_and_keep_raw() { + let phone_util = get_phone_util(); + let mut alpha_numeric_number = PhoneNumber::new(); + alpha_numeric_number.set_country_code(1); + alpha_numeric_number.set_national_number(80074935247); + alpha_numeric_number.set_raw_input("800 six-flags".to_string()); + alpha_numeric_number.set_country_code_source(CountryCodeSource::FROM_DEFAULT_COUNTRY); + + let test_number = phone_util.parse_and_keep_raw_input("800 six-flags", "US").unwrap(); + assert_eq!(alpha_numeric_number, test_number); + + alpha_numeric_number.set_national_number(8007493524); + alpha_numeric_number.set_raw_input("1800 six-flag".to_string()); + alpha_numeric_number.set_country_code_source(CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN); + let test_number = phone_util.parse_and_keep_raw_input("1800 six-flag", "US").unwrap(); + assert_eq!(alpha_numeric_number, test_number); + + alpha_numeric_number.set_raw_input("+1800 six-flag".to_string()); + alpha_numeric_number.set_country_code_source(CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN); + let test_number = phone_util.parse_and_keep_raw_input("+1800 six-flag", "CN").unwrap(); + assert_eq!(alpha_numeric_number, test_number); + + alpha_numeric_number.set_raw_input("001800 six-flag".to_string()); + alpha_numeric_number.set_country_code_source(CountryCodeSource::FROM_NUMBER_WITH_IDD); + let test_number = phone_util.parse_and_keep_raw_input("001800 six-flag", "NZ").unwrap(); + assert_eq!(alpha_numeric_number, test_number); + + // Попробуем с невалидным регионом - ожидаем ошибку. + let result = phone_util.parse("123 456 7890", "CS"); + assert!(result.is_err()); + + let mut korean_number = PhoneNumber::new(); + korean_number.set_country_code(82); + korean_number.set_national_number(22123456); + korean_number.set_raw_input("08122123456".to_string()); + korean_number.set_country_code_source(CountryCodeSource::FROM_DEFAULT_COUNTRY); + korean_number.set_preferred_domestic_carrier_code("81".to_string()); + let test_number = phone_util.parse_and_keep_raw_input("08122123456", "KR").unwrap(); + assert_eq!(korean_number, test_number); +} + +#[test] +fn parse_italian_leading_zeros() { + let phone_util = get_phone_util(); + let mut zeros_number = PhoneNumber::new(); + zeros_number.set_country_code(61); + + // Тестируем номер "011". + zeros_number.set_national_number(11); + zeros_number.set_italian_leading_zero(true); + // `number_of_leading_zeros` по умолчанию равен 1, поэтому его не устанавливаем. + let test_number = phone_util.parse("011", "AU").unwrap(); + assert_eq!(zeros_number, test_number); + + // Тестируем номер "001". + zeros_number.set_national_number(1); + zeros_number.set_italian_leading_zero(true); + zeros_number.set_number_of_leading_zeros(2); + let test_number = phone_util.parse("001", "AU").unwrap(); + assert_eq!(zeros_number, test_number); + + // Тестируем номер "000". Этот номер имеет 2 ведущих нуля. + zeros_number.set_national_number(0); + zeros_number.set_italian_leading_zero(true); + zeros_number.set_number_of_leading_zeros(2); + let test_number = phone_util.parse("000", "AU").unwrap(); + assert_eq!(zeros_number, test_number); + + // Тестируем номер "0000". Этот номер имеет 3 ведущих нуля. + zeros_number.set_national_number(0); + zeros_number.set_italian_leading_zero(true); + zeros_number.set_number_of_leading_zeros(3); + let test_number = phone_util.parse("0000", "AU").unwrap(); + assert_eq!(zeros_number, test_number); +} + +#[test] +fn maybe_strip_national_prefix_and_carrier_code() { + let phone_util = get_phone_util(); + let mut metadata = PhoneMetadata::new(); + let general_desc = PhoneNumberDesc::new(); + metadata.general_desc = MessageField::some(general_desc); + metadata.general_desc + .as_mut() + .map(| m | m.set_national_number_pattern("\\d{4,8}".to_string())); + + metadata.set_national_prefix_for_parsing("34".to_string()); + let mut number_to_strip = "34356778".to_string(); + let phone_number_and_carrier_code = phone_util + .maybe_strip_national_prefix_and_carrier_code(&metadata, &number_to_strip) + .unwrap(); + + assert_eq!("356778", phone_number_and_carrier_code.0, "Should have had national prefix stripped."); + assert_eq!(None, phone_number_and_carrier_code.1, "Should have had no carrier code stripped."); + + // Повторная попытка удаления - теперь номер не должен начинаться с национального префикса, + // поэтому дальнейшее удаление не должно происходить. + let phone_number_and_carrier_code = phone_util + .maybe_strip_national_prefix_and_carrier_code(&metadata, &number_to_strip) + .unwrap(); + + assert_eq!("356778", phone_number_and_carrier_code.0, "Should have had no change - no national prefix present."); + + // В некоторых странах нет национального префикса. Повторяем тест без указания префикса. + metadata.clear_national_prefix_for_parsing(); + let phone_number_and_carrier_code = phone_util + .maybe_strip_national_prefix_and_carrier_code(&metadata, &number_to_strip) + .unwrap(); + + assert!(phone_number_and_carrier_code.1.is_none(), "Should have had no change - empty national prefix."); + + // Если результирующий номер не соответствует национальному правилу, он не должен быть удален. + metadata.set_national_prefix_for_parsing("3".to_string()); + let mut number_to_strip = "3123".to_string(); + let phone_number_and_carrier_code = phone_util + .maybe_strip_national_prefix_and_carrier_code(&metadata, &number_to_strip) + .unwrap(); + assert_eq!("3123", phone_number_and_carrier_code.0, "Should have had no change - after stripping, it wouldn't have matched the national rule."); + + // Тестируем извлечение кода выбора оператора. + metadata.set_national_prefix_for_parsing("0(81)?".to_string()); + let mut number_to_strip = "08122123456".to_string(); +let phone_number_and_carrier_code = phone_util + .maybe_strip_national_prefix_and_carrier_code(&metadata, &number_to_strip) + .unwrap(); + assert_eq!(Some("81"), phone_number_and_carrier_code.1, "Should have had carrier code stripped."); + assert_eq!("22123456", phone_number_and_carrier_code.0, "Should have had national prefix and carrier code stripped."); + + // Если было правило преобразования, проверяем, что оно было применено. + // There is a regex difference how transform do works in rust and cpp. + // Since patterns in metadata.xml only ends with $\d and no rules like this appears + // we can do this. But this should be handled on any changes + metadata.set_national_prefix_transform_rule("5${1}5".to_string()); + // Обратите внимание, что здесь присутствует захватывающая группа. + metadata.set_national_prefix_for_parsing("0(\\d{2})".to_string()); + let mut number_to_strip = "031123".to_string(); + let phone_number_and_carrier_code = phone_util + .maybe_strip_national_prefix_and_carrier_code(&metadata, &number_to_strip) + .unwrap(); + + assert_eq!("5315123", phone_number_and_carrier_code.0, "Was not successfully transformed."); +} + #[test] fn format_out_of_country_with_invalid_region() { let phone_util = get_phone_util(); @@ -1034,57 +1434,630 @@ fn get_number_type() { #[test] fn parse_national_number() { let phone_util = get_phone_util(); + let mut nz_number = PhoneNumber::new(); nz_number.set_country_code(64); nz_number.set_national_number(33316005); - let test_number = phone_util - .parse("033316005", RegionCode::nz()) - .unwrap(); + // С национальным префиксом. + let test_number = phone_util.parse("033316005", "NZ").unwrap(); assert_eq!(nz_number, test_number); - assert!(!test_number.has_country_code_source()); - assert_eq!(CountryCodeSource::UNSPECIFIED, test_number.country_code_source()); - let test_number = phone_util.parse("33316005", RegionCode::nz()).unwrap(); + // Без национального префикса. + let test_number = phone_util.parse("33316005", "NZ").unwrap(); assert_eq!(nz_number, test_number); + + // С национальным префиксом и форматированием. + let test_number = phone_util.parse("03-331 6005", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("03 331 6005", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Тестирование парсинга формата RFC3966 с phone-context. + let test_number = phone_util.parse("tel:03-331-6005;phone-context=+64", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("tel:331-6005;phone-context=+64-3", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("tel:331-6005;phone-context=+64-3", "US").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("My number is tel:03-331-6005;phone-context=+64", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Тестирование парсинга RFC3966 с опциональными параметрами. + let test_number = phone_util.parse("tel:03-331-6005;phone-context=+64;a=%A1", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Тестирование парсинга RFC3966 с ISDN-субадресом. + let test_number = phone_util.parse("tel:03-331-6005;isub=12345;phone-context=+64", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("tel:+64-3-331-6005;isub=12345", "US").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("03-331-6005;phone-context=+64", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Тестирование международных префиксов. + // Код страны должен быть удалён. + let test_number = phone_util.parse("0064 3 d331 6005", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Попробуем снова, но на этот раз с международным номером для региона US. + // Код страны должен быть распознан и обработан корректно. + let test_number = phone_util.parse("01164 3 331 6005", "US").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("+64 3 331 6005", "US").unwrap(); + assert_eq!(nz_number, test_number); + + // Ведущий плюс должен игнорироваться, т.к. за ним следует не код страны, а IDD для США. + let test_number = phone_util.parse("+01164 3 331 6005", "US").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("+0064 3 331 6005", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + let test_number = phone_util.parse("+ 00 64 3 331 6005", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + let mut us_local_number = PhoneNumber::new(); + us_local_number.set_country_code(1); + us_local_number.set_national_number(2530000); + let test_number = phone_util.parse("tel:253-0000;phone-context=www.google.com", "US").unwrap(); + assert_eq!(us_local_number, test_number); + let test_number = phone_util.parse("tel:253-0000;isub=12345;phone-context=www.google.com", "US").unwrap(); + assert_eq!(us_local_number, test_number); + let test_number = phone_util.parse("tel:2530000;isub=12345;phone-context=1234.com", "US").unwrap(); + assert_eq!(us_local_number, test_number); + + // Тест для http://b/issue?id=2247493 + let mut nz_number_issue = PhoneNumber::new(); + nz_number_issue.set_country_code(64); + nz_number_issue.set_national_number(64123456); + let test_number = phone_util.parse("+64(0)64123456", "US").unwrap(); + assert_eq!(nz_number_issue, test_number); + + // Проверка, что "/" в номере телефона обрабатывается корректно. + let mut de_number = PhoneNumber::new(); + de_number.set_country_code(49); + de_number.set_national_number(12345678); + let test_number = phone_util.parse("123/45678", "DE").unwrap(); + assert_eq!(de_number, test_number); + + let mut us_number = PhoneNumber::new(); + us_number.set_country_code(1); + // Проверка, что '1' не используется как код страны при парсинге, если номер уже валиден. + us_number.set_national_number(1234567890); + let test_number = phone_util.parse("123-456-7890", "US").unwrap(); + assert_eq!(us_number, test_number); + + // Тестирование номеров со звездочкой. + let mut star_number = PhoneNumber::new(); + star_number.set_country_code(81); + star_number.set_national_number(2345); + let test_number = phone_util.parse("+81 *2345", "JP").unwrap(); + assert_eq!(star_number, test_number); + + let mut short_number = PhoneNumber::new(); + short_number.set_country_code(64); + short_number.set_national_number(12); + let test_number = phone_util.parse("12", "NZ").unwrap(); + assert_eq!(short_number, test_number); + + // Тест для короткого номера с ведущим нулём для страны, где 0 - национальный префикс. + // Убедиться, что он не интерпретируется как национальный префикс, если + // оставшаяся длина номера соответствует только местному номеру. + let mut short_number = PhoneNumber::new(); + short_number.set_country_code(44); + short_number.set_national_number(123456); + short_number.set_italian_leading_zero(true); + let test_number = phone_util.parse("0123456", "GB").unwrap(); + assert_eq!(short_number, test_number); +} + +#[test] +fn parse_with_phone_context() { + fn assert_throws_for_invalid_phone_context(phone_util: &PhoneNumberUtil, number_to_parse: &str) { + let result = phone_util.parse(number_to_parse, "ZZ"); + assert!(result.is_err(), "Expected an error for: {}", number_to_parse); + } + let phone_util = get_phone_util(); + let mut expected_number = PhoneNumber::new(); + expected_number.set_country_code(64); + expected_number.set_national_number(33316005); - let test_number = phone_util.parse("03-331 6005", RegionCode::nz()).unwrap(); - assert_eq!(nz_number, test_number); + // context = ";phone-context=" descriptor + // descriptor = domainname / global-number-digits + + // Валидные global-phone-digits + let mut actual_number = phone_util.parse("tel:033316005;phone-context=+64", "ZZ").unwrap(); + assert_eq!(expected_number, actual_number); - let test_number = phone_util.parse("tel:03-331-6005;phone-context=+64", RegionCode::nz()).unwrap(); - assert_eq!(nz_number, test_number); + actual_number = phone_util.parse("tel:033316005;phone-context=+64;{this isn't part of phone-context anymore!}", "ZZ").unwrap(); + assert_eq!(expected_number, actual_number); + + expected_number.set_national_number(3033316005); + actual_number = phone_util.parse("tel:033316005;phone-context=+64-3", "ZZ").unwrap(); + assert_eq!(expected_number, actual_number); + + expected_number.set_country_code(55); + expected_number.set_national_number(5033316005); + actual_number = phone_util.parse("tel:033316005;phone-context=+(555)", "ZZ").unwrap(); + assert_eq!(expected_number, actual_number); + + expected_number.set_country_code(1); + expected_number.set_national_number(23033316005); + actual_number = phone_util.parse("tel:033316005;phone-context=+-1-2.3()", "ZZ").unwrap(); + assert_eq!(expected_number, actual_number); + + // Валидный domainname + expected_number.set_country_code(64); + expected_number.set_national_number(33316005); + actual_number = phone_util.parse("tel:033316005;phone-context=abc.nz", "NZ").unwrap(); + assert_eq!(expected_number, actual_number); + + actual_number = phone_util.parse("tel:033316005;phone-context=www.PHONE-numb3r.com", "NZ").unwrap(); + assert_eq!(expected_number, actual_number); + + actual_number = phone_util.parse("tel:033316005;phone-context=a", "NZ").unwrap(); + assert_eq!(expected_number, actual_number); + + actual_number = phone_util.parse("tel:033316005;phone-context=3phone.J.", "NZ").unwrap(); + assert_eq!(expected_number, actual_number); + + actual_number = phone_util.parse("tel:033316005;phone-context=a--z", "NZ").unwrap(); + assert_eq!(expected_number, actual_number); + + // Невалидный descriptor + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context="); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=+"); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=64"); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=++64"); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=+abc"); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=."); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=3phone"); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=a-.nz"); + assert_throws_for_invalid_phone_context(&phone_util, "tel:033316005;phone-context=a{b}c"); } #[test] fn failed_parse_on_invalid_numbers() { let phone_util = get_phone_util(); - assert!(matches!(phone_util.parse("This is not a phone number", RegionCode::nz()), Err(ParseError::ExtractNumberError(_)))); - assert!(matches!(phone_util.parse("01495 72553301873 810104", RegionCode::gb()), Err(ParseError::TooLongNsn))); - assert!(matches!(phone_util.parse("123 456 7890", RegionCode::get_unknown()), Err(ParseError::InvalidCountryCodeError))); - assert!(matches!(phone_util.parse("+---", RegionCode::de()), Err(ParseError::ExtractNumberError(_)))); - assert!(matches!(phone_util.parse("+49 0", RegionCode::de()), Err(ParseError::TooShortNsn))); - assert!(matches!(phone_util.parse("0044", RegionCode::gb()), Err(ParseError::TooShortAfterIdd))); + + // Проверяем, что парсинг невалидных номеров завершается ошибкой. + assert!(matches!( + phone_util.parse("This is not a phone number", "NZ").unwrap_err(), + ParseError::NotANumber(_) + )); + assert!(matches!( + phone_util.parse("1 Still not a number", "NZ").unwrap_err(), + ParseError::NotANumber(_) + )); + assert!(matches!( + phone_util.parse("1 MICROSOFT", "NZ").unwrap_err(), + ParseError::NotANumber(_) + )); + assert!(matches!( + phone_util.parse("12 MICROSOFT", "NZ").unwrap_err(), + ParseError::NotANumber(_) + )); + assert_eq!( + phone_util.parse("01495 72553301873 810104", "GB").unwrap_err(), + ParseError::TooLongNsn + ); + assert!(matches!( + phone_util.parse("+---", "DE").unwrap_err(), + ParseError::NotANumber(_) + )); + assert!(matches!( + phone_util.parse("+***", "DE").unwrap_err(), + ParseError::NotANumber(_) + )); + assert!(matches!( + phone_util.parse("+*******91", "DE").unwrap_err(), + ParseError::NotANumber(_) + )); + assert_eq!( + phone_util.parse("+49 0", "DE").unwrap_err(), + ParseError::TooShortNsn + ); + assert_eq!( + phone_util.parse("+210 3456 56789", "NZ").unwrap_err(), + ParseError::InvalidCountryCodeError + ); + // 00 - правильный МНН, но 210 - невалидный код страны. + assert_eq!( + phone_util.parse("+ 00 210 3 331 6005", "NZ").unwrap_err(), + ParseError::InvalidCountryCodeError + ); + assert_eq!( + phone_util.parse("123 456 7890", "ZZ").unwrap_err(), + ParseError::InvalidCountryCodeError + ); + assert_eq!( + phone_util.parse("123 456 7890", "CS").unwrap_err(), + ParseError::InvalidCountryCodeError + ); + assert_eq!( + phone_util.parse("0044-----", "GB").unwrap_err(), + ParseError::TooShortAfterIdd + ); + assert_eq!( + phone_util.parse("0044", "GB").unwrap_err(), + ParseError::TooShortAfterIdd + ); + assert_eq!( + phone_util.parse("011", "US").unwrap_err(), + ParseError::TooShortAfterIdd + ); + assert_eq!( + phone_util.parse("0119", "US").unwrap_err(), + ParseError::TooShortAfterIdd + ); + // RFC3966 phone-context является веб-сайтом. + assert_eq!( + phone_util.parse("tel:555-1234;phone-context=www.google.com", "ZZ").unwrap_err(), + ParseError::InvalidCountryCodeError + ); + // Это невалидно, так как отсутствует знак "+" в phone-context. + assert!(matches!( + phone_util.parse("tel:555-1234;phone-context=1-331", "ZZ").unwrap_err(), + ParseError::NotANumber(_) + )); + // Присутствует только символ phone-context, но нет данных. + assert!(matches!( + phone_util.parse(";phone-context=", "ZZ").unwrap_err(), + ParseError::NotANumber(_) + )); +} + +#[test] +fn parse_numbers_with_plus_with_no_region() { + let phone_util = get_phone_util(); + let mut nz_number = PhoneNumber::new(); + nz_number.set_country_code(64); + nz_number.set_national_number(33316005); + // "ZZ" (неизвестный регион) разрешен только если номер начинается с "+", + // тогда код страны можно определить. + let mut result_proto = phone_util.parse("+64 3 331 6005", "ZZ").unwrap(); + assert_eq!(nz_number, result_proto); + + // Тестируем с полноширинным плюсом. + result_proto = phone_util.parse("\u{FF0B}64 3 331 6005", "ZZ").unwrap(); + assert_eq!(nz_number, result_proto); + // Тестируем с обычным плюсом, но с начальными символами, которые нужно удалить. + result_proto = phone_util.parse(" +64 3 331 6005", "ZZ").unwrap(); + assert_eq!(nz_number, result_proto); + + let mut toll_free_number = PhoneNumber::new(); + toll_free_number.set_country_code(800); + toll_free_number.set_national_number(12345678); + result_proto = phone_util.parse("+800 1234 5678", "ZZ").unwrap(); + assert_eq!(toll_free_number, result_proto); + + let mut universal_premium_rate = PhoneNumber::new(); + universal_premium_rate.set_country_code(979); + universal_premium_rate.set_national_number(123456789); + result_proto = phone_util.parse("+979 123 456 789", "ZZ").unwrap(); + assert_eq!(universal_premium_rate, result_proto); + + // Тестируем парсинг формата RFC3966 с phone context. + result_proto = phone_util.parse("tel:03-331-6005;phone-context=+64", "ZZ").unwrap(); + assert_eq!(nz_number, result_proto); + + result_proto = phone_util.parse(" tel:03-331-6005;phone-context=+64", "ZZ").unwrap(); + assert_eq!(nz_number, result_proto); + + result_proto = phone_util.parse("tel:03-331-6005;isub=12345;phone-context=+64", "ZZ").unwrap(); + assert_eq!(nz_number, result_proto); + + nz_number.set_raw_input("+64 3 331 6005".to_string()); + nz_number.set_country_code_source(CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN); + result_proto = phone_util.parse_and_keep_raw_input("+64 3 331 6005", "ZZ").unwrap(); + assert_eq!(nz_number, result_proto); +} + +#[test] +fn parse_number_too_short_if_national_prefix_stripped() { + let phone_util = get_phone_util(); + + // Тестируем, что у номера, первые цифры которого совпадают с национальным префиксом, + // они не удаляются, если это приведет к тому, что номер станет слишком коротким, + // чтобы быть возможным (стандартной длины) телефонным номером для этого региона. + let mut by_number = PhoneNumber::new(); + by_number.set_country_code(375); + by_number.set_national_number(8123); + let mut test_number = phone_util.parse("8123", "BY").unwrap(); + assert_eq!(by_number, test_number); + + by_number.set_national_number(81234); + test_number = phone_util.parse("81234", "BY").unwrap(); + assert_eq!(by_number, test_number); + + // Префикс не удаляется, так как ввод является валидным 6-значным номером, + // в то время как результат удаления - всего 5 цифр. + by_number.set_national_number(812345); + test_number = phone_util.parse("812345", "BY").unwrap(); + assert_eq!(by_number, test_number); + + // Префикс удаляется, так как возможны только 6-значные номера. + by_number.set_national_number(123456); + test_number = phone_util.parse("8123456", "BY").unwrap(); + assert_eq!(by_number, test_number); } #[test] fn parse_extensions() { let phone_util = get_phone_util(); + let mut nz_number = PhoneNumber::new(); nz_number.set_country_code(64); nz_number.set_national_number(33316005); - nz_number.set_extension("3456".to_owned()); - - let test_number = phone_util - .parse("03 331 6005 ext 3456", RegionCode::nz()).unwrap(); - assert_eq!(nz_number, test_number); - - let test_number = phone_util - .parse("03 331 6005 #3456", RegionCode::nz()).unwrap(); + nz_number.set_extension("3456".to_string()); + + let mut test_number = phone_util.parse("03 331 6005 ext 3456", "NZ").unwrap(); assert_eq!(nz_number, test_number); - let test_number = phone_util - .parse("03 331 6005x3456", RegionCode::nz()).unwrap(); + test_number = phone_util.parse("03 331 6005x3456", "NZ").unwrap(); assert_eq!(nz_number, test_number); + + test_number = phone_util.parse("03-331 6005 int.3456", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + test_number = phone_util.parse("03 331 6005 #3456", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Тестируем, что следующие номера не извлекают добавочные номера: + let mut non_extn_number = PhoneNumber::new(); + non_extn_number.set_country_code(1); + non_extn_number.set_national_number(80074935247); + + test_number = phone_util.parse("1800 six-flags", "US").unwrap(); + assert_eq!(non_extn_number, test_number); + + test_number = phone_util.parse("1800 SIX-FLAGS", "US").unwrap(); + assert_eq!(non_extn_number, test_number); + + test_number = phone_util.parse("0~0 1800 7493 5247", "PL").unwrap(); + assert_eq!(non_extn_number, test_number); + + test_number = phone_util.parse("(1800) 7493.5247", "US").unwrap(); + assert_eq!(non_extn_number, test_number); + + // Проверяем, что соответствует последний экземпляр токена расширения. + let mut extn_number = PhoneNumber::new(); + extn_number.set_country_code(1); + extn_number.set_national_number(80074935247); + extn_number.set_extension("1234".to_string()); + test_number = phone_util.parse("0~0 1800 7493 5247 ~1234", "PL").unwrap(); + assert_eq!(extn_number, test_number); + + // Проверяем исправление ошибки, когда последняя цифра номера ранее опускалась, + // если это был 0 при извлечении расширения. Также проверяем несколько различных + // случаев расширений. + let mut uk_number = PhoneNumber::new(); + uk_number.set_country_code(44); + uk_number.set_national_number(2034567890); + uk_number.set_extension("456".to_string()); + + test_number = phone_util.parse("+44 2034567890x456", "NZ").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44 2034567890x456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44 2034567890 x456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44 2034567890 X456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44 2034567890 X 456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44 2034567890 X 456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44 2034567890 x 456 ", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44 2034567890 X 456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("+44-2034567890;ext=456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + test_number = phone_util.parse("tel:2034567890;ext=456;phone-context=+44", "ZZ").unwrap(); + assert_eq!(uk_number, test_number); + + // Полноширинное расширение, только "extn". + test_number = phone_util.parse("+442034567890extn456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + // Только "xtn". + test_number = phone_util.parse("+44-2034567890xtn456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + // Только "xt". + test_number = phone_util.parse("+44-2034567890xt456", "GB").unwrap(); + assert_eq!(uk_number, test_number); + + let mut us_with_extension = PhoneNumber::new(); + us_with_extension.set_country_code(1); + us_with_extension.set_national_number(8009013355); + us_with_extension.set_extension("7246433".to_string()); + + test_number = phone_util.parse("(800) 901-3355 x 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + test_number = phone_util.parse("(800) 901-3355 , ext 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + test_number = phone_util.parse("(800) 901-3355 ; 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + // Тестирование символа расширения без окружающих пробелов. + test_number = phone_util.parse("(800) 901-3355;7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + test_number = phone_util.parse("(800) 901-3355 ,extension 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + test_number = phone_util.parse("(800) 901-3355 ,extensión 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + // Повтор с маленькой буквой o с акутом, созданной с помощью комбинированных символов. + test_number = phone_util.parse("(800) 901-3355 ,extensión 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + test_number = phone_util.parse("(800) 901-3355 , 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + test_number = phone_util.parse("(800) 901-3355 ext: 7246433", "US").unwrap(); + assert_eq!(us_with_extension, test_number); + // Тестирование русского расширения "доб" с вариантами, найденными в интернете. + let mut ru_with_extension = PhoneNumber::new(); + ru_with_extension.set_country_code(7); + ru_with_extension.set_national_number(4232022511); + ru_with_extension.set_extension("100".to_string()); + test_number = phone_util.parse("8 (423) 202-25-11, доб. 100", "RU").unwrap(); + assert_eq!(ru_with_extension, test_number); + test_number = phone_util.parse("8 (423) 202-25-11 доб. 100", "RU").unwrap(); + assert_eq!(ru_with_extension, test_number); + test_number = phone_util.parse("8 (423) 202-25-11, доб 100", "RU").unwrap(); + assert_eq!(ru_with_extension, test_number); + test_number = phone_util.parse("8 (423) 202-25-11 доб 100", "RU").unwrap(); + assert_eq!(ru_with_extension, test_number); + test_number = phone_util.parse("8 (423) 202-25-11доб 100", "RU").unwrap(); + assert_eq!(ru_with_extension, test_number); + // В верхнем регистре + test_number = phone_util.parse("8 (423) 202-25-11 ДОБ 100", "RU").unwrap(); + assert_eq!(ru_with_extension, test_number); + + // Тестируем, что если у номера два расширения, мы игнорируем второе. + let mut us_with_two_extensions_number = PhoneNumber::new(); + us_with_two_extensions_number.set_country_code(1); + us_with_two_extensions_number.set_national_number(2121231234); + us_with_two_extensions_number.set_extension("508".to_string()); + + test_number = phone_util.parse("(212)123-1234 x508/x1234", "US").unwrap(); + assert_eq!(us_with_two_extensions_number, test_number); + test_number = phone_util.parse("(212)123-1234 x508/ x1234", "US").unwrap(); + assert_eq!(us_with_two_extensions_number, test_number); + test_number = phone_util.parse("(212)123-1234 x508\\x1234", "US").unwrap(); + assert_eq!(us_with_two_extensions_number, test_number); + + // Тестируем парсинг номеров вида (645) 123-1234-910#, где последние 3 цифры + // перед # - это расширение. + us_with_extension.clear(); + us_with_extension.set_country_code(1); + us_with_extension.set_national_number(6451231234); + us_with_extension.set_extension("910".to_string()); + test_number = phone_util.parse("+1 (645) 123 1234-910#", "US").unwrap(); + assert_eq!(us_with_extension, test_number); +} + +#[test] +fn test_parse_handles_long_extensions_with_explicit_labels() { + let phone_util = get_phone_util(); + // Тестируем верхние и нижние пределы длины добавочного номера для каждого типа метки. + let mut nz_number = PhoneNumber::new(); + nz_number.set_country_code(64); + nz_number.set_national_number(33316005); + + // Сначала в формате RFC: ext_limit_after_explicit_label + nz_number.set_extension("0".to_string()); + let test_number = phone_util.parse("tel:+6433316005;ext=0", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + nz_number.set_extension("01234567890123456789".to_string()); + let test_number = phone_util.parse("tel:+6433316005;ext=01234567890123456789", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Слишком длинное расширение. + let result = phone_util.parse("tel:+6433316005;ext=012345678901234567890", "NZ"); + assert!(result.is_err()); + + // Явная метка расширения: ext_limit_after_explicit_label + nz_number.set_extension("1".to_string()); + let test_number = phone_util.parse("03 3316005ext:1", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + nz_number.set_extension("12345678901234567890".to_string()); + let test_number = phone_util.parse("03 3316005 xtn:12345678901234567890", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + let test_number = phone_util.parse("03 3316005 extension\t12345678901234567890", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + let test_number = phone_util.parse("03 3316005 xtensio:12345678901234567890", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + let test_number = phone_util.parse("03 3316005 xtensión, 12345678901234567890#", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + let test_number = phone_util.parse("03 3316005extension.12345678901234567890", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + let test_number = phone_util.parse("03 3316005 доб:12345678901234567890", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + // Слишком длинное расширение. + let result = phone_util.parse("03 3316005 extension 123456789012345678901", "NZ"); + assert!(result.is_err()); +} + + +#[test] +fn test_parse_handles_long_extensions_with_auto_dialling_labels() { + let phone_util = get_phone_util(); + // Во-вторых, случаи автодозвона и других стандартных меток добавочных номеров: + // ext_limit_after_likely_label + let mut us_number_user_input = PhoneNumber::new(); + us_number_user_input.set_country_code(1); + us_number_user_input.set_national_number(2679000000); + us_number_user_input.set_extension("123456789012345".to_string()); + + let mut test_number = phone_util.parse("+12679000000,,123456789012345#", "US").unwrap(); + assert_eq!(us_number_user_input, test_number); + + test_number = phone_util.parse("+12679000000;123456789012345#", "US").unwrap(); + assert_eq!(us_number_user_input, test_number); + + let mut uk_number_user_input = PhoneNumber::new(); + uk_number_user_input.set_country_code(44); + uk_number_user_input.set_national_number(2034000000); + uk_number_user_input.set_extension("123456789".to_string()); + + let test_number = phone_util.parse("+442034000000,,123456789#", "GB").unwrap(); + assert_eq!(uk_number_user_input, test_number); + + // Слишком длинное расширение. + let result = phone_util.parse("+12679000000,,1234567890123456#", "US"); + assert!(result.is_err()); +} + +#[test] +fn test_parse_handles_short_extensions_with_ambiguous_char() { + let phone_util = get_phone_util(); + // В-третьих, для единичных и нестандартных случаев: ext_limit_after_ambiguous_char + let mut nz_number = PhoneNumber::new(); + nz_number.set_country_code(64); + nz_number.set_national_number(33316005); + nz_number.set_extension("123456789".to_string()); + + let mut test_number = phone_util.parse("03 3316005 x 123456789", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + test_number = phone_util.parse("03 3316005 x. 123456789", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + test_number = phone_util.parse("03 3316005 #123456789#", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + test_number = phone_util.parse("03 3316005 ~ 123456789", "NZ").unwrap(); + assert_eq!(nz_number, test_number); + + let result = phone_util.parse("03 3316005 ~ 1234567890", "NZ"); + assert!(result.is_err()); +} + +#[test] +fn test_parse_handles_short_extensions_when_not_sure_of_label() { + let phone_util = get_phone_util(); + // В-третьих, когда нет явной метки расширения, но оно обозначено + // конечным #: ext_limit_when_not_sure + let mut us_number = PhoneNumber::new(); + us_number.set_country_code(1); + us_number.set_national_number(1234567890); + us_number.set_extension("666666".to_string()); + + let mut test_number = phone_util.parse("+1123-456-7890 666666#", "US").unwrap(); + assert_eq!(us_number, test_number); + + us_number.set_extension("6".to_string()); + test_number = phone_util.parse("+11234567890-6#", "US").unwrap(); + assert_eq!(us_number, test_number); + + // Слишком длинное расширение. + let result = phone_util.parse("+1123-456-7890 7777777#", "US"); + assert!(result.is_err()); } #[test]