Add some phonenumberutil functions

This commit is contained in:
Vlasislav Kashin
2025-07-09 13:22:32 +03:00
parent 29f5e5664c
commit 7692433296
11 changed files with 1409 additions and 91 deletions

View File

@@ -6,7 +6,8 @@ mod phonenumberutil;
mod regexp_cache;
mod regex_based_matcher;
pub mod i18n;
pub mod regex_util;
pub(crate) mod regex_util;
pub(crate) mod string_util;
/// I decided to create this module because there are many
/// boilerplate places in the code that can be replaced with macros,

View File

@@ -1,3 +1,6 @@
use core::error;
use std::num::ParseIntError;
use thiserror::Error;
use crate::regexp_cache::ErrorInvalidRegex;
@@ -5,5 +8,38 @@ use crate::regexp_cache::ErrorInvalidRegex;
#[derive(Debug, PartialEq, Error)]
pub enum PhoneNumberUtilError {
#[error("{0}")]
InvalidRegexError(#[from] ErrorInvalidRegex)
}
InvalidRegexError(#[from] ErrorInvalidRegex),
#[error("Parse error: {0}")]
ParseError(#[from] ParseError),
#[error("Extract number error: {0}")]
ExtractNumberError(#[from] ExtractNumberError)
}
#[derive(Debug, PartialEq, Error)]
pub enum ParseError {
// Removed as OK variant
// NoParsingError,
#[error("Invalid country code")]
InvalidCountryCodeError, // INVALID_COUNTRY_CODE in the java version.
#[error("Not a number")]
NotANumber,
#[error("Too short after idd")]
TooShortAfterIdd,
#[error("Too short Nsn")]
TooShortNsn,
#[error("Too long nsn")]
TooLongNsn, // TOO_LONG in the java version.
#[error("{0}")]
InvalidRegexError(#[from] ErrorInvalidRegex),
#[error("{0}")]
ParseNumberAsIntError(#[from] ParseIntError)
}
#[derive(Debug, PartialEq, Error)]
pub enum ExtractNumberError {
#[error("No valid start character found")]
NoValidStartCharacter,
#[error("Invalid number")]
NotANumber,
}

View File

@@ -0,0 +1,31 @@
use std::borrow::Cow;
use crate::proto_gen::phonenumber::phone_number::CountryCodeSource;
#[derive(Debug)]
pub struct PhoneNumberWithCountryCodeSource<'a> {
pub phone_number: Cow<'a, str>,
pub country_code_source: CountryCodeSource
}
impl<'a> PhoneNumberWithCountryCodeSource<'a> {
pub fn new(phone_number: Cow<'a, str>, country_code_source: CountryCodeSource) -> Self {
Self { phone_number, country_code_source }
}
}
#[derive(Debug)]
pub struct PhoneNumberAndCarrierCode<'a> {
pub carrier_code: Option<&'a str>,
pub phone_number: Cow<'a, str>
}
impl<'a> PhoneNumberAndCarrierCode<'a> {
pub fn new<B: Into<Cow<'a, str>>>(carrier_code: Option<&'a str>, phone_number: B) -> Self {
Self { carrier_code, phone_number: phone_number.into() }
}
pub fn new_phone<B: Into<Cow<'a, str>>>(phone_number: B) -> Self {
Self { carrier_code: None, phone_number: phone_number.into() }
}
}

View File

@@ -4,30 +4,15 @@ mod errors;
mod enums;
mod phonenumberutil;
mod phone_number_regexps_and_mappings;
pub(self) mod helper_types;
use std::sync::LazyLock;
pub use enums::{MatchType, PhoneNumberFormat, PhoneNumberType, ValidationResultErr, ValidNumberLenType};
use thiserror::Error;
use crate::phonenumberutil::phonenumberutil::PhoneNumberUtil;
// use crate::phonenumberutil::phonenumberutil::PhoneNumberUtil;
#[derive(Debug, Error)]
pub enum ErrorType {
#[error("No parsing")]
NoParsingError,
#[error("Invalid country code")]
InvalidCountryCodeError, // INVALID_COUNTRY_CODE in the java version.
#[error("Not a number")]
NotANumber,
#[error("Too short after idd")]
TooShortAfterIdd,
#[error("Too short Nsn")]
TooShortNsn,
#[error("Too long nsn")]
TooLongNsn, // TOO_LONG in the java version.
}
static PHONE_NUMBER_UTIL: LazyLock<PhoneNumberUtil> = LazyLock::new(|| {
PhoneNumberUtil::new()
});
// static PHONE_NUMBER_UTIL: LazyLock<PhoneNumberUtil> = LazyLock::new(|| {
// PhoneNumberUtil::new()
// });

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ impl RegexBasedMatcher {
// find first occurrence
if allow_prefix_match {
Ok(regexp.consume_start(phone_number).is_some())
Ok(regexp.matches_start(phone_number))
} else {
Ok(regexp.full_match(phone_number))
}

View File

@@ -1,6 +1,4 @@
use std::borrow::Cow;
use regex::{Captures, Regex};
use regex::{Captures, Match, Regex};
pub trait RegexFullMatch {
/// Eq of C fullMatch
@@ -8,18 +6,12 @@ pub trait RegexFullMatch {
}
pub trait RegexConsume {
/// Eq of C Consume
fn consume_start<'a>(&self, s: &'a str) -> Option<Cow<'a, str>> {
self.consume_start_capturing(s).map(| res| res.0)
fn matches_start<'a>(&self, s: &'a str) -> bool {
self.find_start(s).is_some()
}
fn consume_start_capturing<'a>(&self, s: &'a str) -> Option<(Cow<'a, str>, Captures<'a>)>;
fn find_and_consume<'a>(&self, s: &'a str) -> Option<Cow<'a, str>> {
self.find_and_consume_capturing(s).map(| res| res.0)
}
fn find_and_consume_capturing<'a>(&self, s: &'a str) -> Option<(Cow<'a, str>, Captures<'a>)>;
fn captures_start<'a>(&self, s: &'a str) -> Option<Captures<'a>>;
fn find_start<'a>(&self, s: &'a str) -> Option<Match<'a>>;
}
trait RegexMatchStart {
@@ -48,24 +40,21 @@ impl RegexMatchStart for Regex {
}
impl RegexConsume for Regex {
fn consume_start_capturing<'a>(&self, s: &'a str) -> Option<(Cow<'a, str>, Captures<'a>)> {
_consume(self, s, true)
fn captures_start<'a>(&self, s: &'a str) -> Option<Captures<'a>> {
let captures = self.captures(s)?;
let full_capture = captures.get(0)?;
if full_capture.start() != 0 {
return None
}
Some(captures)
}
fn find_and_consume_capturing<'a>(&self, s: &'a str) -> Option<(Cow<'a, str>, Captures<'a>)> {
_consume(self, s, false)
fn find_start<'a>(&self, s: &'a str) -> Option<Match<'a>> {
let found = self.find(s)?;
if found.start() != 0 {
return None
}
Some(found)
}
}
fn _consume<'a>(
r: &Regex, input: &'a str,
anchor_at_start: bool
) -> Option<(Cow<'a, str>, Captures<'a>)> {
let captures = r.captures(input)?;
let full_capture = captures.get(0)?;
if anchor_at_start && full_capture.start() != 0 {
return None
}
Some((Cow::Borrowed(&input[full_capture.end()..]), captures))
}

36
src/string_util.rs Normal file
View File

@@ -0,0 +1,36 @@
use std::borrow::Cow;
/// Strips prefix of given string Cow. Returns option with `Some` if
/// prefix found and stripped.
///
/// Calls `drain` if string is owned and returns slice if string is borrowed
pub fn strip_cow_prefix<'a>(cow: Cow<'a, str>, prefix: &str) -> Option<Cow<'a, str>> {
match cow {
Cow::Borrowed(s) => s.strip_prefix(prefix).map(| s | Cow::Borrowed(s)),
Cow::Owned(mut s) => {
if s.starts_with(prefix) {
s.drain(0..prefix.len());
return Some(Cow::Owned(s));
}
None
}
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use crate::string_util::strip_cow_prefix;
#[test]
fn test_usage() {
let str_to_strip = Cow::Owned("test0:test".to_owned());
let stripped = strip_cow_prefix(str_to_strip, "test0");
assert_eq!(stripped, Some(Cow::Owned(":test".to_owned())));
let str_to_strip = Cow::Owned("test:test0".to_owned());
let stripped = strip_cow_prefix(str_to_strip, "test0");
assert_eq!(stripped, None)
}
}