Add more tests, better error handling
This commit is contained in:
@@ -19,8 +19,8 @@ pub enum ParseError {
|
|||||||
// NoParsingError,
|
// NoParsingError,
|
||||||
#[error("Invalid country code")]
|
#[error("Invalid country code")]
|
||||||
InvalidCountryCodeError, // INVALID_COUNTRY_CODE in the java version.
|
InvalidCountryCodeError, // INVALID_COUNTRY_CODE in the java version.
|
||||||
#[error("Not a number")]
|
#[error("Not a number: {0}")]
|
||||||
NotANumber,
|
NotANumber(#[from] NotANumberError),
|
||||||
#[error("Too short after idd")]
|
#[error("Too short after idd")]
|
||||||
TooShortAfterIdd,
|
TooShortAfterIdd,
|
||||||
#[error("Too short Nsn")]
|
#[error("Too short Nsn")]
|
||||||
@@ -29,6 +29,14 @@ pub enum ParseError {
|
|||||||
TooLongNsn, // TOO_LONG in the java version.
|
TooLongNsn, // TOO_LONG in the java version.
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
InvalidRegexError(#[from] ErrorInvalidRegex),
|
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}")]
|
#[error("{0}")]
|
||||||
ParseNumberAsIntError(#[from] ParseIntError),
|
ParseNumberAsIntError(#[from] ParseIntError),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
@@ -43,6 +51,12 @@ pub enum ExtractNumberError {
|
|||||||
NotANumber,
|
NotANumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ExtractNumberError> for ParseError {
|
||||||
|
fn from(value: ExtractNumberError) -> Self {
|
||||||
|
NotANumberError::ExtractNumberError(value).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Error)]
|
#[derive(Debug, PartialEq, Error)]
|
||||||
pub enum GetExampleNumberError {
|
pub enum GetExampleNumberError {
|
||||||
#[error("Parse error: {0}")]
|
#[error("Parse error: {0}")]
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ impl PhoneNumberRegExpsAndMappings {
|
|||||||
separator_pattern: Regex::new(&format!("[{}]+", VALID_PUNCTUATION)).unwrap(),
|
separator_pattern: Regex::new(&format!("[{}]+", VALID_PUNCTUATION)).unwrap(),
|
||||||
extn_patterns_for_matching: create_extn_pattern(false),
|
extn_patterns_for_matching: create_extn_pattern(false),
|
||||||
extn_pattern: Regex::new(&format!("(?i)(?:{})$", &extn_patterns_for_parsing)).unwrap(),
|
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,
|
&valid_phone_number,
|
||||||
&extn_patterns_for_parsing
|
&extn_patterns_for_parsing
|
||||||
)).unwrap(),
|
)).unwrap(),
|
||||||
|
|||||||
@@ -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::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::max,
|
cmp::max,
|
||||||
@@ -7,38 +22,25 @@ use std::{
|
|||||||
|
|
||||||
use super::phone_number_regexps_and_mappings::PhoneNumberRegExpsAndMappings;
|
use super::phone_number_regexps_and_mappings::PhoneNumberRegExpsAndMappings;
|
||||||
use crate::{
|
use crate::{
|
||||||
i18n,
|
errors::NotANumberError, i18n, interfaces::MatcherApi, macros::owned_from_cow_or, phonemetadata::PhoneMetadataCollection, phonenumberutil::{
|
||||||
interfaces::MatcherApi,
|
|
||||||
macros::owned_from_cow_or,
|
|
||||||
phonemetadata::PhoneMetadataCollection,
|
|
||||||
phonenumberutil::{
|
|
||||||
MatchType, PhoneNumberFormat, PhoneNumberType, ValidNumberLenType,
|
|
||||||
errors::{
|
errors::{
|
||||||
ExtractNumberError, GetExampleNumberError, InternalLogicError,
|
ExtractNumberError, GetExampleNumberError, InternalLogicError,
|
||||||
InvalidMetadataForValidRegionError, InvalidNumberError, ParseError,
|
InvalidMetadataForValidRegionError, InvalidNumberError, ParseError,
|
||||||
ValidationResultErr,
|
ValidationResultErr,
|
||||||
},
|
}, helper_constants::{
|
||||||
helper_constants::{
|
|
||||||
DEFAULT_EXTN_PREFIX, MAX_LENGTH_COUNTRY_CODE, MAX_LENGTH_FOR_NSN, MIN_LENGTH_FOR_NSN,
|
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,
|
NANPA_COUNTRY_CODE, PLUS_SIGN, REGION_CODE_FOR_NON_GEO_ENTITY, RFC3966_EXTN_PREFIX,
|
||||||
RFC3966_ISDN_SUBADDRESS, RFC3966_PHONE_CONTEXT, RFC3966_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,
|
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,
|
is_national_number_suffix_of_the_other, load_compiled_metadata, normalize_helper,
|
||||||
prefix_number_with_country_calling_code, test_number_length,
|
prefix_number_with_country_calling_code, test_number_length,
|
||||||
test_number_length_with_unknown_type,
|
test_number_length_with_unknown_type,
|
||||||
},
|
}, helper_types::{PhoneNumberAndCarrierCode, PhoneNumberWithCountryCodeSource}, MatchType, PhoneNumberFormat, PhoneNumberType, ValidNumberLenType
|
||||||
helper_types::{PhoneNumberAndCarrierCode, PhoneNumberWithCountryCodeSource},
|
}, proto_gen::{
|
||||||
},
|
|
||||||
proto_gen::{
|
|
||||||
phonemetadata::{NumberFormat, PhoneMetadata, PhoneNumberDesc},
|
phonemetadata::{NumberFormat, PhoneMetadata, PhoneNumberDesc},
|
||||||
phonenumber::{PhoneNumber, phone_number::CountryCodeSource},
|
phonenumber::{phone_number::CountryCodeSource, PhoneNumber},
|
||||||
},
|
}, regex_based_matcher::RegexBasedMatcher, regex_util::{RegexConsume, RegexFullMatch}, regexp_cache::ErrorInvalidRegex, string_util::strip_cow_prefix
|
||||||
regex_based_matcher::RegexBasedMatcher,
|
|
||||||
regex_util::{RegexConsume, RegexFullMatch},
|
|
||||||
regexp_cache::ErrorInvalidRegex,
|
|
||||||
string_util::strip_cow_prefix,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use dec_from_char::DecimalExtended;
|
use dec_from_char::DecimalExtended;
|
||||||
@@ -1516,7 +1518,7 @@ impl PhoneNumberUtil {
|
|||||||
Self::extract_phone_context(number_to_parse, index_of_phone_context);
|
Self::extract_phone_context(number_to_parse, index_of_phone_context);
|
||||||
if !self.is_phone_context_valid(phone_context) {
|
if !self.is_phone_context_valid(phone_context) {
|
||||||
trace!("The phone-context value for phone number {number_to_parse} is invalid.");
|
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
|
// If the phone context contains a phone number prefix, we need to capture
|
||||||
// it, whereas domains will be ignored.
|
// it, whereas domains will be ignored.
|
||||||
@@ -1628,7 +1630,8 @@ impl PhoneNumberUtil {
|
|||||||
return Ok(self
|
return Ok(self
|
||||||
.reg_exps
|
.reg_exps
|
||||||
.capture_up_to_second_number_start_pattern
|
.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())
|
.map(move |m| m.as_str())
|
||||||
.unwrap_or(extracted_number));
|
.unwrap_or(extracted_number));
|
||||||
}
|
}
|
||||||
@@ -1736,7 +1739,7 @@ impl PhoneNumberUtil {
|
|||||||
let national_number = self.build_national_number_for_parsing(number_to_parse)?;
|
let national_number = self.build_national_number_for_parsing(number_to_parse)?;
|
||||||
if !self.is_viable_phone_number(&national_number) {
|
if !self.is_viable_phone_number(&national_number) {
|
||||||
trace!("The string supplied did not seem to be a 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) {
|
if check_region && !self.check_region_for_parsing(&national_number, default_region) {
|
||||||
@@ -1808,22 +1811,19 @@ impl PhoneNumberUtil {
|
|||||||
return Err(ParseError::TooShortNsn.into());
|
return Err(ParseError::TooShortNsn.into());
|
||||||
}
|
}
|
||||||
if let Some(country_metadata) = country_metadata {
|
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,
|
country_metadata,
|
||||||
&potential_national_number,
|
&potential_national_number,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let carrier_code = phone_number_and_carrier_code
|
let carrier_code = carrier_code
|
||||||
.as_ref()
|
.map(|c| c.to_string());
|
||||||
.and_then(|p| p.carrier_code.map(|c| c.to_string()));
|
|
||||||
let potential_national_number =
|
if potential_national_number != phone_number {
|
||||||
if let Some(phone_number_and_carrier_code) = phone_number_and_carrier_code {
|
potential_national_number = Cow::Owned(phone_number.into_owned());
|
||||||
Cow::Owned(phone_number_and_carrier_code.phone_number.to_string())
|
}
|
||||||
} else {
|
|
||||||
potential_national_number
|
|
||||||
};
|
|
||||||
|
|
||||||
// We require that the NSN remaining after stripping the national prefix
|
// 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.
|
// 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);
|
temp_number.set_country_code(country_code);
|
||||||
|
|
||||||
if let Some(zeroes_count) =
|
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_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);
|
let number_as_int = u64::from_str_radix(&normalized_national_number, 10);
|
||||||
match number_as_int {
|
match number_as_int {
|
||||||
Ok(number_as_int) => temp_number.set_national_number(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);
|
return Ok(temp_number);
|
||||||
}
|
}
|
||||||
@@ -2488,12 +2489,12 @@ impl PhoneNumberUtil {
|
|||||||
&self,
|
&self,
|
||||||
metadata: &PhoneMetadata,
|
metadata: &PhoneMetadata,
|
||||||
phone_number: &'a str,
|
phone_number: &'a str,
|
||||||
) -> RegexResult<Option<PhoneNumberAndCarrierCode<'a>>> {
|
) -> RegexResult<(Cow<'a, str>, Option<&'a str>)> {
|
||||||
let possible_national_prefix = metadata.national_prefix_for_parsing();
|
let possible_national_prefix = metadata.national_prefix_for_parsing();
|
||||||
if phone_number.is_empty() || possible_national_prefix.is_empty() {
|
if phone_number.is_empty() || possible_national_prefix.is_empty() {
|
||||||
// Early return for numbers of zero length or with no national prefix
|
// Early return for numbers of zero length or with no national prefix
|
||||||
// possible.
|
// possible.
|
||||||
return Ok(None);
|
return Ok((phone_number.into(), None));
|
||||||
}
|
}
|
||||||
let general_desc = &metadata.general_desc;
|
let general_desc = &metadata.general_desc;
|
||||||
// Check if the original number is viable.
|
// Check if the original number is viable.
|
||||||
@@ -2530,17 +2531,17 @@ impl PhoneNumberUtil {
|
|||||||
// have been some part of the prefix that we captured.
|
// have been some part of the prefix that we captured.
|
||||||
// We make the transformation and check that the resultant number is still
|
// We make the transformation and check that the resultant number is still
|
||||||
// viable. If so, replace the number and return.
|
// 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 =
|
let replaced_number =
|
||||||
possible_national_prefix_pattern.replace(&phone_number, transform_rule);
|
possible_national_prefix_pattern.replace(&phone_number, transform_rule);
|
||||||
if is_viable_original_number
|
if is_viable_original_number
|
||||||
&& !helper_functions::is_match(&self.matcher_api, &replaced_number, general_desc)
|
&& !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(
|
return Ok((replaced_number, carrier_code_temp));
|
||||||
carrier_code_temp,
|
|
||||||
replaced_number,
|
|
||||||
)));
|
|
||||||
} else if let Some(matched) = captures.and_then(|c| c.get(0)) {
|
} else if let Some(matched) = captures.and_then(|c| c.get(0)) {
|
||||||
trace!(
|
trace!(
|
||||||
"Parsed the first digits as a national prefix for number '{}'.",
|
"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.
|
// transformation is necessary, and we just remove the national prefix.
|
||||||
let stripped_number = &phone_number[matched.end()..];
|
let stripped_number = &phone_number[matched.end()..];
|
||||||
if is_viable_original_number
|
if is_viable_original_number
|
||||||
&& !helper_functions::is_match(&self.matcher_api, stripped_number, general_desc)
|
&& !helper_functions::is_match(&self.matcher_api, stripped_number, general_desc) {
|
||||||
{
|
return Ok((phone_number.into(), None));
|
||||||
return Ok(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!(
|
trace!(
|
||||||
"The first digits did not match the national prefix for number '{}'.",
|
"The first digits did not match the national prefix for number '{}'.",
|
||||||
phone_number
|
phone_number
|
||||||
);
|
);
|
||||||
Ok(None)
|
Ok((phone_number.into(), None))
|
||||||
}
|
}
|
||||||
|
|
||||||
// A helper function to set the values related to leading zeros in a
|
// A helper function to set the values related to leading zeros in a
|
||||||
|
|||||||
1031
src/tests/tests.rs
1031
src/tests/tests.rs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user