Skip to content

Commit

Permalink
Merge pull request #20 from thevilx/improvePhoneNumber
Browse files Browse the repository at this point in the history
Add thiserror to phone_number module
  • Loading branch information
ali77gh authored Dec 24, 2023
2 parents adbfa24 + 5b19052 commit c62f961
Show file tree
Hide file tree
Showing 6 changed files with 485 additions and 170 deletions.
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ include = ["src/**/*.rs", "Cargo.toml", "LICENSE", "README.md"]


[dependencies]
urlencoding = { version = "2.1.3", optional = true }
regex = "1.10.2"
urlencoding = { version = "2.1.3", optional = true }
serde = { version = "1.0.193", features = ["derive"], optional = true }
thiserror = { version = "1.0.48", optional = true }


# Edit `Makefile` and `src/lib.src` after making changes in this section:
Expand All @@ -43,13 +44,17 @@ commas = []
digits = []
find-capital-by-province = ["to-persian-chars"]
is-persian = []
national-id = []
national-id = ["dep:thiserror"]
remove-ordinal-suffix = []
to-persian-chars = []
url-fix = ["dep:urlencoding"]
verity-card-number = []
phone-number = []
phone-number = ["dep:thiserror"]
serde = ["dep:serde"]

[package.metadata.docs.rs]
all-features = true

[dev-dependencies]
# To test `serde` feature expectaions:
serde_json = "1.0.107"
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ build: full default add-ordinal-suffix commas digits find-capital-by-province is
check: clippy lint

test:
cargo test --all-features -- --nocapture
RUST_BACKTRACE=1 cargo test --all-features -- --nocapture

