55 lines
1.5 KiB
Rust
55 lines
1.5 KiB
Rust
|
#![allow(clippy::type_complexity)]
|
||
|
|
||
|
pub trait Validator {
|
||
|
type Output;
|
||
|
type Error;
|
||
|
|
||
|
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Self::Output, Self::Error>>;
|
||
|
}
|
||
|
|
||
|
#[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<usize>,
|
||
|
pub max_length: Option<usize>,
|
||
|
pub range: Option<std::ops::RangeInclusive<char>>,
|
||
|
}
|
||
|
|
||
|
impl Validator for PinValidator {
|
||
|
type Output = String;
|
||
|
type Error = PinError;
|
||
|
|
||
|
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<String, PinError>> {
|
||
|
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)
|
||
|
})
|
||
|
}
|
||
|
}
|