use std::cmp::Ordering;
use regex::Regex;
use crate::{FieldRuleFault, Text, text, values::FormScalar};
pub(super) type Order = fn(&str, &str) -> Option<Ordering>;
pub(super) fn order<T: FormScalar>(a: &str, b: &str) -> Option<Ordering> {
let a = T::parse_form(a.trim()).ok()?;
let b = T::parse_form(b.trim()).ok()?;
a.order(&b)
}
#[derive(Default)]
pub(crate) struct Rules {
pub(crate) min_length: Option<usize>,
pub(crate) max_length: Option<usize>,
pub(crate) min: Option<String>,
pub(crate) max: Option<String>,
pub(crate) pattern: Option<Pattern>,
}
pub(crate) struct Pattern {
pub(crate) source: String,
regex: Result<Regex, String>,
}
impl Pattern {
pub(crate) fn new(source: String) -> Self {
let regex = Regex::new(&format!("^(?:{source})$")).map_err(|error| error.to_string());
Self { source, regex }
}
}
impl Rules {
pub(crate) fn faults(&self, order: Order) -> Vec<FieldRuleFault> {
let mut faults = Vec::new();
for bound in [&self.min, &self.max].into_iter().flatten() {
if order(bound, bound).is_none() {
faults.push(FieldRuleFault::UnorderedBound(bound.clone()));
}
}
if let Some(Pattern {
regex: Err(error), ..
}) = &self.pattern
{
faults.push(FieldRuleFault::InvalidPattern(error.clone()));
}
let lengths_cross =
matches!((self.min_length, self.max_length), (Some(min), Some(max)) if min > max);
let bounds_cross = match (&self.min, &self.max) {
(Some(min), Some(max)) => order(min, max) == Some(Ordering::Greater),
_ => false,
};
if lengths_cross || bounds_cross {
faults.push(FieldRuleFault::EmptyRange);
}
faults
}
pub(crate) fn refusal(
&self,
label: &Text,
value: &str,
order: Order,
shown: impl Fn(&str) -> String,
) -> Option<Text> {
let length = value.chars().count();
if let Some(min) = self.min_length
&& length < min
{
return Some(text!("tablo-field-min-length", label = label, min = min));
}
if let Some(max) = self.max_length
&& length > max
{
return Some(text!("tablo-field-max-length", label = label, max = max));
}
if let Some(min) = &self.min
&& order(value, min) == Some(Ordering::Less)
{
return Some(text!("tablo-field-min", label = label, min = shown(min)));
}
if let Some(max) = &self.max
&& order(value, max) == Some(Ordering::Greater)
{
return Some(text!("tablo-field-max", label = label, max = shown(max)));
}
if let Some(Pattern {
regex: Ok(regex), ..
}) = &self.pattern
&& !regex.is_match(value)
{
return Some(text!("tablo-field-pattern", label = label));
}
None
}
}