keyfork/keyfork-prompt/src/validators.rs

171 lines
5.4 KiB
Rust
Raw Normal View History

2024-01-09 07:21:46 +00:00
#![allow(clippy::type_complexity)]
use std::ops::RangeInclusive;
2024-01-09 07:21:46 +00:00
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),
}
/// Validate that a PIN is of a certain length and matches a range of characters.
2024-01-09 07:21:46 +00:00
#[derive(Default, Clone)]
pub struct PinValidator {
pub min_length: Option<usize>,
pub max_length: Option<usize>,
pub range: Option<RangeInclusive<char>>,
2024-01-09 07:21:46 +00:00
}
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)
})
}
}
#[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<usize>),
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<WordLength>,
}
impl Validator for MnemonicValidator {
type Output = Mnemonic;
type Error = MnemonicValidationError;
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Mnemonic, Self::Error>> {
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<const N: usize> {
pub word_lengths: [usize; N],
}
impl<const N: usize> Validator for MnemonicSetValidator<N> {
type Output = [Mnemonic; N];
type Error = MnemonicSetValidationError;
fn to_fn(&self) -> Box<dyn Fn(String) -> Result<Self::Output, Self::Error>> {
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::<Vec<_>>();
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"))
})
}
}
}