Skip to main content

ddd_toolkit_core/domain/
error.rs

1use std::{error::Error, fmt::Display};
2
3/// A validation failure for a domain value, naming the type that rejected
4/// the value and why.
5///
6/// `type_name` is a plain string, not tied to `Self` by the compiler: it is
7/// whatever the caller of [`ValidationError::new`] passes in. Derive-macro
8/// generated code always passes the exact type name it was expanded for, so
9/// it is guaranteed accurate there. A hand-written `impl` (as in this
10/// crate's own test fixtures) must keep the `type_name` argument in sync
11/// with the type by hand - copy-pasting an impl from one type to another is
12/// the most likely way for it to drift.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ValidationError {
15    /// The name of the type that rejected the value.
16    pub type_name: &'static str,
17    /// A human-readable explanation of why the value was rejected.
18    pub reason: String,
19}
20
21impl ValidationError {
22    /// Creates a new `ValidationError` for the type named `type_name`.
23    pub fn new(type_name: &'static str, reason: impl Into<String>) -> Self {
24        Self {
25            type_name,
26            reason: reason.into(),
27        }
28    }
29}
30
31impl Display for ValidationError {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "invalid {}: {}", self.type_name, self.reason)
34    }
35}
36
37impl Error for ValidationError {}
38
39#[cfg(test)]
40mod test {
41    use crate::domain::ValidationError;
42
43    #[test]
44    fn new_sets_type_name_field() {
45        let ve = ValidationError::new("foo", "bar");
46        assert_eq!(ve.type_name, "foo")
47    }
48
49    #[test]
50    fn new_sets_reason_field() {
51        let ve = ValidationError::new("foo", "bar");
52        assert_eq!(ve.reason, "bar")
53    }
54
55    #[test]
56    fn new_accepts_str_reason() {
57        ValidationError::new("foo", "bar");
58    }
59
60    #[test]
61    fn new_accepts_string_reason() {
62        ValidationError::new("foo", "bar".to_string());
63    }
64
65    #[test]
66    fn display_formats_invalid_type_and_reason() {
67        let ve = ValidationError::new("foo", "bar");
68        assert_eq!(format!("{ve}"), "invalid foo: bar")
69    }
70
71    #[test]
72    fn clone_produces_equal_value() {
73        let ve = ValidationError::new("foo", "bar");
74        let ve_clone = ve.clone();
75
76        assert_eq!(ve, ve_clone);
77        assert_eq!(ve.type_name, ve_clone.type_name);
78        assert_eq!(ve.reason, ve_clone.reason)
79    }
80
81    #[test]
82    fn eq_returns_true_for_same_fields() {
83        let ve = ValidationError::new("foo", "bar");
84        let ve_2 = ValidationError::new("foo", "bar");
85
86        assert_eq!(ve, ve_2);
87        assert_eq!(ve.type_name, ve_2.type_name);
88        assert_eq!(ve.reason, ve_2.reason)
89    }
90
91    #[test]
92    fn eq_returns_false_for_different_type_name() {
93        let ve = ValidationError::new("foo", "bar");
94        let ve_2 = ValidationError::new("bar", "bar");
95
96        assert_ne!(ve, ve_2);
97        assert_ne!(ve.type_name, ve_2.type_name);
98        assert_eq!(ve.reason, ve_2.reason)
99    }
100
101    #[test]
102    fn eq_returns_false_for_different_reason() {
103        let ve = ValidationError::new("foo", "bar");
104        let ve_2 = ValidationError::new("foo", "foo");
105
106        assert_ne!(ve, ve_2);
107        assert_eq!(ve.type_name, ve_2.type_name);
108        assert_ne!(ve.reason, ve_2.reason)
109    }
110
111    #[test]
112    fn implements_std_error_trait() {
113        let ve = ValidationError::new("foo", "bar");
114        let err: &dyn std::error::Error = &ve;
115        assert!(err.source().is_none())
116    }
117
118    #[test]
119    fn new_accepts_empty_reason() {
120        let ve = ValidationError::new("foo", "");
121        assert!(ve.reason.is_empty())
122    }
123
124    #[test]
125    fn display_formats_empty_reason() {
126        let ve: ValidationError = ValidationError::new("foo", "");
127        assert_eq!(format!("{}", ve), "invalid foo: ")
128    }
129}