#![allow(clippy::type_complexity)] use std::ops::RangeInclusive; 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), } /// Validate that a PIN is of a certain length and matches a range of characters. #[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) }) } } #[cfg(feature = "mnemonic")] pub mod mnemonic { use std::{mem::MaybeUninit, ops::Range, str::FromStr}; use super::Validator; use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError}; #[derive(thiserror::Error, Debug)] pub enum MnemonicValidationError { #[error("Invalid word length: {0} does not match {1:?}")] InvalidLength(usize, WordLength), #[error("{0}")] MnemonicFromStrError(#[from] MnemonicFromStrError), } #[derive(Clone, Debug)] pub enum WordLength { Range(Range), Count(usize), } impl WordLength { fn matches(&self, word_count: usize) -> bool { match self { WordLength::Range(r) => r.contains(&word_count), WordLength::Count(c) => c == &word_count, } } } /// Validate a mnemonic of a range of word lengths or a specific length. #[derive(Default, Clone)] pub struct MnemonicValidator { pub word_length: Option, } impl Validator for MnemonicValidator { type Output = Mnemonic; type Error = MnemonicValidationError; fn to_fn(&self) -> Box Result> { let word_length = self.word_length.clone(); Box::new(move |s: String| match word_length.as_ref() { Some(wl) => { let count = s.split_whitespace().count(); if !wl.matches(count) { return Err(Self::Error::InvalidLength(count, wl.clone())); } let m = Mnemonic::from_str(&s)?; Ok(m) } None => { let m = Mnemonic::from_str(&s)?; Ok(m) } }) } } #[derive(thiserror::Error, Debug)] pub enum MnemonicSetValidationError { #[error("Invalid word length in set {0}: {1} != expected {2}")] InvalidSetLength(usize, usize, usize), #[error("Error parsing mnemonic set {0}: {1}")] MnemonicFromStrError(usize, MnemonicFromStrError), } /// Validate a set of mnemonics of a specific word length. #[derive(Clone)] pub struct MnemonicSetValidator { pub word_lengths: [usize; N], } impl Validator for MnemonicSetValidator { type Output = [Mnemonic; N]; type Error = MnemonicSetValidationError; fn to_fn(&self) -> Box Result> { let word_lengths = self.word_lengths; Box::new(move |s: String| { let mut counter: usize = 0; let mut output = Vec::with_capacity(N); for (word_set, word_length) in word_lengths.into_iter().enumerate() { let words = s .split_whitespace() .skip(counter) .take(word_length) .collect::>(); if words.len() != word_length { return Err(MnemonicSetValidationError::InvalidSetLength( word_set, words.len(), word_length, )); } let mnemonic = match Mnemonic::from_str(&words.join(" ")) { Ok(m) => m, Err(e) => return Err(Self::Error::MnemonicFromStrError(word_set, e)), }; output.push(mnemonic); counter += word_length; } Ok(output .try_into() .expect("vec with capacity of const N was not filled")) }) } } }