use crate::utils::parser::{parse_color, parse_email, parse_phone_number};
use std::fmt;
use std::str::FromStr;
#[derive(Clone)]
pub enum InputType {
Color,
Email,
Number,
Password(char),
Telephone,
Text,
SignedInteger,
UnsignedInteger,
Custom(fn(&str) -> bool, fn(&str, char) -> bool),
CustomPassword(char, fn(&str) -> bool, fn(&str, char) -> bool),
}
impl PartialEq for InputType {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Color, Self::Color) => true,
(Self::Email, Self::Email) => true,
(Self::Number, Self::Number) => true,
(Self::Password(ch), Self::Password(ch2)) => ch == ch2,
(Self::Telephone, Self::Telephone) => true,
(Self::Text, Self::Text) => true,
(Self::SignedInteger, Self::SignedInteger) => true,
(Self::UnsignedInteger, Self::UnsignedInteger) => true,
(Self::Custom(..), Self::Custom(..)) => true,
(Self::CustomPassword(ch, _, _), Self::CustomPassword(ch2, _, _)) => ch == ch2,
(_, _) => false,
}
}
}
impl fmt::Debug for InputType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::Color => write!(f, "InputType::Color"),
Self::Custom(..) => write!(f, "InputType::Custom"),
Self::CustomPassword(c, _, _) => write!(f, "InputType::CustomPassword({})", c),
Self::Email => write!(f, "InputType::Email"),
Self::Number => write!(f, "InputType::Number"),
Self::Password(ch) => write!(f, "InputType::Password({})", ch),
Self::SignedInteger => write!(f, "InputType::SignedInteger"),
Self::Telephone => write!(f, "InputType::Telephone"),
Self::Text => write!(f, "InputType::Text"),
Self::UnsignedInteger => write!(f, "InputType::UnsignedInteger"),
}
}
}
impl InputType {
pub fn char_valid(&self, input: &str, c: char) -> bool {
match self {
Self::Color => Self::char_valid_for_color(input, c),
Self::Email => Self::char_valid_for_email(input, c),
Self::Number => {
c.is_ascii_digit() || (['+', '-'].contains(&c) && input.is_empty()) || c == '.'
}
Self::Telephone => Self::char_valid_for_phone(input, c),
Self::SignedInteger => {
c.is_ascii_digit() || (['+', '-'].contains(&c) && input.is_empty())
}
Self::UnsignedInteger => c.is_ascii_digit(),
Self::Password(_) | Self::Text => true,
Self::Custom(_, char_valid) | Self::CustomPassword(_, _, char_valid) => {
char_valid(input, c)
}
}
}
pub fn validate(&self, s: &str) -> bool {
match self {
Self::Color => parse_color(s).is_some(),
Self::Email => parse_email(s).is_some(),
Self::Number => f64::from_str(s).is_ok(),
Self::SignedInteger => isize::from_str(s).is_ok(),
Self::UnsignedInteger => usize::from_str(s).is_ok(),
Self::Password(_) | Self::Text => true,
Self::Telephone => parse_phone_number(s).is_some(),
Self::Custom(validate, _) | Self::CustomPassword(_, validate, _) => validate(s),
}
}
fn char_valid_for_email(input: &str, c: char) -> bool {
c.is_alphanumeric() || [
'.', '!', '#', '$', '%', '&', '\'', '*', '+', '/', '\\', '=', '?', '^', '_', '`',
'{', '|', '}', '~', '-',
]
.contains(&c) || (c == '@' && !input.is_empty() && input.find('@').is_none()) }
fn char_valid_for_phone(input: &str, c: char) -> bool {
c.is_ascii_digit()
|| (c == '+' && input.is_empty())
|| ([' ', '-'].contains(&c) && !input.is_empty())
}
fn char_valid_for_color(input: &str, c: char) -> bool {
(c.is_alphanumeric() && !input.starts_with('#'))
|| (input.starts_with('#') && c.is_ascii_hexdigit())
|| c == ' '
|| (c == '#' && input.is_empty())
|| (c == ',' && input.starts_with("rgb"))
|| (c == '(' && input.starts_with("rgb") && !input.contains('('))
|| (c.is_ascii_digit() && input.starts_with("rgb"))
|| (c == ')' && input.len() >= 15 && !input.contains(')')) }
}
#[cfg(test)]
mod test {
use super::*;
use lazy_regex::{Lazy, Regex};
use pretty_assertions::assert_eq;
#[test]
fn validate_input_type() {
assert_eq!(InputType::Color.validate("#ff00bb"), true);
assert_eq!(
InputType::Email.validate("christian.visintin@github.com"),
true
);
assert_eq!(InputType::Number.validate("-96.4"), true);
assert_eq!(InputType::Number.validate("-35"), true);
assert_eq!(InputType::Number.validate("32.4"), true);
assert_eq!(InputType::SignedInteger.validate("-96"), true);
assert_eq!(InputType::SignedInteger.validate("+128"), true);
assert_eq!(InputType::SignedInteger.validate("+128.5"), false);
assert_eq!(InputType::UnsignedInteger.validate("-96"), false);
assert_eq!(InputType::UnsignedInteger.validate("+128"), true);
assert_eq!(InputType::UnsignedInteger.validate("+128.5"), false);
assert_eq!(InputType::Text.validate("Hello world!"), true);
assert_eq!(InputType::Password('*').validate("Hello world!"), true);
assert_eq!(InputType::Telephone.validate("+39 345 777 6117"), true);
let custom = InputType::Custom(custom_valid, custom_char_valid);
assert_eq!(custom.validate("v0.7.0"), true);
assert_eq!(custom.validate("vaaaa"), false);
let custom = InputType::CustomPassword('*', custom_valid, custom_char_valid);
assert_eq!(custom.validate("v0.7.0"), true);
assert_eq!(custom.validate("vaaaa"), false);
}
#[test]
fn validate_input_char_color() {
assert_eq!(InputType::Color.char_valid("#ff00b", 'b'), true);
assert_eq!(InputType::Color.char_valid("#ff00b", 'g'), false);
assert_eq!(InputType::Color.char_valid("#", 'b'), true);
assert_eq!(InputType::Color.char_valid("", '#'), true);
assert_eq!(InputType::Color.char_valid("#", '#'), false);
assert_eq!(InputType::Color.char_valid("", 'r'), true);
assert_eq!(InputType::Color.char_valid("r", 'g'), true);
assert_eq!(InputType::Color.char_valid("rg", 'b'), true);
assert_eq!(InputType::Color.char_valid("rgb", '('), true);
assert_eq!(InputType::Color.char_valid("rgb(", '2'), true);
assert_eq!(InputType::Color.char_valid("rgb(2", '5'), true);
assert_eq!(InputType::Color.char_valid("rgb(255", ','), true);
assert_eq!(InputType::Color.char_valid("rgb(255, 255, 255", ')'), true);
assert_eq!(
InputType::Color.char_valid("rgb(255, 255, 255)", ')'),
false
);
assert_eq!(InputType::Color.char_valid("rgb(", '('), false);
assert_eq!(InputType::Color.char_valid("cr", 'i'), true);
assert_eq!(InputType::Color.char_valid("", 'c'), true);
assert_eq!(InputType::Color.char_valid("c", '#'), false);
}
#[test]
fn validate_input_email() {
assert_eq!(InputType::Email.char_valid("chrostaceo", '.'), true);
assert_eq!(InputType::Email.char_valid("chrostaceo.", 'v'), true);
assert_eq!(
InputType::Email.char_valid("chrostaceo.veeseenteen", '1'),
true
);
assert_eq!(
InputType::Email.char_valid("chrostaceo.veeseenteen1997", '!'),
true
);
assert_eq!(
InputType::Email.char_valid("chrostaceo.veeseenteen1997!", '@'),
true
);
assert_eq!(
InputType::Email.char_valid("chrostaceo.veeseenteen1997!@", '@'),
false
);
assert_eq!(
InputType::Email.char_valid("chrostaceo.veeseenteen1997!@", 'g'),
true
);
assert_eq!(
InputType::Email.char_valid("chrostaceo.veeseenteen1997!@gmail", '.'),
true
);
assert_eq!(
InputType::Email.char_valid("chrostaceo.veeseenteen1997!@gmail.co", 'm'),
true
);
}
#[test]
fn validate_input_char_phone() {
assert_eq!(InputType::Telephone.char_valid("", '+'), true);
assert_eq!(InputType::Telephone.char_valid("+", '+'), false);
assert_eq!(InputType::Telephone.char_valid("", '-'), false);
assert_eq!(InputType::Telephone.char_valid("", ' '), false);
assert_eq!(InputType::Telephone.char_valid("", 'b'), false);
assert_eq!(InputType::Telephone.char_valid("+", '3'), true);
assert_eq!(InputType::Telephone.char_valid("+39", ' '), true);
assert_eq!(InputType::Telephone.char_valid("+39 ", '3'), true);
assert_eq!(InputType::Telephone.char_valid("+39 345", ' '), true);
assert_eq!(
InputType::Telephone.char_valid("+39 345 777 611", '7'),
true
);
}
#[test]
fn validate_input_char_numbers() {
assert_eq!(InputType::Number.char_valid("", '+'), true);
assert_eq!(InputType::Number.char_valid("", '-'), true);
assert_eq!(InputType::Number.char_valid("", '.'), true);
assert_eq!(InputType::Number.char_valid("", '1'), true);
assert_eq!(InputType::Number.char_valid("+", '1'), true);
assert_eq!(InputType::Number.char_valid("-", '2'), true);
assert_eq!(InputType::Number.char_valid("-24", '.'), true);
assert_eq!(InputType::Number.char_valid("-24.", '5'), true);
assert_eq!(InputType::Number.char_valid("-", '+'), false);
assert_eq!(InputType::Number.char_valid("24", '-'), false);
assert_eq!(InputType::Number.char_valid("24", 'a'), false);
assert_eq!(InputType::SignedInteger.char_valid("", '+'), true);
assert_eq!(InputType::SignedInteger.char_valid("", '-'), true);
assert_eq!(InputType::SignedInteger.char_valid("+2", '2'), true);
assert_eq!(InputType::SignedInteger.char_valid("", '1'), true);
assert_eq!(InputType::SignedInteger.char_valid("-52", '-'), false);
assert_eq!(InputType::SignedInteger.char_valid("-52", 'a'), false);
assert_eq!(InputType::UnsignedInteger.char_valid("", '2'), true);
assert_eq!(InputType::UnsignedInteger.char_valid("", '+'), false);
assert_eq!(InputType::UnsignedInteger.char_valid("", 'b'), false);
assert_eq!(InputType::UnsignedInteger.char_valid("", '.'), false);
assert_eq!(InputType::UnsignedInteger.char_valid("24", '5'), true);
}
#[test]
fn validate_input_char_text() {
assert_eq!(InputType::Text.char_valid("", 'a'), true);
assert_eq!(InputType::Password('*').char_valid("", 'b'), true);
}
#[test]
fn validate_input_char_custom() {
let custom = InputType::Custom(custom_valid, custom_char_valid);
assert_eq!(custom.char_valid("", 'v'), true);
assert_eq!(custom.char_valid("v", 'v'), false);
assert_eq!(custom.char_valid("v", '0'), true);
assert_eq!(custom.char_valid("v0.7", '.'), true);
assert_eq!(custom.char_valid("v0.7.", '0'), true);
let custom = InputType::CustomPassword('*', custom_valid, custom_char_valid);
assert_eq!(custom.char_valid("", 'v'), true);
assert_eq!(custom.char_valid("v", 'v'), false);
assert_eq!(custom.char_valid("v", '0'), true);
assert_eq!(custom.char_valid("v0.7", '.'), true);
assert_eq!(custom.char_valid("v0.7.", '0'), true);
}
fn custom_valid(s: &str) -> bool {
static TEST_REGEX: Lazy<Regex> = lazy_regex!(r".*(:?[0-9]\.[0-9]\.[0-9])");
TEST_REGEX.is_match(s)
}
fn custom_char_valid(s: &str, c: char) -> bool {
s.is_empty() || (!s.is_empty() && (c.is_numeric() || c == '.'))
}
}