diff --git a/README.md b/README.md index 54f9f65..7c451e9 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ A set of helpers to sanitize, convert or transform information related to Persia | Persian Content Checks | ✅ Done | | Numeric Ordinal Suffixes | ✅ Done | | Phone Number | ✅ Done | -| Regions (Province & City) | 🚧 In Progress | -| Bank-related Helper Functions | 🚧 In Progress | +| Bank-related Helper Functions | ✅ Done | | Date Conversion | ✅ Done | +| Regions (Province & City) | 🚧 In Progress | --- diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 0ae24ac..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -# merge_imports = true -# space_after_colon = true diff --git a/src/banking/card_number_extractor.rs b/src/banking/card_number_extractor.rs new file mode 100644 index 0000000..fa98b07 --- /dev/null +++ b/src/banking/card_number_extractor.rs @@ -0,0 +1,281 @@ +use super::Banking; +use crate::{digit::Digit, utils::impl_trait_for_string_types}; + +/// Card number information +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CardNumber { + /// Base card-number + pub base: String, + /// Card-number without any extra character + pub pure: String, + /// Start Index of card-number (based on chars) + pub index: usize, + /// Card-numbers is valid or not + pub is_valid: Option, + /// bank name of card-number + // TODO(saeid): maybe make this `Option>`? + pub bank_name: Option<&'static str>, +} + +/// Extract card number options +#[derive(Clone, Copy, Debug)] +pub struct ExtractCardNumberOptions { + /// Check if every card-numbers is valid or not + pub check_validation: bool, + /// Detect Bank's name by extracted card-number + pub detect_bank_name: bool, + /// Return list of only valid card-numbers + pub filter_valid_card_numbers: bool, +} + +impl ExtractCardNumberOptions { + /// Enable all `check_validation`, `detect_bank_name` and `filter_valid_card_numbers` options. + pub fn all() -> Self { + Self { + check_validation: true, + detect_bank_name: true, + filter_valid_card_numbers: true, + } + } + + /// Disable all options. + pub fn none() -> Self { + Self { + check_validation: false, + detect_bank_name: false, + filter_valid_card_numbers: false, + } + } +} + +impl Default for ExtractCardNumberOptions { + fn default() -> Self { + Self { + check_validation: true, + detect_bank_name: false, + filter_valid_card_numbers: true, + } + } +} + +pub trait ExtractCardNumber: AsRef { + /// Extract all the card numbers. + fn extract_card_numbers(&self, options: ExtractCardNumberOptions) -> Vec { + let digits = self.as_ref(); + let mut result = Vec::new(); + + let mut len = 0; + let mut base = String::with_capacity(20); + let mut pure = String::with_capacity(16); + for c in digits.chars() { + match CharType::new(&c) { + CharType::Digit => { + base.push(c); + pure.push(c); + len += 1; + + // a valid iranian card-number have 16 digits + if len == 16 { + if pure.have_non_en_digit() { + // if there is any non english digit replace them with english digits + pure = pure.digits_to_en(); + } + + result.push(CardNumber { + base: base.clone(), + is_valid: options + .check_validation + .then(|| pure.is_valid_bank_card_number()), + bank_name: if options.detect_bank_name { + pure.get_bank_name_from_card_number() + } else { + None + }, + pure: pure.clone(), + index: result.len() + 1, + }); + // clear buffers and len after we pushed the information to result + base.clear(); + pure.clear(); + len = 0; + } + } + CharType::Seperator => base.push(c), + CharType::Other => { + // clear buffers and len in case of unsupported character + base.clear(); + pure.clear(); + len = 0; + } + } + } + + if options.filter_valid_card_numbers { + result.retain(|c| c.is_valid.unwrap_or(true)); + } + + result + } +} + +enum CharType { + Digit, + Seperator, + Other, +} + +impl CharType { + fn new(c: &char) -> Self { + match c { + '0'..='9' | '\u{0660}'..='\u{0669}' | '\u{06F0}'..='\u{06F9}' => Self::Digit, + '-' | '_' | '*' | '.' => Self::Seperator, + _ => Self::Other, + } + } +} + +impl_trait_for_string_types!(ExtractCardNumber); + +#[cfg(test)] +mod test { + use super::*; + + macro_rules! create_card { + ($base:literal, $pure:literal, $index:literal) => { + CardNumber { + base: $base.to_owned(), + pure: $pure.to_owned(), + index: $index, + is_valid: None, + bank_name: None, + } + }; + ($base:literal, $pure:literal, $index:literal, $is_valid:literal) => { + CardNumber { + base: $base.to_owned(), + pure: $pure.to_owned(), + index: $index, + is_valid: Some($is_valid), + bank_name: None, + } + }; + ($base:literal, $pure:literal, $index:literal, $is_valid:literal, $bank_name:literal) => { + CardNumber { + base: $base.to_owned(), + pure: $pure.to_owned(), + index: $index, + is_valid: Some($is_valid), + bank_name: Some($bank_name), + } + }; + } + + #[test] + fn extract_card_number_test() { + // Should find and extract 4 Card Numbers + let string = r#"شماره کارتم رو برات نوشتم: + 6219-8610-3452-9007 + اینم یه شماره کارت دیگه ای که دارم + 5022291070873466 + ۵۰۲۲۲۹۱۰۸۱۸۷۳۴۶۶ + ۵۰۲۲-۲۹۱۰-۷۰۸۷-۳۴۶۶"#; + + // Should find and extract 4 Card Numbers + let cards = vec![ + create_card!("6219-8610-3452-9007", "6219861034529007", 1), + create_card!("5022291070873466", "5022291070873466", 2), + create_card!("۵۰۲۲۲۹۱۰۸۱۸۷۳۴۶۶", "5022291081873466", 3), + create_card!("۵۰۲۲-۲۹۱۰-۷۰۸۷-۳۴۶۶", "5022291070873466", 4), + ]; + + assert_eq!( + cards, + string.extract_card_numbers(ExtractCardNumberOptions { + check_validation: false, + ..Default::default() + }) + ); + + // Should find and format the Card-Number into Text that includes Persian & English digits + { + let string = "شماره کارتم رو برات نوشتم: ۵۰۲۲-2910-7۰۸۷-۳۴۶۶"; + + let card = create_card!("۵۰۲۲-2910-7۰۸۷-۳۴۶۶", "5022291070873466", 1); + + assert_eq!( + vec![card], + string.extract_card_numbers(ExtractCardNumberOptions { + check_validation: false, + ..Default::default() + }) + ); + } + + // Should validate extract card-numbers + let cards = vec![ + create_card!("6219-8610-3452-9007", "6219861034529007", 1, true), + create_card!("5022291070873466", "5022291070873466", 2, true), + create_card!("۵۰۲۲۲۹۱۰۸۱۸۷۳۴۶۶", "5022291081873466", 3, false), + create_card!("۵۰۲۲-۲۹۱۰-۷۰۸۷-۳۴۶۶", "5022291070873466", 4, true), + ]; + + assert_eq!( + cards, + string.extract_card_numbers(ExtractCardNumberOptions { + check_validation: true, + filter_valid_card_numbers: false, + ..Default::default() + }) + ); + + // Should return only valid card-numbers + let cards = vec![ + create_card!("6219-8610-3452-9007", "6219861034529007", 1, true), + create_card!("5022291070873466", "5022291070873466", 2, true), + create_card!("۵۰۲۲-۲۹۱۰-۷۰۸۷-۳۴۶۶", "5022291070873466", 4, true), + ]; + + assert_eq!( + cards, + string.extract_card_numbers(ExtractCardNumberOptions { + check_validation: true, + filter_valid_card_numbers: true, + ..Default::default() + }) + ); + + // Should detect Banks number for valid card-numbers + let cards = vec![ + create_card!( + "6219-8610-3452-9007", + "6219861034529007", + 1, + true, + "بانک سامان" + ), + create_card!( + "5022291070873466", + "5022291070873466", + 2, + true, + "بانک پاسارگاد" + ), + create_card!( + "۵۰۲۲-۲۹۱۰-۷۰۸۷-۳۴۶۶", + "5022291070873466", + 4, + true, + "بانک پاسارگاد" + ), + ]; + + assert_eq!( + cards, + string.extract_card_numbers(ExtractCardNumberOptions { + check_validation: true, + filter_valid_card_numbers: true, + detect_bank_name: true + }) + ); + } +} diff --git a/src/banking/mod.rs b/src/banking/mod.rs index 9cf23b7..0a97dc2 100644 --- a/src/banking/mod.rs +++ b/src/banking/mod.rs @@ -2,8 +2,9 @@ use crate::banking::bank_codes_table::BANK_CODE_TABLE; use crate::utils::impl_trait_for_string_types; pub mod bank_codes_table; +pub mod card_number_extractor; pub mod sheba; -pub mod sheba_table; +mod sheba_table; /// Set of helpers for the banking system of Iran. pub trait Banking: AsRef { @@ -35,7 +36,7 @@ pub trait Banking: AsRef { } /// Get the bank name from card number. - fn get_bank_name_from_card_number(&self) -> Option<&str> { + fn get_bank_name_from_card_number(&self) -> Option<&'static str> { let number = self.as_ref(); number.is_valid_bank_card_number().then(|| { BANK_CODE_TABLE diff --git a/src/banking/sheba.rs b/src/banking/sheba.rs index 45129c6..b5e5e63 100644 --- a/src/banking/sheba.rs +++ b/src/banking/sheba.rs @@ -7,11 +7,15 @@ pub trait ShebaNumber: AsRef { self.iso_7064_mod_97_10().is_ok_and(|i| i == 1) } - fn sheba_code_info(&self) -> Option<&ShebaInfo> { + fn get_sheba_info(&self) -> Option { if !self.is_valid_sheba_code() { return None; } - SHEBA_CODE_TABLE.get(&&self.as_ref()[4..7]) + + let digits = self.as_ref(); + SHEBA_CODE_TABLE + .get(&&digits[4..7]) + .map(|sc| sc.process(digits)) } fn iso_7064_mod_97_10(&self) -> crate::Result { @@ -45,6 +49,10 @@ impl_trait_for_string_types!(ShebaNumber); #[cfg(test)] mod sheba_code { + use crate::banking::sheba_table::{ + process_parsian, process_pasargad, process_shahr, ShebaAccountNumber, + }; + use super::*; #[test] @@ -57,6 +65,55 @@ mod sheba_code { #[test] fn sheba_code_info() { - assert!("IR210180000000009190404878".sheba_code_info().is_some()) + assert!("IR210180000000009190404878".get_sheba_info().is_some()); + assert!("IR012345678901234567890123".get_sheba_info().is_none()); + assert!("IR012345678A01234567890123".get_sheba_info().is_none()); + + let sheba_info_pasargad = ShebaInfo { + code: "057", + nickname: "pasargad", + name: "Pasargad Bank", + persian_name: "بانک پاسارگاد", + account_number: Some(ShebaAccountNumber { + normal: "220800134473701".to_owned(), + formatted: "220-800-13447370-1".to_owned(), + }), + process: Some(process_pasargad), + }; + let sheba_info_shahr = ShebaInfo { + code: "061", + nickname: "shahr", + name: "City Bank", + persian_name: "بانک شهر", + account_number: Some(ShebaAccountNumber { + normal: "700796858044".to_owned(), + formatted: "700796858044".to_owned(), + }), + process: Some(process_shahr), + }; + let sheba_info_parsian = ShebaInfo { + code: "054", + nickname: "parsian", + name: "Parsian Bank", + persian_name: "بانک پارسیان", + account_number: Some(ShebaAccountNumber { + normal: "020817909002".to_owned(), + formatted: "002-00817909-002".to_owned(), + }), + process: Some(process_parsian), + }; + + assert_eq!( + "IR550570022080013447370101".get_sheba_info(), + Some(sheba_info_pasargad) + ); + assert_eq!( + "IR790610000000700796858044".get_sheba_info(), + Some(sheba_info_shahr) + ); + assert_eq!( + "IR820540102680020817909002".get_sheba_info(), + Some(sheba_info_parsian) + ); } } diff --git a/src/banking/sheba_table.rs b/src/banking/sheba_table.rs index ca93dea..7e1d5e6 100644 --- a/src/banking/sheba_table.rs +++ b/src/banking/sheba_table.rs @@ -1,240 +1,379 @@ use crate::utils::{create_fixed_map, FixedMap}; -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ShebaAccountNumber { + pub normal: String, + pub formatted: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ShebaInfo { pub code: &'static str, pub nickname: &'static str, pub name: &'static str, pub persian_name: &'static str, + pub account_number: Option, + pub process: Option, +} + +impl ShebaInfo { + /// Process the sheba if needed and return a newly created [`ShebaInfo`]. + pub fn process(&self, sheba_code: &str) -> ShebaInfo { + let mut sheba_clone = self.clone(); + if let Some(process) = self.process { + process(&mut sheba_clone, sheba_code); + } + sheba_clone + } } pub static SHEBA_CODE_TABLE: FixedMap<&str, ShebaInfo> = create_fixed_map! { "010" => ShebaInfo{ + code: "010", nickname: "central-bank", name: "Central Bank of Iran", persian_name: "بانک مرکزی جمهوری اسلامی ایران", - code: "010", + account_number: None, + process: None, }, "011" => ShebaInfo{ + code: "011", nickname: "sanat-o-madan", name: "Sanat O Madan Bank", persian_name: "بانک صنعت و معدن", - code: "011", + account_number: None, + process: None, }, "012" => ShebaInfo{ + code: "012", nickname: "mellat", name: "Mellat Bank", persian_name: "بانک ملت", - code: "012", + account_number: None, + process: None, }, "013" => ShebaInfo{ + code: "013", nickname: "refah", name: "Refah Bank", persian_name: "بانک رفاه کارگران", - code: "013", + account_number: None, + process: None, }, "014" => ShebaInfo{ + code: "014", nickname: "maskan", name: "Maskan Bank", persian_name: "بانک مسکن", - code: "014", + account_number: None, + process: None, }, "015" => ShebaInfo{ + code: "015", nickname: "sepah", name: "Sepah Bank", persian_name: "بانک سپه", - code: "015", + account_number: None, + process: None, }, "016" => ShebaInfo{ + code: "016", nickname: "keshavarzi", name: "Keshavarzi", persian_name: "بانک کشاورزی", - code: "016", + account_number: None, + process: None, }, "017" => ShebaInfo{ + code: "017", nickname: "melli", name: "Melli", persian_name: "بانک ملی ایران", - code: "017", + account_number: None, + process: None, }, "018" => ShebaInfo{ + code: "018", nickname: "tejarat", name: "Tejarat Bank", persian_name: "بانک تجارت", - code: "018", + account_number: None, + process: None, }, "019" => ShebaInfo{ + code: "019", nickname: "saderat", name: "Saderat Bank", persian_name: "بانک صادرات ایران", - code: "019", + account_number: None, + process: None, }, "020" => ShebaInfo{ + code: "020", nickname: "tosee-saderat", name: "Tose Saderat Bank", persian_name: "بانک توسعه صادرات", - code: "020", + account_number: None, + process: None, }, "021" => ShebaInfo{ + code: "021", nickname: "post", name: "Post Bank", persian_name: "پست بانک ایران", - code: "021", + account_number: None, + process: None, }, "022" => ShebaInfo{ + code: "022", nickname: "toose-taavon", name: "Tosee Taavon Bank", persian_name: "بانک توسعه تعاون", - code: "022", + account_number: None, + process: None, }, "051" => ShebaInfo{ + code: "051", nickname: "tosee", name: "Tosee Bank", persian_name: "موسسه اعتباری توسعه", - code: "051", + account_number: None, + process: None, }, "052" => ShebaInfo{ + code: "052", nickname: "ghavamin", name: "Ghavamin Bank", persian_name: "بانک قوامین", - code: "052", + account_number: None, + process: None, }, "053" => ShebaInfo{ + code: "053", nickname: "karafarin", name: "Karafarin Bank", persian_name: "بانک کارآفرین", - code: "053", + account_number: None, + process: None, }, "054" => ShebaInfo{ + code: "054", nickname: "parsian", name: "Parsian Bank", persian_name: "بانک پارسیان", - code: "054", + account_number: None, + process: Some(process_parsian), }, "055" => ShebaInfo{ + code: "055", nickname: "eghtesad-novin", name: "Eghtesad Novin Bank", persian_name: "بانک اقتصاد نوین", - code: "055", + account_number: None, + process: None, }, "056" => ShebaInfo{ + code: "056", nickname: "saman", name: "Saman Bank", persian_name: "بانک سامان", - code: "056", + account_number: None, + process: None, }, "057" => ShebaInfo{ + code: "057", nickname: "pasargad", name: "Pasargad Bank", persian_name: "بانک پاسارگاد", - code: "057", + account_number: None, + process: Some(process_pasargad), }, "058" => ShebaInfo{ + code: "058", nickname: "sarmayeh", name: "Sarmayeh Bank", persian_name: "بانک سرمایه", - code: "058", + account_number: None, + process: None, }, "059" => ShebaInfo{ + code: "059", nickname: "sina", name: "Sina Bank", persian_name: "بانک سینا", - code: "059", + account_number: None, + process: None, }, "060" => ShebaInfo{ + code: "060", nickname: "mehr-iran", name: "Mehr Iran Bank", persian_name: "بانک مهر ایران", - code: "060", + account_number: None, + process: None, }, "061" => ShebaInfo{ + code: "061", nickname: "shahr", name: "City Bank", persian_name: "بانک شهر", - code: "061", + account_number: None, + process: Some(process_shahr), }, "062" => ShebaInfo{ + code: "062", nickname: "ayandeh", name: "Ayandeh Bank", persian_name: "بانک آینده", - code: "062", + account_number: None, + process: None, }, "063" => ShebaInfo{ + code: "063", nickname: "ansar", name: "Ansar Bank", persian_name: "بانک انصار", - code: "063", + account_number: None, + process: None, }, "064" => ShebaInfo{ + code: "064", nickname: "gardeshgari", name: "Gardeshgari Bank", persian_name: "بانک گردشگری", - code: "064", + account_number: None, + process: None, }, "065" => ShebaInfo{ + code: "065", nickname: "hekmat-iranian", name: "Hekmat Iranian Bank", persian_name: "بانک حکمت ایرانیان", - code: "065", + account_number: None, + process: None, }, "066" => ShebaInfo{ + code: "066", nickname: "dey", name: "Dey Bank", persian_name: "بانک دی", - code: "066", + account_number: None, + process: None, }, "069" => ShebaInfo{ + code: "069", nickname: "iran-zamin", name: "Iran Zamin Bank", persian_name: "بانک ایران زمین", - code: "069", + account_number: None, + process: None, }, "070" => ShebaInfo{ + code: "070", nickname: "resalat", name: "Resalat Bank", persian_name: "بانک قرض الحسنه رسالت", - code: "070", + account_number: None, + process: None, }, "073" => ShebaInfo{ + code: "073", nickname: "kosar", name: "Kosar Credit Institute", persian_name: "موسسه اعتباری کوثر", - code: "073", + account_number: None, + process: None, }, "075" => ShebaInfo{ + code: "075", nickname: "melal", name: "Melal Credit Institute", persian_name: "موسسه اعتباری ملل", - code: "075", + account_number: None, + process: None, }, "078" => ShebaInfo{ + code: "078", nickname: "middle-east-bank", name: "Middle East Bank", persian_name: "بانک خاورمیانه", - code: "078", + account_number: None, + process: None, }, "080" => ShebaInfo{ + code: "080", nickname: "noor-bank", name: "Noor Credit Institution", persian_name: "موسسه اعتباری نور", - code: "080", + account_number: None, + process: None, }, "079" => ShebaInfo{ + code: "079", nickname: "mehr-eqtesad", name: "Mehr Eqtesad Bank", persian_name: "بانک مهر اقتصاد", - code: "079", + account_number: None, + process: None, }, "090" => ShebaInfo{ + code: "090", nickname: "mehr-iran", name: "Mehr Iran Bank", persian_name: "بانک مهر ایران", - code: "090", + account_number: None, + process: None, }, "095" => ShebaInfo{ + code: "095", nickname: "iran-venezuela", name: "Iran and Venezuela Bank", persian_name: "بانک ایران و ونزوئلا", - code: "095", + account_number: None, + process: None, }, }; + +pub(super) fn process_parsian(sheba: &mut ShebaInfo, sheba_code: &str) { + let substr = &sheba_code[14..]; + sheba.account_number = Some(ShebaAccountNumber { + normal: substr.to_owned(), + formatted: format!("0{}-0{}-{}", &substr[0..2], &substr[2..9], &substr[9..12]), + }); +} + +pub(super) fn process_pasargad(sheba: &mut ShebaInfo, sheba_code: &str) { + let mut idx = 7; + for ch in sheba_code[7..].chars() { + if ch != '0' { + break; + } + idx += 1; + } + let substr = &sheba_code[idx..sheba_code.len() - 2]; + sheba.account_number = Some(ShebaAccountNumber { + normal: substr.to_owned(), + formatted: format!( + "{}-{}-{}-{}", + &substr[0..3], + &substr[3..6], + &substr[6..14], + &substr[14..15] + ), + }); +} + +pub(super) fn process_shahr(sheba: &mut ShebaInfo, sheba_code: &str) { + let mut idx = 7; + for ch in sheba_code[7..].chars() { + if ch != '0' { + break; + } + idx += 1; + } + let substr = &sheba_code[idx..]; + sheba.account_number = Some(ShebaAccountNumber { + normal: substr.to_owned(), + formatted: substr.to_owned(), + }); +} diff --git a/src/digit/mod.rs b/src/digit/mod.rs index 0694f14..56f3864 100644 --- a/src/digit/mod.rs +++ b/src/digit/mod.rs @@ -4,16 +4,16 @@ use std::convert::TryFrom; /// Supported language variants. #[derive(Clone, Copy)] pub enum Lang { - Fa, En, + Fa, Ar, } /// Set of helpers to manipulate Persian (or Arabic!) digits. pub trait Digit: AsRef { const DIGITS: [[char; 10]; 3] = [ - ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], ]; @@ -55,6 +55,20 @@ pub trait Digit: AsRef { fn digits_ar_to_en(&self) -> String { self.digits_convert(Lang::Ar, Lang::En) } + + /// Takes a string that may contain Persian or Arabic digits, and returns + /// a string that represents the same digits but in English. + fn digits_to_en(&self) -> String { + self.digits_convert(Lang::Ar, Lang::En) + .digits_convert(Lang::Fa, Lang::En) + } + + /// Check if the string have any non english (arabic, persian) digits. + fn have_non_en_digit(&self) -> bool { + self.as_ref() + .chars() + .any(|c| Self::DIGITS.iter().skip(1).any(|d| d.contains(&c))) + } } impl_trait_for_string_types!(Digit); diff --git a/src/persian_content/mod.rs b/src/persian_content/mod.rs index 1e215bc..989e7df 100644 --- a/src/persian_content/mod.rs +++ b/src/persian_content/mod.rs @@ -22,18 +22,18 @@ pub trait PersianContent: AsRef { /// Calculates how much of the text is in Persian Alphabet. /// It doesn't count the numbers and other non-alphabetical chars like " « , ، fn persian_percentage(&self) -> u8 { - let (persian_chars_len, len) = self - .as_ref() - .chars() - .fold((0u32, 0u32), |(mut pc, mut len), c| { - if c.is_alphabetic() { - if HAS_PERSIAN_CHAR.contains(&c) { - pc += 1; + let (persian_chars_len, len) = + self.as_ref() + .chars() + .fold((0u32, 0u32), |(mut pc, mut len), c| { + if c.is_alphabetic() { + if HAS_PERSIAN_CHAR.contains(&c) { + pc += 1; + } + len += 1; } - len += 1; - } - (pc, len) - }); + (pc, len) + }); (persian_chars_len * 100).checked_div(len).unwrap_or(100) as u8 } diff --git a/src/phone_number/landline.rs b/src/phone_number/landline.rs index 3c9522e..8f99996 100644 --- a/src/phone_number/landline.rs +++ b/src/phone_number/landline.rs @@ -18,7 +18,8 @@ pub trait LandlineNumber: AsRef { let mut chars = text.chars().skip(skip); - chars.by_ref().take(2).all(|c| ('1'..='9').contains(&c)) && chars.all(|c| c.is_ascii_digit()) + chars.by_ref().take(2).all(|c| ('1'..='9').contains(&c)) + && chars.all(|c| c.is_ascii_digit()) } /// Get three-digit prefix of a landline number. diff --git a/src/phone_number/mobile.rs b/src/phone_number/mobile.rs index e17e777..4a9cabb 100644 --- a/src/phone_number/mobile.rs +++ b/src/phone_number/mobile.rs @@ -98,10 +98,14 @@ pub trait MobileNumber: AsRef { let number = format!("0{}", &text[skip..]); - IRAN_MOBILE_OPERATORS.iter().find_map(|(k, v)| { - v.iter().any(|x| x == &&number[..x.len()]).then(|| IranMobileOperator::from_str(k).unwrap()) - }) - .ok_or("Can't find the operator".into()) + IRAN_MOBILE_OPERATORS + .iter() + .find_map(|(k, v)| { + v.iter() + .any(|x| x == &&number[..x.len()]) + .then(|| IranMobileOperator::from_str(k).unwrap()) + }) + .ok_or("Can't find the operator".into()) } }