use dyn_clone::DynClone;
use crate::{error::CustomUserError, list_option::ListOption};
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub enum ErrorMessage {
#[default]
Default,
Custom(String),
}
impl<T> From<T> for ErrorMessage
where
T: ToString,
{
fn from(msg: T) -> Self {
Self::Custom(msg.to_string())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Validation {
Valid,
Invalid(ErrorMessage),
}
pub trait StringValidator: DynClone {
fn validate(&self, input: &str) -> Result<Validation, CustomUserError>;
}
impl Clone for Box<dyn StringValidator> {
fn clone(&self) -> Self {
dyn_clone::clone_box(&**self)
}
}
impl<F> StringValidator for F
where
F: Fn(&str) -> Result<Validation, CustomUserError> + Clone,
{
fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
(self)(input)
}
}
#[cfg(feature = "date")]
pub trait DateValidator: DynClone {
fn validate(&self, input: chrono::NaiveDate) -> Result<Validation, CustomUserError>;
}
#[cfg(feature = "date")]
impl Clone for Box<dyn DateValidator> {
fn clone(&self) -> Self {
dyn_clone::clone_box(&**self)
}
}
#[cfg(feature = "date")]
impl<F> DateValidator for F
where
F: Fn(chrono::NaiveDate) -> Result<Validation, CustomUserError> + Clone,
{
fn validate(&self, input: chrono::NaiveDate) -> Result<Validation, CustomUserError> {
(self)(input)
}
}
pub trait MultiOptionValidator<T: ?Sized>: DynClone {
fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError>;
}
impl<T> Clone for Box<dyn MultiOptionValidator<T>> {
fn clone(&self) -> Self {
dyn_clone::clone_box(&**self)
}
}
impl<F, T> MultiOptionValidator<T> for F
where
F: Fn(&[ListOption<&T>]) -> Result<Validation, CustomUserError> + Clone,
T: ?Sized,
{
fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
(self)(input)
}
}
pub trait CustomTypeValidator<T: ?Sized>: DynClone {
fn validate(&self, input: &T) -> Result<Validation, CustomUserError>;
}
impl<T> Clone for Box<dyn CustomTypeValidator<T>> {
fn clone(&self) -> Self {
dyn_clone::clone_box(&**self)
}
}
impl<F, T> CustomTypeValidator<T> for F
where
F: Fn(&T) -> Result<Validation, CustomUserError> + Clone,
T: ?Sized,
{
fn validate(&self, input: &T) -> Result<Validation, CustomUserError> {
(self)(input)
}
}
pub trait InquireLength {
fn inquire_length(&self) -> usize;
}
impl InquireLength for &str {
fn inquire_length(&self) -> usize {
use unicode_segmentation::UnicodeSegmentation;
self.graphemes(true).count()
}
}
impl<T> InquireLength for &[T] {
fn inquire_length(&self) -> usize {
self.len()
}
}
#[derive(Clone)]
pub struct ValueRequiredValidator {
message: String,
}
impl ValueRequiredValidator {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl Default for ValueRequiredValidator {
fn default() -> Self {
Self {
message: "A response is required.".to_owned(),
}
}
}
impl StringValidator for ValueRequiredValidator {
fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
Ok(if input.is_empty() {
Validation::Invalid(self.message.as_str().into())
} else {
Validation::Valid
})
}
}
#[macro_export]
#[cfg(feature = "macros")]
macro_rules! required {
() => {
$crate::validator::ValueRequiredValidator::default()
};
($message:expr) => {
$crate::validator::ValueRequiredValidator::new($message)
};
}
#[derive(Clone)]
pub struct MaxLengthValidator {
limit: usize,
message: String,
}
impl MaxLengthValidator {
pub fn new(limit: usize) -> Self {
Self {
limit,
message: format!("The length of the response should be at most {limit}"),
}
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
fn validate_inquire_length<T: InquireLength>(
&self,
input: T,
) -> Result<Validation, CustomUserError> {
Ok(if input.inquire_length() <= self.limit {
Validation::Valid
} else {
Validation::Invalid(self.message.as_str().into())
})
}
}
impl StringValidator for MaxLengthValidator {
fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
self.validate_inquire_length(input)
}
}
impl<T: ?Sized> MultiOptionValidator<T> for MaxLengthValidator {
fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
self.validate_inquire_length(input)
}
}
#[macro_export]
#[cfg(feature = "macros")]
macro_rules! max_length {
($length:expr) => {
$crate::validator::MaxLengthValidator::new($length)
};
($length:expr, $message:expr) => {
$crate::max_length!($length).with_message($message)
};
}
#[derive(Clone)]
pub struct MinLengthValidator {
limit: usize,
message: String,
}
impl MinLengthValidator {
pub fn new(limit: usize) -> Self {
Self {
limit,
message: format!("The length of the response should be at least {limit}"),
}
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
fn validate_inquire_length<T: InquireLength>(
&self,
input: T,
) -> Result<Validation, CustomUserError> {
Ok(if input.inquire_length() >= self.limit {
Validation::Valid
} else {
Validation::Invalid(self.message.as_str().into())
})
}
}
impl StringValidator for MinLengthValidator {
fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
self.validate_inquire_length(input)
}
}
impl<T: ?Sized> MultiOptionValidator<T> for MinLengthValidator {
fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
self.validate_inquire_length(input)
}
}
#[macro_export]
#[cfg(feature = "macros")]
macro_rules! min_length {
($length:expr) => {
$crate::validator::MinLengthValidator::new($length)
};
($length:expr, $message:expr) => {
$crate::min_length!($length).with_message($message)
};
}
#[derive(Clone)]
pub struct ExactLengthValidator {
length: usize,
message: String,
}
impl ExactLengthValidator {
pub fn new(length: usize) -> Self {
Self {
length,
message: format!("The length of the response should be {length}"),
}
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = message.into();
self
}
fn validate_inquire_length<T: InquireLength>(
&self,
input: T,
) -> Result<Validation, CustomUserError> {
Ok(if input.inquire_length() == self.length {
Validation::Valid
} else {
Validation::Invalid(self.message.as_str().into())
})
}
}
impl StringValidator for ExactLengthValidator {
fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
self.validate_inquire_length(input)
}
}
impl<T: ?Sized> MultiOptionValidator<T> for ExactLengthValidator {
fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
self.validate_inquire_length(input)
}
}
#[macro_export]
#[cfg(feature = "macros")]
macro_rules! length {
($length:expr) => {
$crate::validator::ExactLengthValidator::new($length)
};
($length:expr, $message:expr) => {
$crate::length!($length).with_message($message)
};
}
#[cfg(test)]
mod validators_test {
use crate::{
error::CustomUserError,
list_option::ListOption,
validator::{
ExactLengthValidator, MaxLengthValidator, MinLengthValidator, MultiOptionValidator,
StringValidator, Validation,
},
};
fn build_option_vec(len: usize) -> Vec<ListOption<&'static str>> {
let mut options = Vec::new();
for i in 0..len {
options.push(ListOption::new(i, ""));
}
options
}
#[test]
fn string_length_counts_graphemes() -> Result<(), CustomUserError> {
let validator = ExactLengthValidator::new(5);
let validator: &dyn StringValidator = &validator;
assert!(matches!(validator.validate("five!")?, Validation::Valid));
assert!(matches!(
validator.validate("♥️♥️♥️♥️♥️")?,
Validation::Valid
));
assert!(matches!(
validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
Validation::Valid
));
assert!(matches!(
validator.validate("five!!!")?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
Validation::Invalid(_)
));
Ok(())
}
#[test]
fn slice_length() -> Result<(), CustomUserError> {
let validator = ExactLengthValidator::new(5);
let validator: &dyn MultiOptionValidator<str> = &validator;
assert!(matches!(
validator.validate(&build_option_vec(5))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(4))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(6))?,
Validation::Invalid(_)
));
Ok(())
}
#[test]
fn string_max_length_counts_graphemes() -> Result<(), CustomUserError> {
let validator = MaxLengthValidator::new(5);
let validator: &dyn StringValidator = &validator;
assert!(matches!(validator.validate("")?, Validation::Valid));
assert!(matches!(validator.validate("five!")?, Validation::Valid));
assert!(matches!(
validator.validate("♥️♥️♥️♥️♥️")?,
Validation::Valid
));
assert!(matches!(
validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
Validation::Valid
));
assert!(matches!(
validator.validate("five!!!")?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate("♥️♥️♥️♥️♥️♥️")?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
Validation::Invalid(_)
));
Ok(())
}
#[test]
fn slice_max_length() -> Result<(), CustomUserError> {
let validator = MaxLengthValidator::new(5);
let validator: &dyn MultiOptionValidator<str> = &validator;
assert!(matches!(
validator.validate(&build_option_vec(0))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(1))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(2))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(3))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(4))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(5))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(6))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(7))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(8))?,
Validation::Invalid(_)
));
Ok(())
}
#[test]
fn string_min_length_counts_graphemes() -> Result<(), CustomUserError> {
let validator = MinLengthValidator::new(5);
let validator: &dyn StringValidator = &validator;
assert!(matches!(validator.validate("")?, Validation::Invalid(_)));
assert!(matches!(
validator.validate("♥️♥️♥️♥️")?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate("mike")?,
Validation::Invalid(_)
));
assert!(matches!(validator.validate("five!")?, Validation::Valid));
assert!(matches!(validator.validate("five!!!")?, Validation::Valid));
assert!(matches!(
validator.validate("♥️♥️♥️♥️♥️")?,
Validation::Valid
));
assert!(matches!(
validator.validate("♥️♥️♥️♥️♥️♥️")?,
Validation::Valid
));
assert!(matches!(
validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
Validation::Valid
));
assert!(matches!(
validator.validate("🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️🤦🏼♂️")?,
Validation::Valid
));
Ok(())
}
#[test]
fn slice_min_length() -> Result<(), CustomUserError> {
let validator = MinLengthValidator::new(5);
let validator: &dyn MultiOptionValidator<str> = &validator;
assert!(matches!(
validator.validate(&build_option_vec(0))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(1))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(2))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(3))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(4))?,
Validation::Invalid(_)
));
assert!(matches!(
validator.validate(&build_option_vec(5))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(6))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(7))?,
Validation::Valid
));
assert!(matches!(
validator.validate(&build_option_vec(8))?,
Validation::Valid
));
Ok(())
}
}