ddd_toolkit_core/domain/
error.rs1use std::{error::Error, fmt::Display};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34pub struct ValidationError {
35 pub type_name: &'static str,
37 pub reason: String,
39}
40
41impl ValidationError {
42 pub fn new(type_name: &'static str, reason: impl Into<String>) -> Self {
44 Self {
45 type_name,
46 reason: reason.into(),
47 }
48 }
49}
50
51impl Display for ValidationError {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "invalid {}: {}", self.type_name, self.reason)
54 }
55}
56
57impl Error for ValidationError {}
58
59#[cfg(test)]
60mod test {
61 use crate::domain::ValidationError;
62
63 #[test]
64 fn new_sets_type_name_field() {
65 let ve = ValidationError::new("foo", "bar");
66 assert_eq!(ve.type_name, "foo")
67 }
68
69 #[test]
70 fn new_sets_reason_field() {
71 let ve = ValidationError::new("foo", "bar");
72 assert_eq!(ve.reason, "bar")
73 }
74
75 #[test]
76 fn new_accepts_str_reason() {
77 ValidationError::new("foo", "bar");
78 }
79
80 #[test]
81 fn new_accepts_string_reason() {
82 ValidationError::new("foo", "bar".to_string());
83 }
84
85 #[test]
86 fn display_formats_invalid_type_and_reason() {
87 let ve = ValidationError::new("foo", "bar");
88 assert_eq!(format!("{ve}"), "invalid foo: bar")
89 }
90
91 #[test]
92 fn clone_produces_equal_value() {
93 let ve = ValidationError::new("foo", "bar");
94 let ve_clone = ve.clone();
95
96 assert_eq!(ve, ve_clone);
97 assert_eq!(ve.type_name, ve_clone.type_name);
98 assert_eq!(ve.reason, ve_clone.reason)
99 }
100
101 #[test]
102 fn eq_returns_true_for_same_fields() {
103 let ve = ValidationError::new("foo", "bar");
104 let ve_2 = ValidationError::new("foo", "bar");
105
106 assert_eq!(ve, ve_2);
107 assert_eq!(ve.type_name, ve_2.type_name);
108 assert_eq!(ve.reason, ve_2.reason)
109 }
110
111 #[test]
112 fn eq_returns_false_for_different_type_name() {
113 let ve = ValidationError::new("foo", "bar");
114 let ve_2 = ValidationError::new("bar", "bar");
115
116 assert_ne!(ve, ve_2);
117 assert_ne!(ve.type_name, ve_2.type_name);
118 assert_eq!(ve.reason, ve_2.reason)
119 }
120
121 #[test]
122 fn eq_returns_false_for_different_reason() {
123 let ve = ValidationError::new("foo", "bar");
124 let ve_2 = ValidationError::new("foo", "foo");
125
126 assert_ne!(ve, ve_2);
127 assert_eq!(ve.type_name, ve_2.type_name);
128 assert_ne!(ve.reason, ve_2.reason)
129 }
130
131 #[test]
132 fn implements_std_error_trait() {
133 let ve = ValidationError::new("foo", "bar");
134 let err: &dyn std::error::Error = &ve;
135 assert!(err.source().is_none())
136 }
137
138 #[test]
139 fn new_accepts_empty_reason() {
140 let ve = ValidationError::new("foo", "");
141 assert!(ve.reason.is_empty())
142 }
143
144 #[test]
145 fn display_formats_empty_reason() {
146 let ve: ValidationError = ValidationError::new("foo", "");
147 assert_eq!(format!("{}", ve), "invalid foo: ")
148 }
149
150 #[cfg(feature = "serde")]
151 #[test]
152 fn serializes_to_json_object_with_type_name_and_reason_fields() {
153 let ve = ValidationError::new("Email", "missing '@'");
154 let json = serde_json::to_value(&ve).unwrap();
155 assert_eq!(
156 json,
157 serde_json::json!({"type_name": "Email", "reason": "missing '@'"})
158 );
159 }
160}