Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add thiserror to phone_number module #20

Merged
merged 4 commits into from
Dec 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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