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;