valid/constraint/
mod.rs

1//! Constraints defined by this crate
2//!
3//! For each constraint the possible error codes are defined as a set of
4//! constants. The name of the constants follow the naming convention:
5//!
6//! ```text,ignore
7//! INVALID_<constraint-name>[_<variant>]
8//! ```
9//!
10//! The <variant> part is optional and only present if the constraint has some
11//! variants. The name of a constraint and its variants is converted to
12//! screaming snake case. The string values of the error codes follow a similar
13//! naming convention but use a dash (`-`) instead of the underscore to separate
14//! terms. Thus the codes are compatible with the convention used in the
15//! [_fluent_] project.
16//!
17//! [_fluent_]: https://projectfluent.org/
18
19use crate::property::{
20    HasCharCount, HasCheckedValue, HasDecimalDigits, HasEmptyValue, HasLength, HasMember,
21    HasZeroValue,
22};
23use crate::{
24    invalid_optional_value, invalid_relation, invalid_value, FieldName, RelatedFields, Validate,
25    Validation, Value,
26};
27use std::convert::TryFrom;
28
29/// Error code: the value does not assert to true (`AssertTrue` constraint)
30pub const INVALID_ASSERT_TRUE: &str = "invalid-assert-true";
31
32/// Error code: the value does not assert to false (`AssertFalse` constraint)
33pub const INVALID_ASSERT_FALSE: &str = "invalid-assert-false";
34
35/// Error code: the value is empty (`NotEmpty` constraint)
36pub const INVALID_NOT_EMPTY: &str = "invalid-not-empty";
37
38/// Error code: the length is not the exactly the specified value
39/// (`Length::Exact` constraint)
40pub const INVALID_LENGTH_EXACT: &str = "invalid-length-exact";
41
42/// Error code: the length is not less or equal the specified maximum
43/// (`Length::Max` constraint)
44pub const INVALID_LENGTH_MAX: &str = "invalid-length-max";
45
46/// Error code: the length is not greater or equal the specified minimum
47/// (`Length::Min` constraint)
48pub const INVALID_LENGTH_MIN: &str = "invalid-length-min";
49
50/// Error code: the number of characters is not exactly the specified value
51/// (`CharCount::Exact` constraint)
52pub const INVALID_CHAR_COUNT_EXACT: &str = "invalid-char-count-exact";
53
54/// Error code: the number of characters is not less or equal the specified
55/// maximum (`CharCount::Max` constraint)
56pub const INVALID_CHAR_COUNT_MAX: &str = "invalid-char-count-max";
57
58/// Error code: the number of characters is not greater or equal the specified
59/// minimum (`CharCount::Min` constraint)
60pub const INVALID_CHAR_COUNT_MIN: &str = "invalid-char-count-min";
61
62/// Error code: the value is not exactly the specified value
63/// (`Bound::Exact` constraint)
64pub const INVALID_BOUND_EXACT: &str = "invalid-bound-exact";
65
66/// Error code: the value is not less than or equal to the specified maximum
67/// (`Bound::ClosedRange` or `Bound::OpenClosedRange` constraint)
68pub const INVALID_BOUND_CLOSED_MAX: &str = "invalid-bound-closed-max";
69
70/// Error code: the value is not greater than or equal to the specified minimum
71/// (`Bound::ClosedRange` or `Bound::ClosedOpenRange` constraint)
72pub const INVALID_BOUND_CLOSED_MIN: &str = "invalid-bound-closed-min";
73
74/// Error code: the value is not less than the specified maximum
75/// (`Bound::OpenRange` or `Bound::ClosedOpenRange` constraint)
76pub const INVALID_BOUND_OPEN_MAX: &str = "invalid-bound-open-max";
77
78/// Error code: the value is not greater than the specified minimum
79/// (`Bound::OpenRange` or `Bound::OpenClosedRange` constraint)
80pub const INVALID_BOUND_OPEN_MIN: &str = "invalid-bound-open-min";
81
82/// Error code: the value is zero (`NonZero` constraint)
83pub const INVALID_NON_ZERO: &str = "invalid-non-zero";
84
85/// Error code: the number of integer digits is not less than or equal to the
86/// specified maximum (`Digits::integer` constraint)
87pub const INVALID_DIGITS_INTEGER: &str = "invalid-digits-integer";
88
89/// Error code: the number of fraction digits is not less than or equal to the
90/// specified maximum (`Digits::fraction` constraint)
91pub const INVALID_DIGITS_FRACTION: &str = "invalid-digits-fraction";
92
93/// Error code: the value does not contain the specified member element
94/// (`Contains` constraint)
95pub const INVALID_CONTAINS_ELEMENT: &str = "invalid-contains-element";
96
97/// Error code: the two values do not match (`MustMatch` constraint)
98pub const INVALID_MUST_MATCH: &str = "invalid-must-match";
99
100/// Error code: the first value is not less than or equal to the second value
101/// (`MustDefineRange::Inclusive` constraint)
102pub const INVALID_MUST_DEFINE_RANGE_INCLUSIVE: &str = "invalid-must-define-range-inclusive";
103
104/// Error code: the first value is not less than the second value
105/// (`MustDefineRange::Exclusive` constraint)
106pub const INVALID_MUST_DEFINE_RANGE_EXCLUSIVE: &str = "invalid-must-define-range-exclusive";
107
108/// The value must be true.
109///
110/// The validation function can be applied in the [`FieldName`] context.
111/// It is implemented for all types `T` that implement the [`HasCheckedValue`]
112/// property trait.
113///
114/// [`FieldName`]: ../core/struct.FieldName.html
115/// [`HasCheckedValue`]: ../property/trait.HasCheckedValue.html
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub struct AssertTrue;
118
119impl<T> Validate<AssertTrue, FieldName> for T
120where
121    T: HasCheckedValue,
122{
123    fn validate(
124        self,
125        name: impl Into<FieldName>,
126        _constraint: &AssertTrue,
127    ) -> Validation<AssertTrue, Self> {
128        if self.is_checked_value() {
129            Validation::success(self)
130        } else {
131            Validation::failure(vec![invalid_value(INVALID_ASSERT_TRUE, name, false, true)])
132        }
133    }
134}
135
136/// The value must be false.
137///
138/// The validation function can be applied in the [`FieldName`] context.
139/// It is implemented for all types `T` that implement the [`HasCheckedValue`]
140/// property trait.
141///
142/// [`FieldName`]: ../core/struct.FieldName.html
143/// [`HasCheckedValue`]: ../property/trait.HasCheckedValue.html
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct AssertFalse;
146
147impl<T> Validate<AssertFalse, FieldName> for T
148where
149    T: HasCheckedValue,
150{
151    fn validate(
152        self,
153        name: impl Into<FieldName>,
154        _constraint: &AssertFalse,
155    ) -> Validation<AssertFalse, Self> {
156        if self.is_checked_value() {
157            Validation::failure(vec![invalid_value(INVALID_ASSERT_FALSE, name, true, false)])
158        } else {
159            Validation::success(self)
160        }
161    }
162}
163
164/// The value must not be empty.
165///
166/// The validation function can be applied in the [`FieldName`] context.
167/// It is implemented for all types `T` that implement the [`HasEmptyValue`]
168/// property trait.
169///
170/// [`FieldName`]: ../core/struct.FieldName.html
171/// [`HasEmptyValue`]: ../property/trait.HasEmptyValue.html
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct NotEmpty;
174
175impl<T> Validate<NotEmpty, FieldName> for T
176where
177    T: HasEmptyValue,
178{
179    fn validate(
180        self,
181        name: impl Into<FieldName>,
182        _constraint: &NotEmpty,
183    ) -> Validation<NotEmpty, Self> {
184        if self.is_empty_value() {
185            Validation::failure(vec![invalid_optional_value(
186                INVALID_NOT_EMPTY,
187                name,
188                None,
189                None,
190            )])
191        } else {
192            Validation::success(self)
193        }
194    }
195}
196
197/// The length of a value must be within some bounds.
198///
199/// The validation function can be applied in the [`FieldName`] context.
200/// It is implemented for all types `T` that implement the [`HasLength`]
201/// property trait.
202///
203/// [`FieldName`]: ../core/struct.FieldName.html
204/// [`HasLength`]: ../property/trait.HasLength.html
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum Length {
207    /// The length of the value must be less than or equal to the specified
208    /// maximum
209    Max(usize),
210    /// The length of the value must be greater than or equal to the specified
211    /// minimum
212    Min(usize),
213    /// The length of the value must be between the specified minimum and
214    /// maximum (inclusive)
215    MinMax(usize, usize),
216    /// The value must be of an exact length
217    Exact(usize),
218}
219
220impl<T> Validate<Length, FieldName> for T
221where
222    T: HasLength,
223{
224    fn validate(self, name: impl Into<FieldName>, constraint: &Length) -> Validation<Length, Self> {
225        let length = self.length();
226        if let Some((code, expected)) = match *constraint {
227            Length::Max(max) => {
228                if length > max {
229                    Some((INVALID_LENGTH_MAX, max))
230                } else {
231                    None
232                }
233            }
234            Length::Min(min) => {
235                if length < min {
236                    Some((INVALID_LENGTH_MIN, min))
237                } else {
238                    None
239                }
240            }
241            Length::MinMax(min, max) => {
242                if length < min {
243                    Some((INVALID_LENGTH_MIN, min))
244                } else if length > max {
245                    Some((INVALID_LENGTH_MAX, max))
246                } else {
247                    None
248                }
249            }
250            Length::Exact(exact_len) => {
251                if length != exact_len {
252                    Some((INVALID_LENGTH_EXACT, exact_len))
253                } else {
254                    None
255                }
256            }
257        } {
258            let actual = Value::try_from(length).ok();
259            let expected = Value::try_from(expected).ok();
260            Validation::failure(vec![invalid_optional_value(code, name, actual, expected)])
261        } else {
262            Validation::success(self)
263        }
264    }
265}
266
267/// The number of characters must be within some bounds.
268///
269/// The validation function can be applied in the [`FieldName`] context.
270/// It is implemented for all types `T` that implement the [`HasCharCount`]
271/// property trait.
272///
273/// [`FieldName`]: ../core/struct.FieldName.html
274/// [`HasCharCount`]: ../property/trait.HasCharCount.html
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum CharCount {
277    /// The number of characters must be less than or equal to the specified
278    /// maximum
279    Max(usize),
280    /// The number of characters must be greater than or equal to the specified
281    /// minimum
282    Min(usize),
283    /// The number of characters must be between the specified minimum and
284    /// maximum (inclusive)
285    MinMax(usize, usize),
286    /// The number of characters must be equal to the specified amount
287    Exact(usize),
288}
289
290impl<T> Validate<CharCount, FieldName> for T
291where
292    T: HasCharCount,
293{
294    fn validate(
295        self,
296        name: impl Into<FieldName>,
297        constraint: &CharCount,
298    ) -> Validation<CharCount, Self> {
299        let char_count = self.char_count();
300        if let Some((code, expected)) = match *constraint {
301            CharCount::Max(max) => {
302                if char_count > max {
303                    Some((INVALID_CHAR_COUNT_MAX, max))
304                } else {
305                    None
306                }
307            }
308            CharCount::Min(min) => {
309                if char_count < min {
310                    Some((INVALID_CHAR_COUNT_MIN, min))
311                } else {
312                    None
313                }
314            }
315            CharCount::MinMax(min, max) => {
316                if char_count < min {
317                    Some((INVALID_CHAR_COUNT_MIN, min))
318                } else if char_count > max {
319                    Some((INVALID_CHAR_COUNT_MAX, max))
320                } else {
321                    None
322                }
323            }
324            CharCount::Exact(exact_val) => {
325                if char_count != exact_val {
326                    Some((INVALID_CHAR_COUNT_EXACT, exact_val))
327                } else {
328                    None
329                }
330            }
331        } {
332            let actual = Value::try_from(char_count).ok();
333            let expected = Value::try_from(expected).ok();
334            Validation::failure(vec![invalid_optional_value(code, name, actual, expected)])
335        } else {
336            Validation::success(self)
337        }
338    }
339}
340
341/// The value must be within some bounds.
342///
343/// The validation function can be applied in the [`FieldName`] context.
344/// It is implemented for all types `T` that implement the `PartialOrd` trait
345/// and `Into<Value>`.
346///
347/// [`FieldName`]: ../core/struct.FieldName.html
348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub enum Bound<T> {
350    /// The value must be between the specified minimum (inclusive) and
351    /// maximum (inclusive)
352    ClosedRange(T, T),
353    /// The value must be between the specified minimum (inclusive) and
354    /// maximum (exclusive)
355    ClosedOpenRange(T, T),
356    /// The value must be between the specified minimum (exclusive) and
357    /// maximum (inclusive)
358    OpenClosedRange(T, T),
359    /// The value must be between the specified minimum (exclusive) and
360    /// maximum (exclusive)
361    OpenRange(T, T),
362    /// The value must have the specified value
363    Exact(T),
364}
365
366impl<T> Validate<Bound<T>, FieldName> for T
367where
368    T: PartialOrd + Clone + Into<Value>,
369{
370    fn validate(
371        self,
372        name: impl Into<FieldName>,
373        constraint: &Bound<T>,
374    ) -> Validation<Bound<T>, Self> {
375        if let Some((code, expected)) = match constraint {
376            Bound::ClosedRange(min, max) => {
377                if self < *min {
378                    Some((INVALID_BOUND_CLOSED_MIN, min.clone()))
379                } else if self > *max {
380                    Some((INVALID_BOUND_CLOSED_MAX, max.clone()))
381                } else {
382                    None
383                }
384            }
385            Bound::ClosedOpenRange(min, max) => {
386                if self < *min {
387                    Some((INVALID_BOUND_CLOSED_MIN, min.clone()))
388                } else if self >= *max {
389                    Some((INVALID_BOUND_OPEN_MAX, max.clone()))
390                } else {
391                    None
392                }
393            }
394            Bound::OpenClosedRange(min, max) => {
395                if self <= *min {
396                    Some((INVALID_BOUND_OPEN_MIN, min.clone()))
397                } else if self > *max {
398                    Some((INVALID_BOUND_CLOSED_MAX, max.clone()))
399                } else {
400                    None
401                }
402            }
403            Bound::OpenRange(min, max) => {
404                if self <= *min {
405                    Some((INVALID_BOUND_OPEN_MIN, min.clone()))
406                } else if self >= *max {
407                    Some((INVALID_BOUND_OPEN_MAX, max.clone()))
408                } else {
409                    None
410                }
411            }
412            Bound::Exact(bound) => {
413                if *bound != self {
414                    Some((INVALID_BOUND_EXACT, bound.clone()))
415                } else {
416                    None
417                }
418            }
419        } {
420            Validation::failure(vec![invalid_value(code, name, self, expected)])
421        } else {
422            Validation::success(self)
423        }
424    }
425}
426
427/// Values of zero are not allowed.
428///
429/// The validation function can be applied in the [`FieldName`] context.
430/// It is implemented for all types `T` that implement the [`HasZeroValue`]
431/// property trait and `Into<Value>`.
432///
433/// [`FieldName`]: ../core/struct.FieldName.html
434/// [`HasZeroValue`]: ../property/trait.HasZeroValue.html
435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
436pub struct NonZero;
437
438impl<T> Validate<NonZero, FieldName> for T
439where
440    T: HasZeroValue + Into<Value>,
441{
442    fn validate(
443        self,
444        name: impl Into<FieldName>,
445        _constraint: &NonZero,
446    ) -> Validation<NonZero, Self> {
447        if self.is_zero_value() {
448            Validation::failure(vec![invalid_optional_value(
449                INVALID_NON_ZERO,
450                name,
451                Some(self.into()),
452                None,
453            )])
454        } else {
455            Validation::success(self)
456        }
457    }
458}
459
460/// Maximum number of allowed integer digits and fraction digits.
461///
462/// The validation function can be applied in the [`FieldName`] context.
463/// It is implemented for all types `T` that implement the [`HasDecimalDigits`]
464/// property trait.
465///
466/// [`FieldName`]: ../core/struct.FieldName.html
467/// [`HasDecimalDigits`]: ../property/trait.HasDecimalDigits.html
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
469pub struct Digits {
470    /// Maximum number of allowed integer digits (digits to the left of the
471    /// decimal point)
472    pub integer: u64,
473    /// Maximum number of allowed fraction digits (digits to the right of the
474    /// decimal point)
475    pub fraction: u64,
476}
477
478impl<T> Validate<Digits, FieldName> for T
479where
480    T: HasDecimalDigits,
481{
482    fn validate(self, name: impl Into<FieldName>, constraint: &Digits) -> Validation<Digits, Self> {
483        let integer = self.integer_digits();
484        let fraction = self.fraction_digits();
485        if integer <= constraint.integer {
486            if fraction <= constraint.fraction {
487                Validation::success(self)
488            } else {
489                Validation::failure(vec![invalid_value(
490                    INVALID_DIGITS_FRACTION,
491                    name,
492                    fraction,
493                    constraint.fraction,
494                )])
495            }
496        } else if fraction <= constraint.fraction {
497            Validation::failure(vec![invalid_value(
498                INVALID_DIGITS_INTEGER,
499                name,
500                integer,
501                constraint.integer,
502            )])
503        } else {
504            let name = name.into();
505            Validation::failure(vec![
506                invalid_value(
507                    INVALID_DIGITS_INTEGER,
508                    name.clone(),
509                    integer,
510                    constraint.integer,
511                ),
512                invalid_value(INVALID_DIGITS_FRACTION, name, fraction, constraint.fraction),
513            ])
514        }
515    }
516}
517
518/// The value must contain the specified member or the specified member must be
519/// part of the value.
520///
521/// The validation function can be applied in the [`FieldName`] context.
522/// It is implemented for all types `T` that implement the [`HasMember`]
523/// property trait and `Into<Value>`.
524///
525/// [`FieldName`]: ../core/struct.FieldName.html
526/// [`HasMember`]: ../property/trait.HasMember.html
527#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub struct Contains<'a, A>(pub &'a A);
529
530impl<'a, T, A> Validate<Contains<'a, A>, FieldName> for T
531where
532    T: HasMember<A> + Into<Value>,
533    A: Clone + Into<Value>,
534{
535    fn validate(
536        self,
537        name: impl Into<FieldName>,
538        constraint: &Contains<'a, A>,
539    ) -> Validation<Contains<'a, A>, Self> {
540        if self.has_member(&constraint.0) {
541            Validation::success(self)
542        } else {
543            Validation::failure(vec![invalid_value(
544                INVALID_CONTAINS_ELEMENT,
545                name,
546                self,
547                constraint.0.clone(),
548            )])
549        }
550    }
551}
552
553/// Two related fields must be equal.
554///
555/// The validation function can be applied in the [`RelatedFields`] context.
556/// It is implemented for all types `T` that implement the `PartialEq` trait.
557///
558/// [`RelatedFields`]: ../core/struct.RelatedFields.html
559#[derive(Debug, Clone, Copy, PartialEq, Eq)]
560pub struct MustMatch;
561
562impl<T> Validate<MustMatch, RelatedFields> for (T, T)
563where
564    T: PartialEq + Into<Value>,
565{
566    fn validate(
567        self,
568        fields: impl Into<RelatedFields>,
569        _constraint: &MustMatch,
570    ) -> Validation<MustMatch, Self> {
571        let RelatedFields(name1, name2) = fields.into();
572        if self.0 == self.1 {
573            Validation::success(self)
574        } else {
575            Validation::failure(vec![invalid_relation(
576                INVALID_MUST_MATCH,
577                name1,
578                self.0,
579                name2,
580                self.1,
581            )])
582        }
583    }
584}
585
586/// Two related fields must define a range.
587///
588/// This constraint is useful for structs with pairs of fields that define a
589/// range such as `valid_from` and `valid_until` or `min_salary` and
590/// `max_salary`.
591///
592/// The validation function can be applied in the [`RelatedFields`] context.
593/// It is implemented for all types `T` that implement the `PartialOrd` trait
594/// and `Into<Value`.
595///
596/// [`RelatedFields`]: ../core/struct.RelatedFields.html
597#[derive(Debug, Clone, Copy, PartialEq, Eq)]
598pub enum MustDefineRange {
599    /// The first value must be less than or equal to the second value
600    Inclusive,
601    /// The first value must be less than the second value
602    Exclusive,
603}
604
605impl<T> Validate<MustDefineRange, RelatedFields> for (T, T)
606where
607    T: PartialOrd + Into<Value>,
608{
609    fn validate(
610        self,
611        fields: impl Into<RelatedFields>,
612        constraint: &MustDefineRange,
613    ) -> Validation<MustDefineRange, Self> {
614        let RelatedFields(name1, name2) = fields.into();
615        match *constraint {
616            MustDefineRange::Inclusive => {
617                if self.0 <= self.1 {
618                    Validation::success(self)
619                } else {
620                    Validation::failure(vec![invalid_relation(
621                        INVALID_MUST_DEFINE_RANGE_INCLUSIVE,
622                        name1,
623                        self.0,
624                        name2,
625                        self.1,
626                    )])
627                }
628            }
629            MustDefineRange::Exclusive => {
630                if self.0 < self.1 {
631                    Validation::success(self)
632                } else {
633                    Validation::failure(vec![invalid_relation(
634                        INVALID_MUST_DEFINE_RANGE_EXCLUSIVE,
635                        name1,
636                        self.0,
637                        name2,
638                        self.1,
639                    )])
640                }
641            }
642        }
643    }
644}
645
646#[cfg(feature = "regex")]
647pub use with_regex::*;
648
649#[cfg(feature = "regex")]
650mod with_regex {
651    use crate::{invalid_value, FieldName, Validate, Validation};
652    use regex::Regex;
653
654    /// Error code: the value does not match the specified pattern
655    /// (`Pattern` constraint)
656    pub const INVALID_PATTERN: &str = "invalid-pattern";
657
658    /// The value must match some regular expression.
659    ///
660    /// The validation function can be applied in the [`FieldName`] context.
661    /// It is implemented for `String`.
662    ///
663    /// [`FieldName`]: ../core/struct.FieldName.html
664    #[derive(Debug, Clone)]
665    pub struct Pattern(pub Regex);
666
667    impl Validate<Pattern, FieldName> for String {
668        fn validate(
669            self,
670            name: impl Into<FieldName>,
671            constraint: &Pattern,
672        ) -> Validation<Pattern, Self> {
673            if constraint.0.is_match(&self) {
674                Validation::success(self)
675            } else {
676                Validation::failure(vec![invalid_value(
677                    INVALID_PATTERN,
678                    name,
679                    self,
680                    constraint.0.to_string(),
681                )])
682            }
683        }
684    }
685}
686
687#[cfg(test)]
688mod tests;