#[macro_use]
extern crate lazy_static;
extern crate regex;
use regex::Regex;
use std::ops::RangeInclusive;
mod luhn;
lazy_static! {
static ref VISAELECTRON_PATTERN_REGEX: Regex =
Regex::new(r"^4(026|17500|405|508|844|91[37])").unwrap();
static ref MAESTRO_PATTERN_REGEX: Regex = Regex::new(r"^(5(018|0[23]|[68])|6(39|7))").unwrap();
static ref FORBRUGSFORENINGEN_PATTERN_REGEX: Regex = Regex::new(r"^600").unwrap();
static ref DANKORT_PATTERN_REGEX: Regex = Regex::new(r"^5019").unwrap();
static ref VISA_PATTERN_REGEX: Regex = Regex::new(r"^4").unwrap();
static ref MIR_PATTERN_REGEX: Regex = Regex::new(r"^220[0-4]").unwrap();
static ref MASTERCARD_PATTERN_REGEX: Regex = Regex::new(r"^(5[1-5]|2[2-7])").unwrap();
static ref AMEX_PATTERN_REGEX: Regex = Regex::new(r"^3[47]").unwrap();
static ref DINERSCLUB_PATTERN_REGEX: Regex = Regex::new(r"^3[0689]").unwrap();
static ref DISCOVER_PATTERN_REGEX: Regex = Regex::new(r"^6([045]|22)").unwrap();
static ref UNIONPAY_PATTERN_REGEX: Regex = Regex::new(r"^(62|88)").unwrap();
static ref JCB_PATTERN_REGEX: Regex = Regex::new(r"^35").unwrap();
static ref OTHER_PATTERN_REGEX: Regex = Regex::new(r"^[0-9]+$").unwrap();
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum Type {
VisaElectron,
Maestro,
Forbrugsforeningen,
Dankort,
Visa,
MIR,
MasterCard,
Amex,
DinersClub,
Discover,
UnionPay,
JCB,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum ValidateError {
InvalidFormat,
InvalidLength,
InvalidLuhn,
UnknownType,
}
impl Type {
pub fn name(&self) -> String {
match self {
Type::VisaElectron => "visaelectron",
Type::Maestro => "maestro",
Type::Forbrugsforeningen => "forbrugsforeningen",
Type::Dankort => "dankort",
Type::Visa => "visa",
Type::MIR => "mir",
Type::MasterCard => "mastercard",
Type::Amex => "amex",
Type::DinersClub => "dinersclub",
Type::Discover => "discover",
Type::UnionPay => "unionpay",
Type::JCB => "jcb",
}
.to_string()
}
fn pattern(&self) -> &Regex {
match self {
Type::VisaElectron => &VISAELECTRON_PATTERN_REGEX,
Type::Maestro => &MAESTRO_PATTERN_REGEX,
Type::Forbrugsforeningen => &FORBRUGSFORENINGEN_PATTERN_REGEX,
Type::Dankort => &DANKORT_PATTERN_REGEX,
Type::Visa => &VISA_PATTERN_REGEX,
Type::MIR => &MIR_PATTERN_REGEX,
Type::MasterCard => &MASTERCARD_PATTERN_REGEX,
Type::Amex => &AMEX_PATTERN_REGEX,
Type::DinersClub => &DINERSCLUB_PATTERN_REGEX,
Type::Discover => &DISCOVER_PATTERN_REGEX,
Type::UnionPay => &UNIONPAY_PATTERN_REGEX,
Type::JCB => &JCB_PATTERN_REGEX,
}
}
fn length(&self) -> RangeInclusive<usize> {
match self {
Type::VisaElectron => 16..=16,
Type::Maestro => 12..=19,
Type::Forbrugsforeningen => 16..=16,
Type::Dankort => 16..=16,
Type::Visa => 13..=16,
Type::MIR => 16..=19,
Type::MasterCard => 16..=16,
Type::Amex => 15..=15,
Type::DinersClub => 14..=14,
Type::Discover => 16..=16,
Type::JCB => 16..=16,
Type::UnionPay => 16..=19,
}
}
const fn all() -> &'static [Type] {
&[
Type::VisaElectron,
Type::Maestro,
Type::Forbrugsforeningen,
Type::Dankort,
Type::Visa,
Type::MIR,
Type::MasterCard,
Type::Amex,
Type::DinersClub,
Type::Discover,
Type::UnionPay,
Type::JCB,
]
}
}
impl ToString for Type {
fn to_string(&self) -> String {
match self {
Type::VisaElectron => "VisaElectron",
Type::Maestro => "Maestro",
Type::Forbrugsforeningen => "Forbrugsforeningen",
Type::Dankort => "Dankort",
Type::Visa => "Visa",
Type::MIR => "MIR",
Type::MasterCard => "MasterCard",
Type::Amex => "Amex",
Type::DinersClub => "DinersClub",
Type::Discover => "Discover",
Type::UnionPay => "UnionPay",
Type::JCB => "JCB",
}
.to_string()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Validate {
pub card_type: Type,
}
impl Validate {
pub fn from(card_number: &str) -> Result<Validate, ValidateError> {
let card_type = Validate::evaluate_type(card_number)?;
if !Validate::is_length_valid(card_number, &card_type) {
return Err(ValidateError::InvalidLength);
}
if !Validate::is_luhn_valid(card_number) {
return Err(ValidateError::InvalidLuhn);
}
Ok(Validate { card_type })
}
pub fn evaluate_type(card_number: &str) -> Result<Type, ValidateError> {
if OTHER_PATTERN_REGEX.is_match(card_number) {
for card in Type::all() {
if card.pattern().is_match(card_number) {
return Ok(*card);
}
}
Err(ValidateError::UnknownType)
} else {
Err(ValidateError::InvalidFormat)
}
}
pub fn is_length_valid(card_number: &str, card_type: &Type) -> bool {
let size = card_number.len();
let range = card_type.length();
range.contains(&size)
}
#[inline(always)]
pub fn is_luhn_valid(card_number: &str) -> bool {
luhn::valid(card_number)
}
}