use either::*;
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use crate::carrier::Carrier;
use crate::country;
use crate::error;
use crate::extension::Extension;
use crate::formatter;
use crate::metadata::{Database, Metadata, DATABASE};
use crate::national_number::NationalNumber;
use crate::parser;
use crate::validator;
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub struct PhoneNumber {
pub(crate) code: country::Code,
pub(crate) national: NationalNumber,
pub(crate) extension: Option<Extension>,
pub(crate) carrier: Option<Carrier>,
}
pub struct Country<'a>(&'a PhoneNumber);
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Type {
FixedLine,
Mobile,
FixedLineOrMobile,
TollFree,
PremiumRate,
SharedCost,
PersonalNumber,
Voip,
Pager,
Uan,
Emergency,
Voicemail,
ShortCode,
StandardRate,
Carrier,
NoInternational,
Unknown,
}
impl FromStr for PhoneNumber {
type Err = error::Parse;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parser::parse(None, s)
}
}
impl fmt::Display for PhoneNumber {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.format())
}
}
impl PhoneNumber {
pub fn country(&self) -> Country {
Country(self)
}
pub fn code(&self) -> &country::Code {
&self.code
}
pub fn national(&self) -> &NationalNumber {
&self.national
}
pub fn extension(&self) -> Option<&Extension> {
self.extension.as_ref()
}
pub fn carrier(&self) -> Option<&Carrier> {
self.carrier.as_ref()
}
pub fn format(&self) -> formatter::Formatter<'_, 'static, 'static> {
formatter::format(self)
}
pub fn format_with<'n, 'd>(
&'n self,
database: &'d Database,
) -> formatter::Formatter<'n, 'd, 'static> {
formatter::format_with(database, self)
}
pub fn metadata<'a>(&self, database: &'a Database) -> Option<&'a Metadata> {
match validator::source_for(database, self.code.value(), &self.national.to_string())? {
Left(region) => database.by_id(region.as_ref()),
Right(code) => database.by_code(&code).and_then(|m| m.into_iter().next()),
}
}
pub fn is_valid(&self) -> bool {
validator::is_valid(self)
}
pub fn is_valid_with(&self, database: &Database) -> bool {
validator::is_valid_with(database, self)
}
pub fn number_type(&self, database: &Database) -> Type {
match self.metadata(database) {
Some(metadata) => validator::number_type(metadata, &self.national.value.to_string()),
None => Type::Unknown,
}
}
}
impl<'a> Country<'a> {
pub fn code(&self) -> u16 {
self.0.code.value()
}
pub fn id(&self) -> Option<country::Id> {
self.0.metadata(&DATABASE).and_then(|m| m.id().parse().ok())
}
}
impl<'a> Deref for Country<'a> {
type Target = country::Code;
fn deref(&self) -> &Self::Target {
self.0.code()
}
}
#[cfg(test)]
mod test {
use crate::country::{self, *};
use crate::metadata::DATABASE;
use crate::Type;
use crate::{parser, Mode, PhoneNumber};
use anyhow::Context;
use rstest::rstest;
use rstest_reuse::{self, *};
fn parsed(number: &str) -> PhoneNumber {
parser::parse(None, number)
.with_context(|| format!("parsing {number}"))
.unwrap()
}
#[template]
#[rstest]
#[case(parsed("+80012340000"), None, Type::TollFree)]
#[case(parsed("+61406823897"), Some(AU), Type::Mobile)]
#[case(parsed("+611900123456"), Some(AU), Type::PremiumRate)]
#[case(parsed("+32474091150"), Some(BE), Type::Mobile)]
#[case(parsed("+34666777888"), Some(ES), Type::Mobile)]
#[case(parsed("+34612345678"), Some(ES), Type::Mobile)]
#[case(parsed("+441212345678"), Some(GB), Type::FixedLine)]
#[case(parsed("+13459492311"), Some(KY), Type::FixedLine)]
#[case(parsed("+16137827274"), Some(CA), Type::FixedLineOrMobile)]
#[case(parsed("+1 520 878 2491"), Some(US), Type::FixedLineOrMobile)]
#[case(parsed("+1-520-878-2491"), Some(US), Type::FixedLineOrMobile)]
fn phone_numbers(
#[case] number: PhoneNumber,
#[case] country: Option<country::Id>,
#[case] r#type: Type,
) {
}
#[apply(phone_numbers)]
fn country_id(
#[case] number: PhoneNumber,
#[case] country: Option<country::Id>,
#[case] _type: Type,
) -> anyhow::Result<()> {
assert_eq!(country, number.country().id());
Ok(())
}
#[apply(phone_numbers)]
#[ignore]
fn round_trip_parsing(
#[case] number: PhoneNumber,
#[case] country: Option<country::Id>,
#[case] _type: Type,
#[values(Mode::International, Mode::E164, Mode::Rfc3966, Mode::National)] mode: Mode,
) -> anyhow::Result<()> {
let country_hint = if mode == Mode::National {
country
} else {
None
};
let formatted = number.format().mode(mode).to_string();
let parsed = parser::parse(country_hint, &formatted).with_context(|| {
format!("parsing {number} after formatting in {mode:?} mode as {formatted}")
})?;
assert_eq!(number, parsed);
Ok(())
}
#[apply(phone_numbers)]
fn number_type(
#[case] number: PhoneNumber,
#[case] _country: Option<country::Id>,
#[case] r#type: Type,
) {
assert_eq!(r#type, number.number_type(&DATABASE));
}
}