pub type Validator = Box<dyn Fn(&str) -> Result<(), String>>;
pub type CharFilter = Box<dyn Fn(char) -> bool>;
pub fn non_empty() -> Validator {
Box::new(|value: &str| {
if value.trim().is_empty() {
Err("must not be empty".to_string())
} else {
Ok(())
}
})
}
pub fn min_len(min: usize) -> Validator {
Box::new(move |value: &str| {
if value.chars().count() < min {
Err(format!("must be at least {min} characters"))
} else {
Ok(())
}
})
}
pub fn digits() -> CharFilter {
Box::new(|ch: char| ch.is_ascii_digit())
}
pub fn decimal() -> CharFilter {
Box::new(|ch: char| ch.is_ascii_digit() || ch == '.' || ch == '-')
}
pub fn alpha() -> CharFilter {
Box::new(|ch: char| ch.is_alphabetic())
}
pub fn alnum() -> CharFilter {
Box::new(|ch: char| ch.is_alphanumeric())
}
pub fn no_space() -> CharFilter {
Box::new(|ch: char| !ch.is_whitespace())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn non_empty_rejects_blank() {
assert!(non_empty()(" ").is_err());
assert!(non_empty()("x").is_ok());
}
#[test]
fn min_len_counts_characters() {
assert!(min_len(3)("ab").is_err());
assert!(min_len(3)("abc").is_ok());
}
#[test]
fn digit_filter_accepts_only_digits() {
assert!(digits()('5'));
assert!(!digits()('a'));
}
}