use crate::pattern::regex;
use crate::types::{Rule, TokenData};
use super::{detect_issuer, luhn_check, CreditCardNumberData};
fn cc_rule(name: &str, pattern: &str, issuer: &str) -> Rule {
let issuer = issuer.to_string();
Rule {
name: name.to_string(),
pattern: vec![regex(pattern)],
production: Box::new(move |nodes| {
let text = match &nodes[0].token_data {
TokenData::RegexMatch(m) => m.group(1)?,
_ => return None,
};
let digits: String = text.chars().filter(|c| c.is_ascii_digit()).collect();
if luhn_check(&digits) {
Some(TokenData::CreditCardNumber(CreditCardNumberData::new(
&digits, &issuer,
)))
} else {
None
}
}),
}
}
pub fn rules() -> Vec<Rule> {
vec![
cc_rule(
"visa credit card number",
r"(4[0-9]{15}|4[0-9]{3}-[0-9]{4}-[0-9]{4}-[0-9]{4})",
"visa",
),
cc_rule(
"amex card number",
r"(3[47][0-9]{13}|3[47][0-9]{2}-[0-9]{6}-[0-9]{5})",
"amex",
),
cc_rule(
"discover card number",
r"(6(?:011|[45][0-9]{2})[0-9]{12}|6(?:011|[45][0-9]{2})-[0-9]{4}-[0-9]{4}-[0-9]{4})",
"discover",
),
cc_rule(
"mastercard card number",
r"(5[1-5][0-9]{14}|5[1-5][0-9]{2}-[0-9]{4}-[0-9]{4}-[0-9]{4})",
"mastercard",
),
cc_rule(
"diner club card number",
r"(3(?:0[0-5]|[68][0-9])[0-9]{11}|3(?:0[0-5]|[68][0-9])[0-9]-[0-9]{6}-[0-9]{4})",
"dinerclub",
),
{
Rule {
name: "credit card number".to_string(),
pattern: vec![regex(r"(\d{8,19})")],
production: Box::new(|nodes| {
let text = match &nodes[0].token_data {
TokenData::RegexMatch(m) => m.group(1)?,
_ => return None,
};
let digits: String = text.chars().filter(|c| c.is_ascii_digit()).collect();
if !luhn_check(&digits) {
return None;
}
let issuer = detect_issuer(&digits);
Some(TokenData::CreditCardNumber(CreditCardNumberData::new(
&digits, issuer,
)))
}),
}
},
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_luhn() {
assert!(luhn_check("4111111111111111")); assert!(luhn_check("5500000000000004")); assert!(luhn_check("30569309025904")); assert!(!luhn_check("1234567890123456")); }
}