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