diff --git a/src/formatter.rs b/src/formatter.rs index 999aa0e..6f46535 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -265,6 +265,45 @@ mod test { use crate::formatter::Mode; use crate::parser; + #[test] + fn fr_leading_zero() { + assert_eq!( + "06 31 96 65 43", + parser::parse(Some(country::FR), "+330631966543") + .unwrap() + .format() + .mode(Mode::National) + .to_string() + ); + + assert_eq!( + "+33 6 31 96 65 43", + parser::parse(Some(country::FR), "+330631966543") + .unwrap() + .format() + .mode(Mode::International) + .to_string() + ); + + assert_eq!( + "+33631966543", + parser::parse(Some(country::FR), "+330631966543") + .unwrap() + .format() + .mode(Mode::E164) + .to_string() + ); + + assert_eq!( + "tel:+33-6-31-96-65-43", + parser::parse(Some(country::FR), "+330631966543") + .unwrap() + .format() + .mode(Mode::Rfc3966) + .to_string() + ); + } + #[test] fn us() { assert_eq!( diff --git a/src/national_number.rs b/src/national_number.rs index 20c7739..d061926 100644 --- a/src/national_number.rs +++ b/src/national_number.rs @@ -13,7 +13,7 @@ // limitations under the License. use serde_derive::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, str::FromStr}; /// The national number part of a phone number. #[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)] @@ -21,6 +21,17 @@ pub struct NationalNumber { value: u64, } +impl FromStr for NationalNumber { + type Err = crate::error::Parse; + + fn from_str(s: &str) -> Result { + let zeros = s.chars().take_while(|&c| c == '0').count(); + let value = s[zeros..].parse::()?; + + NationalNumber::new(value, zeros as u8) + } +} + impl NationalNumber { pub fn new(value: u64, zeros: u8) -> Result { // E.164 specifies a maximum of 15 decimals, which corresponds to slightly over 48.9 bits. diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 85d0fd6..95833e3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18,7 +18,6 @@ use crate::country; use crate::error; use crate::extension::Extension; use crate::metadata::{Database, DATABASE}; -use crate::national_number::NationalNumber; use crate::phone_number::{PhoneNumber, Type}; use crate::validator::{self, Validation}; use nom::{branch::alt, IResult}; @@ -53,8 +52,27 @@ pub fn parse_with>( // Normalize the number and extract country code. number = helper::country_code(database, country, number)?; + // If no country was supplied, try to determine the metadata from the number. + // We need to determine the country to be able to classify the national prefix if present. + let meta = if let Some(country) = &country { + database.by_id(country.as_ref()) + } else { + let code = country::Code { + value: number.prefix.clone().map(|p| p.parse()).unwrap_or(Ok(0))?, + source: number.country, + }; + + use either::{Left, Right}; + let without_zeros = number.national.trim_start_matches('0'); + match validator::source_for(database, code.value(), without_zeros) { + Some(Left(region)) => database.by_id(region.as_ref()), + Some(Right(code)) => database.by_code(&code).and_then(|m| m.into_iter().next()), + None => None, + } + }; + // Extract carrier and strip national prefix if present. - if let Some(meta) = country.and_then(|c| database.by_id(c.as_ref())) { + if let Some(meta) = meta { let mut potential = helper::national_number(meta, number.clone()); // Strip national prefix if present. @@ -83,10 +101,7 @@ pub fn parse_with>( source: number.country, }, - national: NationalNumber::new( - number.national.parse()?, - number.national.chars().take_while(|&c| c == '0').count() as u8, - )?, + national: number.national.parse()?, extension: number.extension.map(|s| Extension(s.into_owned())), carrier: number.carrier.map(|s| Carrier(s.into_owned())), @@ -95,18 +110,44 @@ pub fn parse_with>( #[cfg(test)] mod test { - use crate::country; + use crate::country::{self, Source}; + use crate::metadata::DATABASE; use crate::national_number::NationalNumber; use crate::parser; use crate::phone_number::PhoneNumber; - - #[test] - fn parse() { - let mut number = PhoneNumber { - code: country::Code { - value: 64, - source: country::Source::Default, - }, + use rstest::*; + + #[rstest] + #[case(Source::Default, country::NZ, "033316005")] + #[case(Source::Default, country::NZ, "33316005")] + #[case(Source::Default, country::NZ, "03-331 6005")] + #[case(Source::Default, country::NZ, "03 331 6005")] + #[case(Source::Plus, country::NZ, "tel:03-331-6005;phone-context=+64")] + // FIXME: What the fuck is this. + // #[case(Source::Plus, country::NZ, "tel:331-6005;phone-context=+64-3")] + #[case(Source::Plus, country::NZ, "tel:03-331-6005;phone-context=+64;a=%A1")] + #[case( + Source::Plus, + country::NZ, + "tel:03-331-6005;isub=12345;phone-context=+64" + )] + #[case(Source::Plus, country::NZ, "tel:+64-3-331-6005;isub=12345")] + #[case(Source::Plus, country::NZ, "03-331-6005;phone-context=+64")] + // Idd + #[case(Source::Idd, country::NZ, "0064 3 331 6005")] + #[case(Source::Idd, country::US, "01164 3 331 6005")] + // Plus + #[case(Source::Plus, country::US, "+64 3 331 6005")] + #[case(Source::Plus, country::US, "+01164 3 331 6005")] + #[case(Source::Plus, country::NZ, "+0064 3 331 6005")] + #[case(Source::Plus, country::NZ, "+ 00 64 3 331 6005")] + fn parse_1( + #[case] source: Source, + #[case] country: impl Into>, + #[case] number: &'static str, + ) { + let reference = PhoneNumber { + code: country::Code { value: 64, source }, national: NationalNumber::new(33316005, 0).unwrap(), @@ -114,99 +155,35 @@ mod test { carrier: None, }; - number.code.source = country::Source::Default; - assert_eq!( - number, - parser::parse(Some(country::NZ), "033316005").unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::NZ), "33316005").unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::NZ), "03-331 6005").unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::NZ), "03 331 6005").unwrap() - ); + let country = country.into(); + println!("parsing {} with country {:?}", number, country); + let parsed = parser::parse(country, number).unwrap(); + println!("number type: {:?}", parsed.number_type(&DATABASE)); + println!("parsed: {:?}", parsed); - number.code.source = country::Source::Plus; - assert_eq!( - number, - parser::parse(Some(country::NZ), "tel:03-331-6005;phone-context=+64").unwrap() - ); - // FIXME: What the fuck is this. - // assert_eq!(number, parser::parse(Some(country::NZ), "tel:331-6005;phone-context=+64-3").unwrap()); - // assert_eq!(number, parser::parse(Some(country::NZ), "tel:331-6005;phone-context=+64-3").unwrap()); - assert_eq!( - number, - parser::parse(Some(country::NZ), "tel:03-331-6005;phone-context=+64;a=%A1").unwrap() - ); - assert_eq!( - number, - parser::parse( - Some(country::NZ), - "tel:03-331-6005;isub=12345;phone-context=+64" - ) - .unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::NZ), "tel:+64-3-331-6005;isub=12345").unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::NZ), "03-331-6005;phone-context=+64").unwrap() - ); - - number.code.source = country::Source::Idd; - assert_eq!( - number, - parser::parse(Some(country::NZ), "0064 3 331 6005").unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::US), "01164 3 331 6005").unwrap() - ); + assert_eq!(reference, parsed); + } - number.code.source = country::Source::Plus; + #[test] + fn parse_2() { assert_eq!( - number, - parser::parse(Some(country::US), "+64 3 331 6005").unwrap() - ); + PhoneNumber { + code: country::Code { + value: 64, + source: Source::Number, + }, - assert_eq!( - number, - parser::parse(Some(country::US), "+01164 3 331 6005").unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::NZ), "+0064 3 331 6005").unwrap() - ); - assert_eq!( - number, - parser::parse(Some(country::NZ), "+ 00 64 3 331 6005").unwrap() - ); + national: NationalNumber::new(64123456, 0).unwrap(), - let number = PhoneNumber { - code: country::Code { - value: 64, - source: country::Source::Number, + extension: None, + carrier: None, }, - - national: NationalNumber::new(64123456, 0).unwrap(), - - extension: None, - carrier: None, - }; - - assert_eq!( - number, parser::parse(Some(country::NZ), "64(0)64123456").unwrap() ); + } + #[test] + fn parse_3() { assert_eq!( PhoneNumber { code: country::Code { @@ -221,7 +198,10 @@ mod test { }, parser::parse(Some(country::DE), "301/23456").unwrap() ); + } + #[test] + fn parse_4() { assert_eq!( PhoneNumber { code: country::Code { @@ -236,7 +216,10 @@ mod test { }, parser::parse(Some(country::JP), "+81 *2345").unwrap() ); + } + #[test] + fn parse_5() { assert_eq!( PhoneNumber { code: country::Code { @@ -251,7 +234,10 @@ mod test { }, parser::parse(Some(country::NZ), "12").unwrap() ); + } + #[test] + fn parse_6() { assert_eq!( PhoneNumber { code: country::Code { diff --git a/src/validator.rs b/src/validator.rs index 51b6fe0..a9a75ec 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -311,61 +311,49 @@ pub fn number_type(meta: &Metadata, value: &str) -> Type { #[cfg(test)] mod test { use crate::country; + use crate::metadata::DATABASE; use crate::parser; use crate::validator; + use rstest::*; + use rstest_reuse::*; + + #[template] + #[rstest] + #[case(country::US, "+1 6502530000", true)] + #[case(country::IT, "+39 0236618300", true)] + #[case(country::FR, "+330631966543", true)] + #[case(None, "+330631966543", true)] + #[case(country::GB, "+44 7912345678", true)] + #[case(None, "+800 12345678", true)] + #[case(None, "+979 123456789", true)] + #[case(None, "+64 21387835", true)] + #[case(None, "+1 2530000", false)] + // #[case(None, "+39 023661830000", false)] + #[case(None, "+44 791234567", false)] + #[case(None, "+49 1234", false)] + #[case(None, "+64 3316005", false)] + #[case(None, "+3923 2366", false)] + #[case(None, "+800 123456789", false)] + // Case for https://github.com/whisperfish/rust-phonenumber/issues/31 + // #[case(None, "+97233142764978", false)] + fn phone_numbers( + #[case] country: impl Into>, + #[case] number: &'static str, + #[case] valid: bool, + ) { + } - #[test] - fn validate() { - assert!(validator::is_valid( - &parser::parse(Some(country::US), "+1 6502530000").unwrap() - )); - - assert!(validator::is_valid( - &parser::parse(Some(country::IT), "+39 0236618300").unwrap() - )); - - assert!(validator::is_valid( - &parser::parse(Some(country::GB), "+44 7912345678").unwrap() - )); - - assert!(validator::is_valid( - &parser::parse(None, "+800 12345678").unwrap() - )); - - assert!(validator::is_valid( - &parser::parse(None, "+979 123456789").unwrap() - )); - - assert!(validator::is_valid( - &parser::parse(None, "+64 21387835").unwrap() - )); - - assert!(!validator::is_valid( - &parser::parse(None, "+1 2530000").unwrap() - )); - - // assert!(!validator::is_valid( - // &parser::parse(None, "+39 023661830000").unwrap() - // )); - - assert!(!validator::is_valid( - &parser::parse(None, "+44 791234567").unwrap() - )); - - assert!(!validator::is_valid( - &parser::parse(None, "+49 1234").unwrap() - )); - - assert!(!validator::is_valid( - &parser::parse(None, "+64 3316005").unwrap() - )); - - assert!(!validator::is_valid( - &parser::parse(None, "+3923 2366").unwrap() - )); - - assert!(!validator::is_valid( - &parser::parse(None, "+800 123456789").unwrap() - )); + #[apply(phone_numbers)] + fn validate( + #[case] country: impl Into>, + #[case] number: &'static str, + #[case] valid: bool, + ) { + let country = country.into(); + println!("parsing {} with country {:?}", number, country); + let parsed = parser::parse(country, number).unwrap(); + println!("number type: {:?}", parsed.number_type(&DATABASE)); + println!("parsed: {:?}", parsed); + assert!(validator::is_valid(&parsed) == valid); } }