#![allow(clippy::type_complexity)] pub trait Validator { type Output; type Error; fn to_fn(&self) -> Box Result>; } #[derive(thiserror::Error, Debug)] pub enum PinError { #[error("PIN too short: {0} < {1}")] TooShort(usize, usize), #[error("PIN too long: {0} > {1}")] TooLong(usize, usize), #[error("PIN contained invalid characters (found {0} at position {1})")] InvalidCharacters(char, usize), } #[derive(Default, Clone)] pub struct PinValidator { pub min_length: Option, pub max_length: Option, pub range: Option>, } impl Validator for PinValidator { type Output = String; type Error = PinError; fn to_fn(&self) -> Box Result> { let min_len = self.min_length.unwrap_or(usize::MIN); let max_len = self.max_length.unwrap_or(usize::MAX); let range = self.range.clone().unwrap_or('0'..='9'); Box::new(move |mut s: String| { s.truncate(s.trim_end().len()); let len = s.len(); if len < min_len { return Err(PinError::TooShort(len, min_len)); } if len > max_len { return Err(PinError::TooLong(len, max_len)); } for (index, ch) in s.chars().enumerate() { if !range.contains(&ch) { return Err(PinError::InvalidCharacters(ch, index)); } } Ok(s) }) } }