docs:
cargo doc --all-features
Expand Down Expand Up @@ -61,6 +61,9 @@ national-id:
@ echo ""
cargo build --no-default-features --features=national-id
@ ls -sh target/debug/*.rlib
cargo build --no-default-features --features="national-id serde"
@ ls -sh target/debug/*.rlib


remove-ordinal-suffix:
@ echo ""
Expand Down
284 changes: 202 additions & 82 deletions src/national_id/mod.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,145 @@
/// Validation of Iranian National Number(code-e Melli)
pub fn verify_iranian_national_id<S>(code: S) -> bool
where
S: Into<String>,
{
let code: String = code.into();
//! Iranian National Number utils (`national-id` Cargo feature).
//!
//! #### Example
//! ```rust
//! use rust_persian_tools::national_id::{NationalIdError, verify_iranian_national_id};
//!
//! assert!(verify_iranian_national_id("11537027").is_ok());
//! assert!(verify_iranian_national_id("0076229645").is_ok());
//! assert!(verify_iranian_national_id("1583250689").is_ok());
//!
//! assert_eq!(
//! verify_iranian_national_id("12345"),
//! Err(NationalIdError::Length(5)),
//! );
//! assert_eq!(
//! verify_iranian_national_id("9999999999"),
//! Err(NationalIdError::Invalid)
//! );
//! ```
//!
//! #### [serde] Integration
//! ##### National Number
//! ```rust
//! use rust_persian_tools::national_id::serde::national_id_de;
//!
//! #[derive(Debug, PartialEq, serde::Deserialize)]
//! struct MyStruct {
//! #[serde(deserialize_with = "national_id_de")]
//! id: String,
//! }
//!
//! let json_str = "{\"id\": \"0076229645\"}";
//! let my_struct: MyStruct = serde_json::from_str(json_str).unwrap();
//! assert_eq!(my_struct, MyStruct{id: "0076229645".to_string()});
//!
//! let json_str_invalid = "{\"id\": \"ZeroOneTwo\"}";
//! assert!(serde_json::from_str::<MyStruct>(json_str_invalid).is_err());
//! assert_eq!(
//! serde_json::from_str::<MyStruct>(json_str_invalid).err().unwrap().to_string(),
//! "Could not convert National Number to numeric at line 1 column 19".to_string(),
//! );
//! ```
//! ##### Option\<National Number\>
//! ```rust
//! use rust_persian_tools::national_id::serde::national_id_option_de;
//!
//! #[derive(Debug, PartialEq, serde::Deserialize)]
//! struct MyStruct {
//! #[serde(default, deserialize_with = "national_id_option_de")]
//! id: Option<String>,
//! }
//!
//! let json_str = "{}";
//! let my_struct: MyStruct = serde_json::from_str(json_str).unwrap();
//! assert_eq!(my_struct, MyStruct{id: None});
//! let json_str = "{\"id\": \"0076229645\"}";
//! let my_struct: MyStruct = serde_json::from_str(json_str).unwrap();
//! assert_eq!(my_struct, MyStruct{id: Some("0076229645".to_string())});
//! ```
let code_length = code.len();
if !(8..=10).contains(&code_length) {
return false;
}
use std::num::ParseIntError;

if code.parse::<u64>().is_err() {
return false;
}
#[cfg(feature = "serde")]
pub mod serde;

if code == "0000000000" {
return false;
}
if code == "1111111111" {
return false;
}
if code == "2222222222" {
return false;
}
if code == "3333333333" {
return false;
}
if code == "4444444444" {
return false;
}
if code == "5555555555" {
return false;
}
if code == "6666666666" {
return false;
}
if code == "7777777777" {
return false;
}
if code == "8888888888" {
return false;
}
if code == "9999999999" {
return false;
}
/// Possible errors during validation of Iranian National Number.
#[derive(Debug, PartialEq, thiserror::Error)]
pub enum NationalIdError {
/// If input length is invalid.
#[error("Invalid length {0} for National Number")]
Length(usize),
/// If input is not a [u64] number.
#[error("Could not convert National Number to numeric")]
NumericConvert { source: ParseIntError },
/// Other checks.
#[error("National Number is invalid")]
Invalid,
}

/// Validation of Iranian National Number (code-e Melli).
///
/// ## Examples
/// ```rust
/// use rust_persian_tools::national_id::{NationalIdError, verify_iranian_national_id};
///
/// assert!(verify_iranian_national_id("68415941").is_ok());
/// assert!(verify_iranian_national_id("0200203241").is_ok());
/// assert!(verify_iranian_national_id("0067749828").is_ok());
///
/// assert_eq!(
/// verify_iranian_national_id("0"),
/// Err(NationalIdError::Length(1)),
/// );
/// assert_eq!(
/// verify_iranian_national_id("1230000000"),
/// Err(NationalIdError::Invalid)
/// );
/// ```
pub fn verify_iranian_national_id(code: impl AsRef<str>) -> Result<(), NationalIdError> {
let code_str = code.as_ref();

let code = ("00".to_owned() + &code)[code_length + 2 - 10..].to_string();
if str::parse::<usize>(&code[3..9]).unwrap() == 0 {
return false;
let length = code_str.len();
if !((8..=10).contains(&length)) {
return Err(NationalIdError::Length(length));
}

let last_number =
str::parse::<usize>(code.chars().last().unwrap().to_string().as_str()).unwrap();
let code_u64 = code_str
.parse::<u64>()
.map_err(|source| NationalIdError::NumericConvert { source })?;

let mut sum = 0;
for i in 0..9 {
sum += str::parse::<usize>(&code[i..i + 1]).unwrap() * (10 - i);
if length == 10 && (code_u64 == 0 || are_digits_the_same(code_u64)) {
return Err(NationalIdError::Invalid);
}

let code_str = &("00".to_owned() + code_str)[length + 2 - 10..];
if code_str[3..9].parse::<u64>().unwrap() == 0 {
return Err(NationalIdError::Invalid);
}

let mut sum = (0usize..9).fold(0, |sum, i| {
sum + code_str[i..i + 1].parse::<usize>().unwrap() * (10 - i)
});
sum %= 11;
let last_number = (code_u64 % 10) as usize;
if (sum < 2 && last_number == sum) || (sum >= 2 && last_number == 11 - sum) {
Ok(())
} else {
Err(NationalIdError::Invalid)
}
}

(sum < 2 && last_number == sum) || (sum >= 2 && last_number == 11 - sum)
#[inline]
fn are_digits_the_same(mut number: u64) -> bool {
let last = number % 10;
while number != 0 {
let current = number % 10;
number /= 10;
if current != last {
return false;
}
}
true
}

#[cfg(test)]
Expand All @@ -69,39 +148,80 @@ mod verify_iranian_national_id_tests {

#[test]
fn check_falsy() {
assert_eq!(verify_iranian_national_id(""), false);
assert_eq!(verify_iranian_national_id("12345"), false);
assert_eq!(verify_iranian_national_id("0"), false);
assert_eq!(verify_iranian_national_id("000000"), false);
assert_eq!(verify_iranian_national_id("12300000"), false);
assert_eq!(verify_iranian_national_id("123000000"), false);
assert_eq!(verify_iranian_national_id("1230000000"), false);
assert_eq!(verify_iranian_national_id("0000000000"), false);
assert_eq!(verify_iranian_national_id("4444444444"), false);
assert_eq!(verify_iranian_national_id("9999999999"), false);
assert_eq!(verify_iranian_national_id("0684159415"), false);
assert_eq!(verify_iranian_national_id("1111111111"), false);
assert_eq!(verify_iranian_national_id("079041a904"), false); // this is not in typescript version
assert_eq!(
verify_iranian_national_id(""),
Err(NationalIdError::Length(0))
);
assert_eq!(
verify_iranian_national_id("12345"),
Err(NationalIdError::Length(5))
);
assert_eq!(
verify_iranian_national_id("0"),
Err(NationalIdError::Length(1))
);
assert_eq!(
verify_iranian_national_id("000000"),
Err(NationalIdError::Length(6))
);
assert_eq!(
verify_iranian_national_id("12300000"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("123000000"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("1230000000"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("0000000000"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("4444444444"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("9999999999"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("0684159415"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("1111111111"),
Err(NationalIdError::Invalid)
);
assert_eq!(
verify_iranian_national_id("079041a904"),
Err(NationalIdError::NumericConvert {
source: "079041a904".parse::<u64>().err().unwrap()
})
); // this is not in typescript version
}

#[test]
fn check_truly() {
assert_eq!(verify_iranian_national_id("11537027"), true);
assert_eq!(verify_iranian_national_id("787833770"), true);
assert_eq!(verify_iranian_national_id("1583250689"), true);
assert_eq!(verify_iranian_national_id("0499370899"), true);
assert_eq!(verify_iranian_national_id("0790419904"), true);
assert_eq!(verify_iranian_national_id("0084575948"), true);
assert_eq!(verify_iranian_national_id("0963695398"), true);
assert_eq!(verify_iranian_national_id("0684159414"), true);
assert_eq!(verify_iranian_national_id("0067749828"), true);
assert_eq!(verify_iranian_national_id("0650451252"), true);
assert_eq!(verify_iranian_national_id("4032152314"), true);
assert_eq!(verify_iranian_national_id("0076229645"), true);
assert_eq!(verify_iranian_national_id("4271467685"), true);
assert_eq!(verify_iranian_national_id("0200203241"), true);
assert_eq!(verify_iranian_national_id("068415941"), true);
assert_eq!(verify_iranian_national_id("68415941"), true);
assert_eq!(verify_iranian_national_id("787833770"), true);
assert_eq!(verify_iranian_national_id("11537027"), Ok(()));
assert_eq!(verify_iranian_national_id("787833770"), Ok(()));
assert_eq!(verify_iranian_national_id("1583250689"), Ok(()));
assert_eq!(verify_iranian_national_id("0499370899"), Ok(()));
assert_eq!(verify_iranian_national_id("0790419904"), Ok(()));
assert_eq!(verify_iranian_national_id("0084575948"), Ok(()));
assert_eq!(verify_iranian_national_id("0963695398"), Ok(()));
assert_eq!(verify_iranian_national_id("0684159414"), Ok(()));
assert_eq!(verify_iranian_national_id("0067749828"), Ok(()));
assert_eq!(verify_iranian_national_id("0650451252"), Ok(()));
assert_eq!(verify_iranian_national_id("4032152314"), Ok(()));
assert_eq!(verify_iranian_national_id("0076229645"), Ok(()));
assert_eq!(verify_iranian_national_id("4271467685"), Ok(()));
assert_eq!(verify_iranian_national_id("0200203241"), Ok(()));
assert_eq!(verify_iranian_national_id("068415941"), Ok(()));
assert_eq!(verify_iranian_national_id("68415941"), Ok(()));
assert_eq!(verify_iranian_national_id("787833770"), Ok(()));
}
}
Loading

0 comments on commit c62f961

Please sign in to comment.