//! Validator and parser types. #![allow(clippy::type_complexity)] use std::ops::RangeInclusive; /// A trait to create validator functions. pub trait Validator { /// The output of the validator function. type Output; /// The error type returned from the validator function. type Error; /// Create a validator function from the given parameters. fn to_fn(&self) -> Box Result>>; } /// A PIN could not be validated from the given input. #[derive(thiserror::Error, Debug)] pub enum PinError { /// The provided PIN was too short. #[error("PIN too short: {0} < {1}")] TooShort(usize, usize), /// The provided PIN was too long. #[error("PIN too long: {0} > {1}")] TooLong(usize, usize), /// The PIN contained invalid characters. #[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 { /// The minimum length of provided PINs. pub min_length: Option, /// The maximum length of provided PINs. pub max_length: Option, /// The characters allowed by the PIN parser. 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(Box::new(PinError::TooShort(len, min_len))); } if len > max_len { return Err(Box::new(PinError::TooLong(len, max_len))); } for (index, ch) in s.chars().enumerate() { if !range.contains(&ch) { return Err(Box::new(PinError::InvalidCharacters(ch, index))); } } Ok(s) }) } } #[cfg(feature = "mnemonic")] pub mod mnemonic { //! Validators for mnemonics. use std::{ops::Range, str::FromStr}; use super::Validator; use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError}; use keyfork_bug::bug; /// A mnemonic could not be validated from the given input. #[derive(thiserror::Error, Debug)] pub enum MnemonicValidationError { /// The provided mnemonic had an unexpected word length. #[error("Invalid word length: {0} does not match {1:?}")] InvalidLength(usize, WordLength), /// A mnemonic could not be parsed from the given mnemonic. #[error("{0}")] MnemonicFromStrError(#[from] MnemonicFromStrError), } /// The mnemonic had an unexpected word length. #[derive(Clone, Debug)] pub enum WordLength { /// The bounds of a mnemonic. Range(Range), /// The exact count of words. 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 { /// The allowed word length of provided mnemonics. 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(Box::new(Self::Error::InvalidLength(count, wl.clone()))); } let m = Mnemonic::from_str(&s)?; Ok(m) } None => { let m = Mnemonic::from_str(&s)?; Ok(m) } }) } } /// A mnemonic of a given choice of lengths. For example, a 128-bit or 256-bit BIP-0032 seed. #[derive(thiserror::Error, Debug)] pub enum MnemonicChoiceValidationError { /// The provided mnemonic did not match any of the valid ranges. #[error("Invalid word length: {0} was not in any {1:?}")] InvalidLength(usize, Vec), /// A mnemonic could not be parsed from the provided mnemonic. #[error("{0}")] MnemonicFromStrError(#[from] MnemonicFromStrError), } /// Validate a single mnemonic against a set of possible word lengths. #[derive(Clone)] pub struct MnemonicChoiceValidator { /// The accepted [`WordLength`] of the mnemonic. pub word_lengths: [WordLength; N], } impl Validator for MnemonicChoiceValidator { type Output = Mnemonic; type Error = MnemonicChoiceValidationError; fn to_fn(&self) -> Box Result>> { let word_lengths = self.word_lengths.clone(); Box::new(move |s: String| { let count = s.split_whitespace().count(); for word_length in &word_lengths { if word_length.matches(count) { let m = Mnemonic::from_str(&s)?; return Ok(m); } } Err(Box::new(MnemonicChoiceValidationError::InvalidLength( count, word_lengths.to_vec(), ))) }) } } /// A mnemonic in the set of mnemonics could not be validated from the given inputs. #[derive(thiserror::Error, Debug)] pub enum MnemonicSetValidationError { /// The provided mnemonic did not have the correct amount of words. #[error("Invalid word length in set {0}: {1} != expected {2}")] InvalidSetLength(usize, usize, usize), /// A mnemonic could not be parsed from the provided mnemonics. #[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 { /// The exact word lengths of all mnemonics. Unlike [`MnemonicValidator`], ranges of words /// are not allowed. 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(Box::new(MnemonicSetValidationError::InvalidSetLength( word_set, words.len(), word_length, ))); } let mnemonic = match Mnemonic::from_str(&words.join(" ")) { Ok(m) => m, Err(e) => { return Err(Box::new(Self::Error::MnemonicFromStrError(word_set, e))) } }; output.push(mnemonic); counter += word_length; } Ok(output .try_into() .expect(bug!("vec with capacity of const N was not filled"))) }) } } }