inquire/
validator.rs

1//! Traits and structs used by prompts to validate user input before
2//! returning the values to their callers.
3//!
4//! Validators receive the user input to a given prompt and decide whether
5//! they are valid, returning `Ok(Validation::Valid)` in the process, or
6//! invalid, returning `Ok(Validation::Invalid(ErrorMessage))`, where the
7//! `ErrorMessage` content is an error message to be displayed to the end user.
8//!
9//! Validators can also return errors, which propagate to the caller prompt
10//! and cause the prompt to return the error.
11//!
12//! This module also provides several macros as shorthands to the struct
13//! constructor functions, exported with the `macros` feature.
14
15use dyn_clone::DynClone;
16
17use crate::{error::CustomUserError, list_option::ListOption};
18
19/// Error message that is displayed to the users when their input is considered not
20/// valid by registered validators.
21#[derive(Clone, Default, Debug, PartialEq, Eq)]
22pub enum ErrorMessage {
23    /// No custom message is defined, a standard one defined in the set
24    /// [`RenderConfig`](crate::ui::RenderConfig) is used instead.
25    #[default]
26    Default,
27
28    /// Custom error message, used instead of the standard one.
29    Custom(String),
30}
31
32impl<T> From<T> for ErrorMessage
33where
34    T: ToString,
35{
36    fn from(msg: T) -> Self {
37        Self::Custom(msg.to_string())
38    }
39}
40
41/// The result type of validation operations when the execution of the validator
42/// function succeeds.
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub enum Validation {
45    /// Variant that indicates that the input value is valid according to the validator.
46    Valid,
47
48    /// Variant that indicates that the input value is invalid according to the validator.
49    ///
50    /// The member represents a custom error message that will be displayed to the user when present.
51    /// When empty a standard error message, configured via the RenderConfig struct, will be shown
52    /// instead.
53    Invalid(ErrorMessage),
54}
55
56/// Validator that receives a string slice as the input, such as [`Text`](crate::Text) and
57/// [`Password`](crate::Password).
58///
59/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
60///
61/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
62/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
63/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
64///
65/// # Examples
66///
67/// ```
68/// use inquire::validator::{StringValidator, Validation};
69///
70/// let validator = |input: &str| match input.chars().find(|c| c.is_numeric()) {
71///     Some(_) => Ok(Validation::Valid),
72///     None => Ok(Validation::Invalid(
73///         "Your password should contain at least 1 digit".into(),
74///     )),
75/// };
76///
77/// assert_eq!(Validation::Valid, validator.validate("hunter2")?);
78/// assert_eq!(
79///     Validation::Invalid("Your password should contain at least 1 digit".into()),
80///     validator.validate("password")?
81/// );
82/// # Ok::<(), inquire::error::CustomUserError>(())
83/// ```
84pub trait StringValidator: DynClone {
85    /// Confirm the given input string is a valid value.
86    fn validate(&self, input: &str) -> Result<Validation, CustomUserError>;
87}
88
89impl Clone for Box<dyn StringValidator> {
90    fn clone(&self) -> Self {
91        dyn_clone::clone_box(&**self)
92    }
93}
94
95impl<F> StringValidator for F
96where
97    F: Fn(&str) -> Result<Validation, CustomUserError> + Clone,
98{
99    fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
100        (self)(input)
101    }
102}
103
104/// Validator used in [`DateSelect`](crate::DateSelect) prompts.
105///
106/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
107///
108/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
109/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
110/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
111///
112/// # Examples
113///
114/// ```
115/// use chrono::{Datelike, NaiveDate, Weekday};
116/// use inquire::validator::{DateValidator, Validation};
117///
118/// let validator = |input: NaiveDate| {
119///     if input.weekday() == Weekday::Sat || input.weekday() == Weekday::Sun {
120///         Ok(Validation::Invalid("Weekends are not allowed".into()))
121///     } else {
122///         Ok(Validation::Valid)
123///     }
124/// };
125///
126/// assert_eq!(Validation::Valid, validator.validate(NaiveDate::from_ymd(2021, 7, 26))?);
127/// assert_eq!(
128///     Validation::Invalid("Weekends are not allowed".into()),
129///     validator.validate(NaiveDate::from_ymd(2021, 7, 25))?
130/// );
131/// # Ok::<(), inquire::error::CustomUserError>(())
132/// ```
133#[cfg(feature = "date")]
134pub trait DateValidator: DynClone {
135    /// Confirm the given input date is a valid value.
136    fn validate(&self, input: chrono::NaiveDate) -> Result<Validation, CustomUserError>;
137}
138
139#[cfg(feature = "date")]
140impl Clone for Box<dyn DateValidator> {
141    fn clone(&self) -> Self {
142        dyn_clone::clone_box(&**self)
143    }
144}
145
146#[cfg(feature = "date")]
147impl<F> DateValidator for F
148where
149    F: Fn(chrono::NaiveDate) -> Result<Validation, CustomUserError> + Clone,
150{
151    fn validate(&self, input: chrono::NaiveDate) -> Result<Validation, CustomUserError> {
152        (self)(input)
153    }
154}
155
156/// Validator used in [`MultiSelect`](crate::MultiSelect) prompts.
157///
158/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
159///
160/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
161/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
162/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
163///
164/// # Examples
165///
166/// ```
167/// use inquire::list_option::ListOption;
168/// use inquire::validator::{MultiOptionValidator, Validation};
169///
170/// let validator = |input: &[ListOption<&&str>]| {
171///     if input.len() <= 2 {
172///         Ok(Validation::Valid)
173///     } else {
174///         Ok(Validation::Invalid("You should select at most two options".into()))
175///     }
176/// };
177///
178/// let mut ans = vec![ListOption::new(0, &"a"), ListOption::new(1, &"b")];
179///
180/// assert_eq!(Validation::Valid, validator.validate(&ans[..])?);
181///
182/// ans.push(ListOption::new(3, &"d"));
183/// assert_eq!(
184///     Validation::Invalid("You should select at most two options".into()),
185///     validator.validate(&ans[..])?
186/// );
187/// # Ok::<(), inquire::error::CustomUserError>(())
188/// ```
189pub trait MultiOptionValidator<T: ?Sized>: DynClone {
190    /// Confirm the given input list is a valid value.
191    fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError>;
192}
193
194impl<T> Clone for Box<dyn MultiOptionValidator<T>> {
195    fn clone(&self) -> Self {
196        dyn_clone::clone_box(&**self)
197    }
198}
199
200impl<F, T> MultiOptionValidator<T> for F
201where
202    F: Fn(&[ListOption<&T>]) -> Result<Validation, CustomUserError> + Clone,
203    T: ?Sized,
204{
205    fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
206        (self)(input)
207    }
208}
209
210/// Validator used in [`CustomType`](crate::CustomType) prompts.
211///
212/// If the input provided by the user is valid, your validator should return `Ok(Validation::Valid)`.
213///
214/// If the input is not valid, your validator should return `Ok(Validation::Invalid(ErrorMessage))`,
215/// where the content of `ErrorMessage` is recommended to be a string whose content will be displayed
216/// to the user as an error message. It is also recommended that this value gives a helpful feedback to the user.
217///
218/// # Examples
219///
220/// ```
221/// use inquire::list_option::ListOption;
222/// use inquire::validator::{MultiOptionValidator, Validation};
223///
224/// let validator = |input: &[ListOption<&&str>]| {
225///     if input.len() <= 2 {
226///         Ok(Validation::Valid)
227///     } else {
228///         Ok(Validation::Invalid("You should select at most two options".into()))
229///     }
230/// };
231///
232/// let mut ans = vec![ListOption::new(0, &"a"), ListOption::new(1, &"b")];
233///
234/// assert_eq!(Validation::Valid, validator.validate(&ans[..])?);
235///
236/// ans.push(ListOption::new(3, &"d"));
237/// assert_eq!(
238///     Validation::Invalid("You should select at most two options".into()),
239///     validator.validate(&ans[..])?
240/// );
241/// # Ok::<(), inquire::error::CustomUserError>(())
242/// ```
243pub trait CustomTypeValidator<T: ?Sized>: DynClone {
244    /// Confirm the given input list is a valid value.
245    fn validate(&self, input: &T) -> Result<Validation, CustomUserError>;
246}
247
248impl<T> Clone for Box<dyn CustomTypeValidator<T>> {
249    fn clone(&self) -> Self {
250        dyn_clone::clone_box(&**self)
251    }
252}
253
254impl<F, T> CustomTypeValidator<T> for F
255where
256    F: Fn(&T) -> Result<Validation, CustomUserError> + Clone,
257    T: ?Sized,
258{
259    fn validate(&self, input: &T) -> Result<Validation, CustomUserError> {
260        (self)(input)
261    }
262}
263
264/// Custom trait to call correct method to retrieve input length.
265///
266/// The method can vary depending on the type of input.
267///
268/// String inputs should count the number of graphemes, via
269/// `.graphemes(true).count()`, instead of the number of bytes
270/// via `.len()`. While simple slices should keep using `.len()`
271pub trait InquireLength {
272    /// String inputs should count the number of graphemes, via
273    /// `.graphemes(true).count()`, instead of the number of bytes
274    /// via `.len()`. While simple slices keep using `.len()`
275    fn inquire_length(&self) -> usize;
276}
277
278impl InquireLength for &str {
279    fn inquire_length(&self) -> usize {
280        use unicode_segmentation::UnicodeSegmentation;
281
282        self.graphemes(true).count()
283    }
284}
285
286impl<T> InquireLength for &[T] {
287    fn inquire_length(&self) -> usize {
288        self.len()
289    }
290}
291
292/// Built-in validator that checks whether the answer is not empty.
293///
294/// # Examples
295///
296/// ```
297/// use inquire::validator::{StringValidator, Validation, ValueRequiredValidator};
298///
299/// let validator = ValueRequiredValidator::default();
300/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
301/// assert_eq!(Validation::Invalid("A response is required.".into()), validator.validate("")?);
302///
303/// let validator = ValueRequiredValidator::new("No empty!");
304/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
305/// assert_eq!(Validation::Invalid("No empty!".into()), validator.validate("")?);
306/// # Ok::<(), inquire::error::CustomUserError>(())
307/// ```
308#[derive(Clone)]
309pub struct ValueRequiredValidator {
310    message: String,
311}
312
313impl ValueRequiredValidator {
314    /// Create a new instance of this validator with given error message.
315    pub fn new(message: impl Into<String>) -> Self {
316        Self {
317            message: message.into(),
318        }
319    }
320}
321
322impl Default for ValueRequiredValidator {
323    /// Create a new instance of this validator with the default error message
324    /// `A response is required`.
325    fn default() -> Self {
326        Self {
327            message: "A response is required.".to_owned(),
328        }
329    }
330}
331
332impl StringValidator for ValueRequiredValidator {
333    fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
334        Ok(if input.is_empty() {
335            Validation::Invalid(self.message.as_str().into())
336        } else {
337            Validation::Valid
338        })
339    }
340}
341
342/// Shorthand for the built-in [`ValueRequiredValidator`] that checks whether the answer is not
343/// empty.
344///
345/// # Arguments
346///
347/// * `$message` - optional - Error message returned by the validator.
348///   Defaults to "A response is required."
349///
350/// # Examples
351///
352/// ```
353/// use inquire::{required, validator::{StringValidator, Validation}};
354///
355/// let validator = required!();
356/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
357/// assert_eq!(Validation::Invalid("A response is required.".into()), validator.validate("")?);
358///
359/// let validator = required!("No empty!");
360/// assert_eq!(Validation::Valid, validator.validate("Generic input")?);
361/// assert_eq!(Validation::Invalid("No empty!".into()), validator.validate("")?);
362/// # Ok::<(), inquire::error::CustomUserError>(())
363/// ```
364#[macro_export]
365#[cfg(feature = "macros")]
366macro_rules! required {
367    () => {
368        $crate::validator::ValueRequiredValidator::default()
369    };
370
371    ($message:expr) => {
372        $crate::validator::ValueRequiredValidator::new($message)
373    };
374}
375
376/// Built-in validator that checks whether the answer length is smaller than
377/// or equal to the specified threshold.
378///
379/// The validator uses a custom-built length function that
380/// has a special implementation for strings which counts the number of
381/// graphemes. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
382///
383/// # Examples
384///
385/// ```
386/// use inquire::validator::{MaxLengthValidator, StringValidator, Validation};
387///
388/// let validator = MaxLengthValidator::new(5);
389/// assert_eq!(Validation::Valid, validator.validate("Good")?);
390/// assert_eq!(
391///     Validation::Invalid("The length of the response should be at most 5".into()),
392///     validator.validate("Terrible")?,
393/// );
394///
395/// let validator = MaxLengthValidator::new(5).with_message("Not too large!");
396/// assert_eq!(Validation::Valid, validator.validate("Good")?);
397/// assert_eq!(Validation::Invalid("Not too large!".into()), validator.validate("Terrible")?);
398/// # Ok::<(), inquire::error::CustomUserError>(())
399/// ```
400#[derive(Clone)]
401pub struct MaxLengthValidator {
402    limit: usize,
403    message: String,
404}
405
406impl MaxLengthValidator {
407    /// Create a new instance of this validator, requiring at most the given length, otherwise
408    /// returning an error with default message.
409    pub fn new(limit: usize) -> Self {
410        Self {
411            limit,
412            message: format!("The length of the response should be at most {limit}"),
413        }
414    }
415
416    /// Define a custom error message returned by the validator.
417    /// Defaults to `The length of the response should be at most $length`.
418    pub fn with_message(mut self, message: impl Into<String>) -> Self {
419        self.message = message.into();
420        self
421    }
422
423    fn validate_inquire_length<T: InquireLength>(
424        &self,
425        input: T,
426    ) -> Result<Validation, CustomUserError> {
427        Ok(if input.inquire_length() <= self.limit {
428            Validation::Valid
429        } else {
430            Validation::Invalid(self.message.as_str().into())
431        })
432    }
433}
434
435impl StringValidator for MaxLengthValidator {
436    fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
437        self.validate_inquire_length(input)
438    }
439}
440
441impl<T: ?Sized> MultiOptionValidator<T> for MaxLengthValidator {
442    fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
443        self.validate_inquire_length(input)
444    }
445}
446
447/// Shorthand for the built-in [`MaxLengthValidator`] that checks whether the answer length is
448/// smaller than or equal to the specified threshold.
449///
450/// # Arguments
451///
452/// * `$length` - Maximum length of the input.
453/// * `$message` - optional - Error message returned by the validator.
454///   Defaults to "The length of the response should be at most $length"
455///
456/// # Examples
457///
458/// ```
459/// use inquire::{max_length, validator::{StringValidator, Validation}};
460///
461/// let validator = max_length!(5);
462/// assert_eq!(Validation::Valid, validator.validate("Good")?);
463/// assert_eq!(Validation::Invalid("The length of the response should be at most 5".into()), validator.validate("Terrible")?);
464///
465/// let validator = max_length!(5, "Not too large!");
466/// assert_eq!(Validation::Valid, validator.validate("Good")?);
467/// assert_eq!(Validation::Invalid("Not too large!".into()), validator.validate("Terrible")?);
468/// # Ok::<(), inquire::error::CustomUserError>(())
469/// ```
470#[macro_export]
471#[cfg(feature = "macros")]
472macro_rules! max_length {
473    ($length:expr) => {
474        $crate::validator::MaxLengthValidator::new($length)
475    };
476
477    ($length:expr, $message:expr) => {
478        $crate::max_length!($length).with_message($message)
479    };
480}
481
482/// Built-in validator that checks whether the answer length is larger than
483/// or equal to the specified threshold.
484///
485/// The validator uses a custom-built length function that
486/// has a special implementation for strings which counts the number of
487/// graphemes. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
488///
489/// # Examples
490///
491/// ```
492/// use inquire::validator::{MinLengthValidator, StringValidator, Validation};
493///
494/// let validator = MinLengthValidator::new(3);
495/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
496/// assert_eq!(
497///     Validation::Invalid("The length of the response should be at least 3".into()),
498///     validator.validate("No")?,
499/// );
500///
501/// let validator = MinLengthValidator::new(3).with_message("You have to give me more than that!");
502/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
503/// assert_eq!(
504///     Validation::Invalid("You have to give me more than that!".into()),
505///     validator.validate("No")?,
506/// );
507/// # Ok::<(), inquire::error::CustomUserError>(())
508/// ```
509#[derive(Clone)]
510pub struct MinLengthValidator {
511    limit: usize,
512    message: String,
513}
514
515impl MinLengthValidator {
516    /// Create a new instance of this validator, requiring at least the given length, otherwise
517    /// returning an error with default message.
518    pub fn new(limit: usize) -> Self {
519        Self {
520            limit,
521            message: format!("The length of the response should be at least {limit}"),
522        }
523    }
524
525    /// Define a custom error message returned by the validator.
526    /// Defaults to `The length of the response should be at least $length`.
527    pub fn with_message(mut self, message: impl Into<String>) -> Self {
528        self.message = message.into();
529        self
530    }
531
532    fn validate_inquire_length<T: InquireLength>(
533        &self,
534        input: T,
535    ) -> Result<Validation, CustomUserError> {
536        Ok(if input.inquire_length() >= self.limit {
537            Validation::Valid
538        } else {
539            Validation::Invalid(self.message.as_str().into())
540        })
541    }
542}
543
544impl StringValidator for MinLengthValidator {
545    fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
546        self.validate_inquire_length(input)
547    }
548}
549
550impl<T: ?Sized> MultiOptionValidator<T> for MinLengthValidator {
551    fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
552        self.validate_inquire_length(input)
553    }
554}
555
556/// Shorthand for the built-in [`MinLengthValidator`] that checks whether the answer length is
557/// larger than or equal to the specified threshold.
558///
559/// # Arguments
560///
561/// * `$length` - Minimum length of the input.
562/// * `$message` - optional - Error message returned by the validator.
563///   Defaults to "The length of the response should be at least $length"
564///
565/// # Examples
566///
567/// ```
568/// use inquire::{min_length, validator::{StringValidator, Validation}};
569///
570/// let validator = min_length!(3);
571/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
572/// assert_eq!(Validation::Invalid("The length of the response should be at least 3".into()), validator.validate("No")?);
573///
574/// let validator = min_length!(3, "You have to give me more than that!");
575/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
576/// assert_eq!(Validation::Invalid("You have to give me more than that!".into()), validator.validate("No")?);
577/// # Ok::<(), inquire::error::CustomUserError>(())
578/// ```
579#[macro_export]
580#[cfg(feature = "macros")]
581macro_rules! min_length {
582    ($length:expr) => {
583        $crate::validator::MinLengthValidator::new($length)
584    };
585
586    ($length:expr, $message:expr) => {
587        $crate::min_length!($length).with_message($message)
588    };
589}
590
591/// Built-in validator that checks whether the answer length is equal to
592/// the specified value.
593///
594/// The validator uses a custom-built length function that
595/// has a special implementation for strings which counts the number of
596/// graphemes. See this [StackOverflow question](https://stackoverflow.com/questions/46290655/get-the-string-length-in-characters-in-rust).
597///
598/// # Examples
599///
600/// ```
601/// use inquire::validator::{ExactLengthValidator, StringValidator, Validation};
602///
603/// let validator = ExactLengthValidator::new(3);
604/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
605/// assert_eq!(
606///     Validation::Invalid("The length of the response should be 3".into()),
607///     validator.validate("No")?,
608/// );
609///
610/// let validator = ExactLengthValidator::new(3).with_message("Three characters please.");
611/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
612/// assert_eq!(Validation::Invalid("Three characters please.".into()), validator.validate("No")?);
613/// # Ok::<(), inquire::error::CustomUserError>(())
614/// ```
615#[derive(Clone)]
616pub struct ExactLengthValidator {
617    length: usize,
618    message: String,
619}
620
621impl ExactLengthValidator {
622    /// Create a new instance of this validator, requiring exactly the given length, otherwise
623    /// returning an error with default message.
624    pub fn new(length: usize) -> Self {
625        Self {
626            length,
627            message: format!("The length of the response should be {length}"),
628        }
629    }
630
631    /// Define a custom error message returned by the validator.
632    /// Defaults to `The length of the response should be $length`.
633    pub fn with_message(mut self, message: impl Into<String>) -> Self {
634        self.message = message.into();
635        self
636    }
637
638    fn validate_inquire_length<T: InquireLength>(
639        &self,
640        input: T,
641    ) -> Result<Validation, CustomUserError> {
642        Ok(if input.inquire_length() == self.length {
643            Validation::Valid
644        } else {
645            Validation::Invalid(self.message.as_str().into())
646        })
647    }
648}
649
650impl StringValidator for ExactLengthValidator {
651    fn validate(&self, input: &str) -> Result<Validation, CustomUserError> {
652        self.validate_inquire_length(input)
653    }
654}
655
656impl<T: ?Sized> MultiOptionValidator<T> for ExactLengthValidator {
657    fn validate(&self, input: &[ListOption<&T>]) -> Result<Validation, CustomUserError> {
658        self.validate_inquire_length(input)
659    }
660}
661
662/// Shorthand for the built-in [`ExactLengthValidator`] that checks whether the answer length is
663/// equal to the specified value.
664///
665/// # Arguments
666///
667/// * `$length` - Expected length of the input.
668/// * `$message` - optional - Error message returned by the validator.
669///   Defaults to "The length of the response should be $length"
670///
671/// # Examples
672///
673/// ```
674/// use inquire::{length, validator::{StringValidator, Validation}};
675///
676/// let validator = length!(3);
677/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
678/// assert_eq!(Validation::Invalid("The length of the response should be 3".into()), validator.validate("No")?);
679///
680/// let validator = length!(3, "Three characters please.");
681/// assert_eq!(Validation::Valid, validator.validate("Yes")?);
682/// assert_eq!(Validation::Invalid("Three characters please.".into()), validator.validate("No")?);
683/// # Ok::<(), inquire::error::CustomUserError>(())
684/// ```
685#[macro_export]
686#[cfg(feature = "macros")]
687macro_rules! length {
688    ($length:expr) => {
689        $crate::validator::ExactLengthValidator::new($length)
690    };
691
692    ($length:expr, $message:expr) => {
693        $crate::length!($length).with_message($message)
694    };
695}
696
697#[cfg(test)]
698mod validators_test {
699    use crate::{
700        error::CustomUserError,
701        list_option::ListOption,
702        validator::{
703            ExactLengthValidator, MaxLengthValidator, MinLengthValidator, MultiOptionValidator,
704            StringValidator, Validation,
705        },
706    };
707
708    fn build_option_vec(len: usize) -> Vec<ListOption<&'static str>> {
709        let mut options = Vec::new();
710
711        for i in 0..len {
712            options.push(ListOption::new(i, ""));
713        }
714
715        options
716    }
717
718    #[test]
719    fn string_length_counts_graphemes() -> Result<(), CustomUserError> {
720        let validator = ExactLengthValidator::new(5);
721        let validator: &dyn StringValidator = &validator;
722
723        assert!(matches!(validator.validate("five!")?, Validation::Valid));
724        assert!(matches!(
725            validator.validate("♥️♥️♥️♥️♥️")?,
726            Validation::Valid
727        ));
728        assert!(matches!(
729            validator.validate("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️")?,
730            Validation::Valid
731        ));
732
733        assert!(matches!(
734            validator.validate("five!!!")?,
735            Validation::Invalid(_)
736        ));
737        assert!(matches!(
738            validator.validate("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️")?,
739            Validation::Invalid(_)
740        ));
741        assert!(matches!(
742            validator.validate("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️")?,
743            Validation::Invalid(_)
744        ));
745
746        Ok(())
747    }
748
749    #[test]
750    fn slice_length() -> Result<(), CustomUserError> {
751        let validator = ExactLengthValidator::new(5);
752        let validator: &dyn MultiOptionValidator<str> = &validator;
753
754        assert!(matches!(
755            validator.validate(&build_option_vec(5))?,
756            Validation::Valid
757        ));
758        assert!(matches!(
759            validator.validate(&build_option_vec(4))?,
760            Validation::Invalid(_)
761        ));
762        assert!(matches!(
763            validator.validate(&build_option_vec(6))?,
764            Validation::Invalid(_)
765        ));
766
767        Ok(())
768    }
769
770    #[test]
771    fn string_max_length_counts_graphemes() -> Result<(), CustomUserError> {
772        let validator = MaxLengthValidator::new(5);
773        let validator: &dyn StringValidator = &validator;
774
775        assert!(matches!(validator.validate("")?, Validation::Valid));
776        assert!(matches!(validator.validate("five!")?, Validation::Valid));
777        assert!(matches!(
778            validator.validate("♥️♥️♥️♥️♥️")?,
779            Validation::Valid
780        ));
781        assert!(matches!(
782            validator.validate("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️")?,
783            Validation::Valid
784        ));
785
786        assert!(matches!(
787            validator.validate("five!!!")?,
788            Validation::Invalid(_)
789        ));
790        assert!(matches!(
791            validator.validate("♥️♥️♥️♥️♥️♥️")?,
792            Validation::Invalid(_)
793        ));
794        assert!(matches!(
795            validator.validate("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️")?,
796            Validation::Invalid(_)
797        ));
798
799        Ok(())
800    }
801
802    #[test]
803    fn slice_max_length() -> Result<(), CustomUserError> {
804        let validator = MaxLengthValidator::new(5);
805        let validator: &dyn MultiOptionValidator<str> = &validator;
806
807        assert!(matches!(
808            validator.validate(&build_option_vec(0))?,
809            Validation::Valid
810        ));
811        assert!(matches!(
812            validator.validate(&build_option_vec(1))?,
813            Validation::Valid
814        ));
815        assert!(matches!(
816            validator.validate(&build_option_vec(2))?,
817            Validation::Valid
818        ));
819        assert!(matches!(
820            validator.validate(&build_option_vec(3))?,
821            Validation::Valid
822        ));
823        assert!(matches!(
824            validator.validate(&build_option_vec(4))?,
825            Validation::Valid
826        ));
827        assert!(matches!(
828            validator.validate(&build_option_vec(5))?,
829            Validation::Valid
830        ));
831        assert!(matches!(
832            validator.validate(&build_option_vec(6))?,
833            Validation::Invalid(_)
834        ));
835        assert!(matches!(
836            validator.validate(&build_option_vec(7))?,
837            Validation::Invalid(_)
838        ));
839        assert!(matches!(
840            validator.validate(&build_option_vec(8))?,
841            Validation::Invalid(_)
842        ));
843
844        Ok(())
845    }
846
847    #[test]
848    fn string_min_length_counts_graphemes() -> Result<(), CustomUserError> {
849        let validator = MinLengthValidator::new(5);
850        let validator: &dyn StringValidator = &validator;
851
852        assert!(matches!(validator.validate("")?, Validation::Invalid(_)));
853        assert!(matches!(
854            validator.validate("♥️♥️♥️♥️")?,
855            Validation::Invalid(_)
856        ));
857        assert!(matches!(
858            validator.validate("mike")?,
859            Validation::Invalid(_)
860        ));
861
862        assert!(matches!(validator.validate("five!")?, Validation::Valid));
863        assert!(matches!(validator.validate("five!!!")?, Validation::Valid));
864        assert!(matches!(
865            validator.validate("♥️♥️♥️♥️♥️")?,
866            Validation::Valid
867        ));
868        assert!(matches!(
869            validator.validate("♥️♥️♥️♥️♥️♥️")?,
870            Validation::Valid
871        ));
872        assert!(matches!(
873            validator.validate("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️")?,
874            Validation::Valid
875        ));
876        assert!(matches!(
877            validator.validate("🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️🤦🏼‍♂️")?,
878            Validation::Valid
879        ));
880
881        Ok(())
882    }
883
884    #[test]
885    fn slice_min_length() -> Result<(), CustomUserError> {
886        let validator = MinLengthValidator::new(5);
887        let validator: &dyn MultiOptionValidator<str> = &validator;
888
889        assert!(matches!(
890            validator.validate(&build_option_vec(0))?,
891            Validation::Invalid(_)
892        ));
893        assert!(matches!(
894            validator.validate(&build_option_vec(1))?,
895            Validation::Invalid(_)
896        ));
897        assert!(matches!(
898            validator.validate(&build_option_vec(2))?,
899            Validation::Invalid(_)
900        ));
901        assert!(matches!(
902            validator.validate(&build_option_vec(3))?,
903            Validation::Invalid(_)
904        ));
905        assert!(matches!(
906            validator.validate(&build_option_vec(4))?,
907            Validation::Invalid(_)
908        ));
909        assert!(matches!(
910            validator.validate(&build_option_vec(5))?,
911            Validation::Valid
912        ));
913        assert!(matches!(
914            validator.validate(&build_option_vec(6))?,
915            Validation::Valid
916        ));
917        assert!(matches!(
918            validator.validate(&build_option_vec(7))?,
919            Validation::Valid
920        ));
921        assert!(matches!(
922            validator.validate(&build_option_vec(8))?,
923            Validation::Valid
924        ));
925
926        Ok(())
927    }
928}