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}