use std::{borrow::Cow, collections::HashSet};
use rustc_hash::FxHashMap;
use strum::IntoEnumIterator;
use zeroes_itoa::LeadingZeroBuffer;
use crate::{
enums::{NumberLengthType, PhoneNumberFormat, PhoneNumberType},
errors::{InvalidRegexError, ValidationError},
generated::{
proto::PhoneNumber, uniprops_digits::uniprops::get_digit_value, uniprops_without_nl,
},
interfaces::MatcherApi,
phonenumberutil::{
helper_types::PrefixParts,
regex_wrapper_types::{PhoneMetadataWrapper, PhoneNumberDescWrapper},
},
};
#[cfg(feature = "builtin_metadata")]
use crate::generated::{metadata::METADATA, proto::PhoneMetadataCollection};
#[cfg(feature = "builtin_metadata")]
use prost::{DecodeError, Message};
use super::helper_constants::{
OPTIONAL_EXT_SUFFIX, PLUS_SIGN, POSSIBLE_CHARS_AFTER_EXT_LABEL,
POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL, RFC3966_EXTN_PREFIX, RFC3966_PREFIX,
};
#[cfg(feature = "builtin_metadata")]
pub fn load_compiled_metadata() -> Result<PhoneMetadataCollection, DecodeError> {
PhoneMetadataCollection::decode(METADATA)
}
pub fn get_number_desc_by_type(
metadata: &PhoneMetadataWrapper,
phone_number_type: PhoneNumberType,
) -> &PhoneNumberDescWrapper {
match phone_number_type {
PhoneNumberType::PremiumRate => &metadata.premium_rate,
PhoneNumberType::TollFree => &metadata.toll_free,
PhoneNumberType::Mobile => &metadata.mobile,
PhoneNumberType::FixedLine | PhoneNumberType::FixedLineOrMobile => &metadata.fixed_line,
PhoneNumberType::SharedCost => &metadata.shared_cost,
PhoneNumberType::VoIP => &metadata.voip,
PhoneNumberType::PersonalNumber => &metadata.personal_number,
PhoneNumberType::Pager => &metadata.pager,
PhoneNumberType::UAN => &metadata.uan,
PhoneNumberType::VoiceMail => &metadata.voicemail,
PhoneNumberType::Unknown => &metadata.general_desc,
}
}
pub fn get_number_prefix_by_format_and_calling_code(
country_calling_code: &'_ str,
number_format: PhoneNumberFormat,
) -> PrefixParts<'_> {
match number_format {
PhoneNumberFormat::E164 => PrefixParts([PLUS_SIGN, country_calling_code, "", ""], 2),
PhoneNumberFormat::International => {
PrefixParts([PLUS_SIGN, country_calling_code, " ", ""], 3)
}
PhoneNumberFormat::RFC3966 => {
PrefixParts([RFC3966_PREFIX, PLUS_SIGN, country_calling_code, "-"], 4)
}
PhoneNumberFormat::National => PrefixParts([""; 4], 0),
}
}
pub fn is_national_number_suffix_of_the_other(
first_number: &PhoneNumber,
second_number: &PhoneNumber,
) -> bool {
let mut buf = itoa::Buffer::new();
let first_number_national_number = buf.format(first_number.national_number);
let mut buf = itoa::Buffer::new();
let second_number_national_number = buf.format(second_number.national_number);
first_number_national_number.ends_with(second_number_national_number)
|| second_number_national_number.ends_with(first_number_national_number)
}
pub fn extn_digits(max_length: u32) -> String {
let mut buf = itoa::Buffer::new();
let max_length_str = buf.format(max_length);
const HELPER_STR_LEN: usize = 2 + 4 + 2;
let mut expr = String::with_capacity(
HELPER_STR_LEN + super::helper_constants::DIGITS.len() + max_length_str.len(),
);
expr.push_str("([");
expr.push_str(super::helper_constants::DIGITS);
expr.push_str("]{1,");
expr.push_str(max_length_str);
expr.push_str("})");
expr
}
pub fn get_national_significant_number<'b>(
phone_number: &PhoneNumber,
buf: &'b mut zeroes_itoa::LeadingZeroBuffer,
) -> Cow<'b, str> {
buf.format(
phone_number.national_number,
if phone_number.italian_leading_zero() {
phone_number
.number_of_leading_zeros()
.try_into()
.unwrap_or(0)
} else {
0
},
)
}
pub fn get_national_significant_number_owned(phone_number: &PhoneNumber) -> String {
let mut buf = LeadingZeroBuffer::new();
get_national_significant_number(phone_number, &mut buf).to_string()
}
pub fn normalize_digits(string: &str) -> String {
string
.chars()
.map(|char| {
get_digit_value(char)
.map(|digit| (digit + b'0') as char)
.unwrap_or(char)
})
.collect()
}
pub fn create_extn_pattern(for_parsing: bool) -> String {
const EXT_LIMIT_AFTER_EXPLICIT_LABEL: u32 = 20;
const EXT_LIMIT_AFTER_LIKELY_LABEL: u32 = 15;
const EXT_LIMIT_AFTER_AMBIGUOUS_CHAR: u32 = 9;
const EXT_LIMIT_WHEN_NOT_SURE: u32 = 6;
#[cfg(all(feature = "lite", not(feature = "regex")))]
const EXPLICIT_EXT_LABELS: &str = concat!(
"(?:",
"[eE]?[xX][tT]",
"(?:[eE][nN][sS][iI](?:[oO\u{00D3}]\u{0301}?|[\u{00F3}\u{00D3}]))?[nN]?",
"|",
"(?:[\u{FF45}\u{FF25}])?[\u{FF58}\u{FF38}][\u{FF54}\u{FF34}](?:[\u{FF4E}\u{FF2E}])?",
"|",
"[\u{0434}\u{0414}][\u{043E}\u{041E}][\u{0431}\u{0411}]",
"|",
"[aA][nN][eE][xX][oO]",
")"
);
#[cfg(all(feature = "lite", not(feature = "regex")))]
const AMBIGUOUS_EXT_LABELS: &str = concat!(
"(?:",
"[xX\u{FF58}\u{FF38}#\u{FF03}~\u{FF5E}]",
"|",
"[iI][nN][tT]",
"|",
"[\u{FF49}\u{FF29}][\u{FF4E}\u{FF2E}][\u{FF54}\u{FF34}]",
")"
);
#[cfg(feature = "regex")]
const EXPLICIT_EXT_LABELS: &str = concat!(
"(?:",
"e?xt(?:ensi(?:o\u{0301}?|\u{00F3}))?n?",
"|",
"(?:\u{FF45})?\u{FF58}\u{FF54}(?:\u{FF4E})?",
"|",
"\u{0434}\u{043E}\u{0431}",
"|",
"anexo",
")"
);
#[cfg(feature = "regex")]
const AMBIGUOUS_EXT_LABELS: &str = concat!(
"(?:",
"[x\u{FF58}#\u{FF03}~\u{FF5E}]",
"|",
"int",
"|",
"\u{FF49}\u{FF4E}\u{FF54}",
")"
);
const AMBIGUOUS_SEPARATOR: &str = "[- ]+";
let rfc_extn = fast_cat::concat_str!(
RFC3966_EXTN_PREFIX,
&extn_digits(EXT_LIMIT_AFTER_EXPLICIT_LABEL)
);
let explicit_extn = fast_cat::concat_str!(
POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL,
EXPLICIT_EXT_LABELS,
POSSIBLE_CHARS_AFTER_EXT_LABEL,
&extn_digits(EXT_LIMIT_AFTER_EXPLICIT_LABEL),
OPTIONAL_EXT_SUFFIX
);
let ambiguous_extn = fast_cat::concat_str!(
POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL,
AMBIGUOUS_EXT_LABELS,
POSSIBLE_CHARS_AFTER_EXT_LABEL,
&extn_digits(EXT_LIMIT_AFTER_AMBIGUOUS_CHAR),
OPTIONAL_EXT_SUFFIX
);
let american_style_extn_with_suffix = fast_cat::concat_str!(
AMBIGUOUS_SEPARATOR,
&extn_digits(EXT_LIMIT_WHEN_NOT_SURE),
"#"
);
let extension_pattern = fast_cat::concat_str!(
&rfc_extn,
"|",
&explicit_extn,
"|",
&ambiguous_extn,
"|",
&american_style_extn_with_suffix
);
if for_parsing {
let auto_dialling_and_ext_labels_found = "(?:,{2}|;)";
let possible_separators_number_ext_label_no_comma = "[ \u{00A0}\t]*";
let auto_dialling_extn = fast_cat::concat_str!(
possible_separators_number_ext_label_no_comma,
auto_dialling_and_ext_labels_found,
POSSIBLE_CHARS_AFTER_EXT_LABEL,
&extn_digits(EXT_LIMIT_AFTER_LIKELY_LABEL),
OPTIONAL_EXT_SUFFIX
);
let only_commas_extn = fast_cat::concat_str!(
possible_separators_number_ext_label_no_comma,
"(?:,)+",
POSSIBLE_CHARS_AFTER_EXT_LABEL,
&extn_digits(EXT_LIMIT_AFTER_AMBIGUOUS_CHAR),
OPTIONAL_EXT_SUFFIX
);
return fast_cat::concat_str!(
&extension_pattern,
"|",
&auto_dialling_extn,
"|",
&only_commas_extn
);
}
extension_pattern
}
pub fn normalize_helper(
normalization_replacements: &FxHashMap<char, char>,
remove_non_matches: bool,
phone_number: &str,
) -> String {
let mut normalized_number = String::with_capacity(phone_number.len());
for phone_char in phone_number.chars() {
if let Some(replacement) = normalization_replacements.get(&phone_char.to_ascii_uppercase())
{
normalized_number.push(*replacement);
} else if !remove_non_matches {
normalized_number.push(phone_char);
}
}
normalized_number
}
pub fn desc_has_possible_number_data(desc: &PhoneNumberDescWrapper) -> bool {
desc.original.possible_length.len() != 1
|| desc
.original
.possible_length
.first()
.map(|l| *l != -1)
.unwrap_or(false)
}
pub fn desc_has_data(desc: &PhoneNumberDescWrapper) -> bool {
desc.original.example_number.is_some()
|| desc_has_possible_number_data(desc)
|| desc.national_number_pattern().pattern_base.is_some()
}
pub fn populate_supported_types_for_metadata(
metadata: &PhoneMetadataWrapper,
types: &mut HashSet<PhoneNumberType>,
) {
PhoneNumberType::iter()
.filter(|number_type| {
!matches!(
number_type,
PhoneNumberType::FixedLineOrMobile | PhoneNumberType::Unknown
)
})
.filter(|number_type| desc_has_data(get_number_desc_by_type(metadata, *number_type)))
.for_each(|number_type| {
types.insert(number_type);
});
}
pub fn get_supported_types_for_metadata(
metadata: &PhoneMetadataWrapper,
) -> HashSet<PhoneNumberType> {
const EFFECTIVE_NUMBER_TYPES: usize = 11 - 2 ;
let mut types = HashSet::with_capacity(EFFECTIVE_NUMBER_TYPES);
populate_supported_types_for_metadata(metadata, &mut types);
types
}
#[inline]
fn build_length_mask(lengths: &[i32]) -> u64 {
let mut mask = 0u64;
for &l in lengths {
if l != -1 {
mask |= 1 << l;
} else {
mask = 0;
}
}
mask
}
pub(crate) fn test_number_length(
phone_number: &str,
phone_metadata: &PhoneMetadataWrapper,
phone_number_type: PhoneNumberType,
) -> Result<NumberLengthType, ValidationError> {
let desc_for_type = get_number_desc_by_type(phone_metadata, phone_number_type);
let mut possible_mask = if desc_for_type.original.possible_length.is_empty() {
build_length_mask(&phone_metadata.general_desc.original.possible_length)
} else {
build_length_mask(&desc_for_type.original.possible_length)
};
let mut local_mask = build_length_mask(&desc_for_type.original.possible_length_local_only);
if phone_number_type == PhoneNumberType::FixedLineOrMobile {
let fixed_line_desc = get_number_desc_by_type(phone_metadata, PhoneNumberType::FixedLine);
if !desc_has_possible_number_data(fixed_line_desc) {
return test_number_length(phone_number, phone_metadata, PhoneNumberType::Mobile);
} else {
let mobile_desc = get_number_desc_by_type(phone_metadata, PhoneNumberType::Mobile);
if desc_has_possible_number_data(mobile_desc) {
let len_to_append = if mobile_desc.original.possible_length.is_empty() {
&phone_metadata.general_desc.original.possible_length
} else {
&mobile_desc.original.possible_length
};
possible_mask |= build_length_mask(len_to_append);
local_mask |= build_length_mask(&mobile_desc.original.possible_length_local_only);
}
}
}
if possible_mask == 0 {
return Err(ValidationError::InvalidLength);
}
let actual_length = phone_number.len() as u32;
if actual_length >= 64 {
return Err(ValidationError::TooLong);
}
let actual_length_bit = 1u64 << actual_length;
if (local_mask & actual_length_bit) != 0 {
return Ok(NumberLengthType::IsPossibleLocalOnly);
}
let minimum_length = possible_mask.trailing_zeros();
let maximum_length = 63 - possible_mask.leading_zeros();
if minimum_length == actual_length {
return Ok(NumberLengthType::IsPossible);
} else if minimum_length > actual_length {
return Err(ValidationError::TooShort);
} else if maximum_length < actual_length {
return Err(ValidationError::TooLong);
}
if (possible_mask & actual_length_bit) != 0 {
Ok(NumberLengthType::IsPossible)
} else {
Err(ValidationError::InvalidLength)
}
}
pub fn test_number_length_with_unknown_type(
phone_number: &str,
phone_metadata: &PhoneMetadataWrapper,
) -> Result<NumberLengthType, ValidationError> {
test_number_length(phone_number, phone_metadata, PhoneNumberType::Unknown)
}
pub(crate) fn copy_core_fields_only(from_number: &PhoneNumber) -> PhoneNumber {
PhoneNumber {
country_code: from_number.country_code,
national_number: from_number.national_number,
extension: from_number.extension.clone(),
italian_leading_zero: from_number.italian_leading_zero,
number_of_leading_zeros: from_number.number_of_leading_zeros,
..Default::default()
}
}
pub fn is_match(
matcher_api: &dyn MatcherApi,
number: &str,
number_desc: &PhoneNumberDescWrapper,
) -> Result<bool, InvalidRegexError> {
matcher_api.match_national_number(number, number_desc, false)
}
pub fn is_unwanted_end_char(c: char) -> bool {
c != '#' && uniprops_without_nl::uniprops::Category::from_char(c).is_some()
}