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