use std::fmt;
use std::ops::Deref;
use regex::Regex;
use crate::error::ValidationError;
use crate::value::Value;
fn validate_date(date: &str) -> bool {
let reg_date = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
reg_date.is_match(date)
}
fn validate_datetime(datetime: &str) -> bool {
let reg_datetime = Regex::new(
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$").unwrap();
reg_datetime.is_match(datetime)
}
fn validate_time(time: &str) -> bool {
let reg_time = Regex::new(r"^\d{2}:\d{2}$").unwrap();
reg_time.is_match(time)
}
fn validate_email(email: &str) -> bool {
let reg_email = Regex::new(r"^\S+@\w[-\.\w]+\.\w{2,}$").unwrap();
reg_email.is_match(email)
}
fn validate_url(url: &str) -> bool {
let reg_url = Regex::new(r"^\w+\:\/\/\w[-\.\w]+(\/\S*)?$").unwrap();
reg_url.is_match(url)
}
#[derive(Debug)]
pub enum Method {
Get,
Post,
Dialog,
}
impl Method {
pub fn attrvalue(&self) -> String {
match &self {
Method::Get => "get",
Method::Post => "post",
Method::Dialog => "dialog",
}.to_string()
}
}
#[derive(Debug)]
pub enum Element {
Input(InputType),
Textarea,
Select(SelectType),
Button(ButtonType),
}
impl Element {
pub fn validate(&self, formvalue: &Value)
-> Result<(), ValidationError> {
match self {
Element::Input(InputType::Date) => {
if !validate_date(&formvalue.to_string()) {
Err(ValidationError::new(
&format!("Invalid date {}.", formvalue.as_string())))
} else {
Ok(())
}
},
Element::Input(InputType::Time) => {
if !validate_time(&formvalue.to_string()) {
Err(ValidationError::new(
&format!("Invalid time {}.", formvalue.as_string())))
} else {
Ok(())
}
},
Element::Input(InputType::DateTime) => {
if !validate_datetime(&formvalue.to_string()) {
Err(ValidationError::new(
&format!(
"Invalid datetime {}.", formvalue.as_string())))
} else {
Ok(())
}
},
Element::Input(InputType::Email) => {
if !validate_email(&formvalue.to_string()) {
Err(ValidationError::new(
&format!(
"Invalid email address {}.",
formvalue.as_string())))
} else {
Ok(())
}
},
Element::Input(InputType::Url) => {
if !validate_url(&formvalue.to_string()) {
Err(ValidationError::new(
&format!("Invalid url {}", formvalue.as_string())))
} else {
Ok(())
}
},
_ => Ok(())
}
}
pub fn element_name(&self) -> &'static str {
match &self {
Element::Input(_) => "input",
Element::Textarea => "textarea",
Element::Select(_) => "select",
Element::Button(_) => "button",
}
}
pub fn element_type(&self) -> &'static str {
match &self {
Element::Input(input_type) => match input_type {
InputType::Text => "text",
InputType::Password => "password",
InputType::Radio => "radio",
InputType::Checkbox => "checkbox",
InputType::Number => "number",
InputType::Range => "range",
InputType::Date => "date",
InputType::DateTime => "datetime-local",
InputType::Month => "month",
InputType::Week => "week",
InputType::Time => "time",
InputType::Url => "url",
InputType::Email => "email",
InputType::Tel => "tel",
InputType::Color => "color",
InputType::File => "file",
InputType::Search => "search",
InputType::Button => "button",
InputType::Reset => "reset",
InputType::Submit => "submit",
InputType::Image => "image",
InputType::Hidden => "hidden",
},
Element::Button(button_type) => match button_type {
ButtonType::Submit => "submit",
ButtonType::Reset => "reset",
ButtonType::Button => "button",
},
_ => "",
}
}
pub fn multi(&self) -> bool {
match self {
Element::Input(InputType::Checkbox) |
Element::Select(SelectType::Multi) => true,
_ => false,
}
}
}
#[derive(Debug)]
pub enum InputType {
Text,
Password,
Radio,
Checkbox,
Number,
Range,
Date,
DateTime,
Month,
Week,
Time,
Url,
Email,
Tel,
Color,
File,
Search,
Button,
Submit,
Reset,
Image,
Hidden,
}
#[derive(Debug)]
pub enum SelectType {
Single,
Multi,
}
#[derive(Debug)]
pub enum ButtonType {
Submit,
Reset,
Button,
}
#[derive(Debug)]
pub enum Spellcheck {
True,
Default,
False,
}
#[derive(Debug)]
pub enum Wrap {
Hard,
Soft,
Off,
}
#[derive(Debug)]
pub enum Autocomplete {
On,
Off,
}
pub enum Constraint<'a> {
MinLength(usize),
MaxLength(usize),
MinNumber(f64),
MaxNumber(f64),
MinDate(&'a str),
MaxDate(&'a str),
MinDateTime(&'a str),
MaxDateTime(&'a str),
MinTime(&'a str),
MaxTime(&'a str),
Pattern(&'a str),
Func(Box<Fn(&Value) -> Result<(), ValidationError>>),
}
impl <'a> Constraint<'a> {
pub fn validate(&self, formvalue: &Value)
-> Result<(), ValidationError> {
match self {
Constraint::MinLength(min) => {
let value = formvalue.as_string();
if value.len() < *min {
return Err(ValidationError::new(
&format!(
"Must be at least {} characters long.",
min)));
}
},
Constraint::MaxLength(max) => {
let value = formvalue.as_string();
if value.len() > *max {
return Err(ValidationError::new(
&format!(
"Can not be more than {} characters long.",
max)));
}
},
Constraint::MinNumber(min) => {
let value: f64 = formvalue.parse()?;
if value < *min {
return Err(ValidationError::new(
&format!("Must be at least {}.", min)));
}
},
Constraint::MaxNumber(max) => {
let value: f64 = formvalue.parse()?;
if value > *max {
return Err(ValidationError::new(
&format!("Can not be more than {}.", max)));
}
},
Constraint::MinDate(min) => {
let value = formvalue.to_string();
if !validate_date(&value) {
return Err(ValidationError::new(
&format!("Invalid date {}.", value)));
}
for (i, chr) in value.as_bytes().iter().enumerate() {
if *chr < min.as_bytes()[i] {
return Err(ValidationError::new(
&format!(
"Date should be after {}.", min)));
}
}
},
Constraint::MaxDate(max) => {
let value = formvalue.to_string();
if !validate_date(&value) {
return Err(ValidationError::new(
&format!("Invalid date {}.", value)));
}
for (i, chr) in value.as_bytes().iter().enumerate() {
if *chr > max.as_bytes()[i] {
return Err(ValidationError::new(
&format!(
"Date can not be after {}.", max)));
}
}
},
Constraint::MinDateTime(min) => {
let value = formvalue.to_string();
if !validate_datetime(&value) {
return Err(ValidationError::new(
&format!("Invalid date and time {}.", value)));
}
for (i, chr) in value.as_bytes().iter().enumerate() {
if *chr < min.as_bytes()[i] {
return Err(ValidationError::new(
&format!(
"Date and time must be after {}",
min)));
}
}
},
Constraint::MaxDateTime(max) => {
let value = formvalue.to_string();
if !validate_datetime(&value) {
return Err(ValidationError::new(
&format!("Invalid date and time {}.", value)));
}
for (i, chr) in value.as_bytes().iter().enumerate() {
if *chr > max.as_bytes()[i] {
return Err(ValidationError::new(
&format!(
"Date and time can not be after {}.",
max)));
}
}
},
Constraint::MinTime(min) => {
let value = formvalue.to_string();
if !validate_time(&value) {
return Err(ValidationError::new(
&format!("Invalid time {}.", value)));
}
for (i, chr) in value.as_bytes().iter().enumerate() {
if *chr < min.as_bytes()[i] {
return Err(ValidationError::new(
&format!(
"Time must be after {}.", min)));
}
}
},
Constraint::MaxTime(max) => {
let value = formvalue.to_string();
if !validate_time(&value) {
return Err(ValidationError::new(
&format!("Invalid time {}.", value)));
}
for (i, chr) in value.as_bytes().iter().enumerate() {
if *chr > max.as_bytes()[i] {
return Err(ValidationError::new(
&format!(
"Time can not be after {}.", max)));
}
}
},
Constraint::Pattern(pattern) => {
let value = formvalue.as_string();
let reg = match Regex::new(pattern) {
Ok(reg) => reg,
Err(_) => return Err(
ValidationError::new(
&format!("Invalid pattern {:?}.", value))),
};
if !reg.is_match(&value) {
return Err(
ValidationError::new(
&format!("Please match the format requested.")));
}
},
Constraint::Func(validator) => {
validator(&formvalue)?;
},
}
Ok(())
}
pub fn attrpair(&self) -> Option<(String, String)> {
match self {
Constraint::MinLength(min) =>
Some((String::from("minlength"), min.to_string())),
Constraint::MaxLength(max) =>
Some((String::from("maxlength"), max.to_string())),
Constraint::MinNumber(min) =>
Some((String::from("min"), min.to_string())),
Constraint::MaxNumber(max) =>
Some((String::from("max"), max.to_string())),
Constraint::MinDate(min) |
Constraint::MinTime(min) |
Constraint::MinDateTime(min) =>
Some((String::from("min"), min.to_string())),
Constraint::MaxDate(max) |
Constraint::MaxTime(max) |
Constraint::MaxDateTime(max) =>
Some((String::from("max"), max.to_string())),
Constraint::Pattern(pattern) =>
Some((String::from("pattern"), pattern.to_string())),
Constraint::Func(_) => None,
}
}
pub fn allowed_on(&self, element: &Element) -> bool {
match self {
Constraint::MinLength(_) |
Constraint::MaxLength(_) => match element {
Element::Textarea => true,
Element::Input(input_type) => match input_type {
InputType::Radio |
InputType::Checkbox |
InputType::File |
InputType::Button |
InputType::Submit |
InputType::Reset |
InputType::Image |
InputType::Hidden => false,
_ => true,
},
_ => false,
},
Constraint::MinNumber(_) => match element {
Element::Input(InputType::Number) => true,
_ => false,
},
Constraint::MaxNumber(_) => match element {
Element::Input(InputType::Number) => true,
_ => false,
},
Constraint::MinDate(_) => match element {
Element::Input(InputType::Date) => true,
_ => false,
},
Constraint::MaxDate(_) => match element {
Element::Input(InputType::Date) => true,
_ => false,
},
Constraint::MinTime(_) => match element {
Element::Input(InputType::Time) => true,
_ => false,
},
Constraint::MaxTime(_) => match element {
Element::Input(InputType::Time) => true,
_ => false,
},
Constraint::MinDateTime(_) => match element {
Element::Input(InputType::DateTime) => true,
_ => false,
},
Constraint::MaxDateTime(_) => match element {
Element::Input(InputType::DateTime) => true,
_ => false,
},
Constraint::Pattern(_) => match element {
Element::Textarea => true,
Element::Input(input_type) => match input_type {
InputType::Text |
InputType::Password |
InputType::Date |
InputType::Url |
InputType::Email |
InputType::Tel |
InputType::Search => true,
_ => false,
},
_ => false,
},
Constraint::Func(_) => true,
}
}
}
impl <'a> fmt::Debug for Constraint<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Constraint::MinLength(len) => {
write!(f, "Constraint::MinLength({})", len)
},
Constraint::MaxLength(len) => {
write!(f, "Constraint::MaxLength({})", len)
},
Constraint::MinNumber(number) => {
write!(f, "Constraint::MinNumber({})", number)
},
Constraint::MaxNumber(number) => {
write!(f, "Constraint::MaxNumber({})", number)
},
Constraint::MinDate(number) => {
write!(f, "Constraint::MinDate({})", number)
},
Constraint::MaxDate(number) => {
write!(f, "Constraint::MaxDate({})", number)
},
Constraint::MinTime(number) => {
write!(f, "Constraint::MinTime({})", number)
},
Constraint::MaxTime(number) => {
write!(f, "Constraint::MaxTime({})", number)
},
Constraint::MinDateTime(number) => {
write!(f, "Constraint::MinDateTime({})", number)
},
Constraint::MaxDateTime(number) => {
write!(f, "Constraint::MaxDateTime({})", number)
},
Constraint::Pattern(pattern) => {
write!(f, "Constraint::Pattern({})", pattern)
},
Constraint::Func(_) => {
write!(f, "Constraint::Func(Fn)")
},
}
}
}
#[derive(Debug)]
pub enum Attr<'a> {
Any(&'a str, &'a str),
Id(&'a str),
Title(&'a str),
Placeholder(&'a str),
Autocomplete(Autocomplete),
Autofocus,
Disabled,
Readonly,
Tabindex(i64),
StepFloat(f64),
StepInt(u64),
Size(u64),
Width(u64),
Height(u64),
Rows(u64),
Cols(u64),
Spellcheck(Spellcheck),
Wrap(Wrap),
FormAction(&'a str),
FormEnctype(&'a str),
FormNoValidate,
FormTarget(&'a str),
}
impl <'a> Attr<'a> {
pub fn attrpair(&self) -> (String, String) {
let (name, value) = match self {
Attr::Any(name, value) => (name.deref(), value.to_string()),
Attr::StepFloat(step) => ("step", step.to_string()),
Attr::StepInt(step) => ("step", step.to_string()),
Attr::Size(size) => ("size", size.to_string()),
Attr::Width(width) => ("width", width.to_string()),
Attr::Height(height) => ("height", height.to_string()),
Attr::Rows(rows) => ("rows", rows.to_string()),
Attr::Cols(cols) => ("cols", cols.to_string()),
Attr::Spellcheck(wrap) =>
("wrap", match wrap {
Spellcheck::True => String::from("true"),
Spellcheck::Default => String::from("default"),
Spellcheck::False => String::from("false"),
}),
Attr::Wrap(wrap) =>
("wrap", match wrap {
Wrap::Hard => String::from("hard"),
Wrap::Soft => String::from("soft"),
Wrap::Off => String::from("off"),
}),
Attr::FormAction(formaction) =>
("formaction", formaction.to_string()),
Attr::FormEnctype(formenctype) =>
("formenctype", formenctype.to_string()),
Attr::FormNoValidate => (
"formnovalidate", String::from("formnovalidate")),
Attr::FormTarget(formtarget) =>
("formtarget", formtarget.to_string()),
Attr::Id(id) => ("id", id.to_string()),
Attr::Title(label) => ("title", label.to_string()),
Attr::Placeholder(label) =>
("placeholder", label.to_string()),
Attr::Autocomplete(autocomplete) =>
("autocomplete", match autocomplete {
Autocomplete::On => String::from("on"),
Autocomplete::Off => String::from("off"),
}),
Attr::Autofocus => ("autofocus", String::from("autofocus")),
Attr::Disabled => ("disabled", String::from("disabled")),
Attr::Readonly => ("readonly", String::from("readonly")),
Attr::Tabindex(tabindex) => ("tabindex", tabindex.to_string()),
};
(String::from(name), value)
}
pub fn allowed_on(&self, element: &Element) -> bool {
match element {
Element::Input(input_type) => match self {
Attr::Any(_, _) |
Attr::Id(_) |
Attr::Title(_) => true,
Attr::StepFloat(_) => match input_type {
InputType::Number => true,
_ => false,
},
Attr::StepInt(_) => match input_type {
InputType::Number |
InputType::Date |
InputType::Time |
InputType::DateTime |
InputType::Month |
InputType::Week |
InputType::Range => true,
_ => false,
},
Attr::Width(_) |
Attr::Height(_) => match input_type {
InputType::Image => true,
_ => false,
},
Attr::Rows(_) |
Attr::Cols(_) |
Attr::Spellcheck(_) |
Attr::Wrap(_) => false,
Attr::FormAction(_) |
Attr::FormEnctype(_) |
Attr::FormTarget(_) => match input_type {
InputType::Submit |
InputType::Image => true,
_ => false,
},
Attr::FormNoValidate => match input_type {
InputType::Submit => true,
_ => false,
},
Attr::Placeholder(_) => match input_type {
InputType::Text |
InputType::Password |
InputType::Email |
InputType::Url |
InputType::Tel |
InputType::Search => true,
_ => false,
},
Attr::Autocomplete(_) => match input_type {
InputType::Text |
InputType::Email |
InputType::Url |
InputType::Tel |
InputType::Search |
InputType::Date |
InputType::DateTime |
InputType::Month |
InputType::Week |
InputType::Time |
InputType::Range |
InputType::Color => true,
_ => false,
},
Attr::Autofocus => match input_type {
InputType::Button |
InputType::Submit |
InputType::Reset |
InputType::Image |
InputType::Hidden => false,
_ => true,
},
Attr::Size(_) |
Attr::Disabled |
Attr::Readonly |
Attr::Tabindex(_) => match input_type {
InputType::Hidden => false,
_ => true,
},
},
Element::Textarea => match self {
Attr::Any(_, _) |
Attr::Id(_) |
Attr::Title(_) |
Attr::Placeholder(_) |
Attr::Autocomplete(_) |
Attr::Autofocus |
Attr::Disabled |
Attr::Readonly |
Attr::Rows(_) |
Attr::Cols(_) |
Attr::Spellcheck(_) |
Attr::Wrap(_) => true,
_ => false,
},
Element::Select(_) => match self {
Attr::Any(_, _) |
Attr::Id(_) |
Attr::Title(_) |
Attr::Autocomplete(_) |
Attr::Autofocus |
Attr::Disabled |
Attr::Readonly |
Attr::Size(_) |
_ => false,
},
Element::Button(_) => match self {
Attr::Any(_, _) |
Attr::Id(_) |
Attr::Title(_) |
Attr::Autocomplete(_) |
Attr::Autofocus |
Attr::Disabled |
Attr::Readonly |
Attr::FormAction(_) |
Attr::FormEnctype(_) |
Attr::FormTarget(_) |
Attr::FormNoValidate |
Attr::Size(_) |
_ => false,
},
}
}
}