ddd_toolkit_core/port/
error.rs1use std::fmt::Display;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum PortErrorKind {
8 Conflict,
11 Unavailable,
14 Other,
16}
17
18#[derive(Debug)]
21pub struct PortError {
22 kind: PortErrorKind,
23 source: Box<dyn std::error::Error + Send + Sync>,
24}
25
26impl PortError {
27 pub fn new(
29 kind: PortErrorKind,
30 source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
31 ) -> Self {
32 Self {
33 kind,
34 source: source.into(),
35 }
36 }
37
38 pub fn conflict(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
40 Self::new(PortErrorKind::Conflict, source)
41 }
42
43 pub fn unavailable(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
45 Self::new(PortErrorKind::Unavailable, source)
46 }
47
48 pub fn other(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
50 Self::new(PortErrorKind::Other, source)
51 }
52
53 pub fn kind(&self) -> PortErrorKind {
55 self.kind
56 }
57}
58
59impl Display for PortError {
60 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
61 write!(f, "{:?}: {}", self.kind, self.source)
62 }
63}
64
65impl std::error::Error for PortError {
66 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
67 Some(self.source.as_ref())
68 }
69}
70
71#[cfg(test)]
72mod test {
73 use super::*;
74
75 #[test]
76 fn new_returns_error_with_given_kind() {
77 let error = PortError::new(PortErrorKind::Conflict, "boom");
78
79 assert_eq!(error.kind(), PortErrorKind::Conflict);
80 }
81
82 #[test]
83 fn conflict_returns_error_with_conflict_kind() {
84 let error = PortError::conflict("boom");
85
86 assert_eq!(error.kind(), PortErrorKind::Conflict);
87 }
88
89 #[test]
90 fn unavailable_returns_error_with_unavailable_kind() {
91 let error = PortError::unavailable("boom");
92
93 assert_eq!(error.kind(), PortErrorKind::Unavailable);
94 }
95
96 #[test]
97 fn other_returns_error_with_other_kind() {
98 let error = PortError::other("boom");
99
100 assert_eq!(error.kind(), PortErrorKind::Other);
101 }
102
103 #[test]
104 fn source_returns_inner_error() {
105 let error: &dyn std::error::Error = &PortError::conflict("boom");
106
107 let source = error.source().expect("source should be Some");
108 assert_eq!(source.to_string(), "boom");
109 }
110
111 #[derive(Debug)]
112 struct MiddleError {
113 inner: Box<dyn std::error::Error + Send + Sync>,
114 }
115
116 impl Display for MiddleError {
117 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118 write!(f, "middle")
119 }
120 }
121
122 impl std::error::Error for MiddleError {
123 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
124 Some(self.inner.as_ref())
125 }
126 }
127
128 #[test]
129 fn source_preserves_nested_error_chain() {
130 let middle = MiddleError {
131 inner: "root".into(),
132 };
133 let error: &dyn std::error::Error = &PortError::other(middle);
134
135 let middle = error.source().expect("source should be Some");
136 let root = middle.source().expect("nested source should be Some");
137 assert_eq!(middle.to_string(), "middle");
138 assert_eq!(root.to_string(), "root");
139 }
140
141 #[test]
142 fn display_formats_kind_and_source_message() {
143 let error = PortError::conflict("boom");
144
145 assert_eq!(error.to_string(), "Conflict: boom");
146 }
147
148 #[test]
149 fn new_accepts_str_string_and_custom_error() {
150 let _ = PortError::new(PortErrorKind::Other, "str source");
151 let _ = PortError::new(PortErrorKind::Other, String::from("string source"));
152 let _ = PortError::new(
153 PortErrorKind::Other,
154 MiddleError {
155 inner: "root".into(),
156 },
157 );
158 }
159
160 #[test]
161 fn kind_copy_produces_equal_value() {
162 let kind = PortErrorKind::Unavailable;
163 let copied = kind;
164
165 assert_eq!(copied, kind);
166 }
167
168 #[test]
169 fn kind_eq_returns_true_for_same_variant() {
170 assert_eq!(PortErrorKind::Conflict, PortErrorKind::Conflict);
171 }
172
173 #[test]
174 fn kind_eq_returns_false_for_different_variant() {
175 assert_ne!(PortErrorKind::Conflict, PortErrorKind::Unavailable);
176 }
177
178 #[test]
179 fn display_with_empty_source_message_ends_with_colon_space() {
180 let error = PortError::other("");
181
182 assert_eq!(error.to_string(), "Other: ");
183 }
184}