dragonfly_client_core/error/
errors.rs1use std::{error::Error as ErrorTrait, fmt};
18
19use super::message::Message;
20
21#[derive(Debug, PartialEq, Eq, Clone)]
23pub enum ErrorType {
24 StorageError,
25 ConfigError,
26 SerializeError,
27 ValidationError,
28 ParseError,
29 CertificateError,
30 TLSConfigError,
31 AsyncRuntimeError,
32 StreamError,
33 ConnectError,
34 PluginError,
35}
36
37impl ErrorType {
39 pub fn as_str(&self) -> &'static str {
41 match self {
42 ErrorType::StorageError => "StorageError",
43 ErrorType::ConfigError => "ConfigError",
44 ErrorType::ValidationError => "ValidationError",
45 ErrorType::ParseError => "ParseError",
46 ErrorType::CertificateError => "CertificateError",
47 ErrorType::SerializeError => "SerializeError",
48 ErrorType::TLSConfigError => "TLSConfigError",
49 ErrorType::AsyncRuntimeError => "AsyncRuntimeError",
50 ErrorType::StreamError => "StreamError",
51 ErrorType::ConnectError => "ConnectError",
52 ErrorType::PluginError => "PluginError",
53 }
54 }
55}
56
57#[derive(Debug)]
59pub struct ExternalError {
60 pub etype: ErrorType,
61 pub cause: Option<Box<dyn ErrorTrait + Send + Sync>>,
62 pub context: Option<Message>,
63}
64
65impl ExternalError {
67 pub fn new(etype: ErrorType) -> Self {
69 ExternalError {
70 etype,
71 cause: None,
72 context: None,
73 }
74 }
75
76 pub fn with_context(mut self, message: impl Into<Message>) -> Self {
78 self.context = Some(message.into());
79 self
80 }
81
82 pub fn with_cause(mut self, cause: Box<dyn ErrorTrait + Send + Sync>) -> Self {
84 self.cause = Some(cause);
85 self
86 }
87
88 fn chain_display(
90 &self,
91 previous: Option<&ExternalError>,
92 f: &mut fmt::Formatter<'_>,
93 ) -> fmt::Result {
94 if previous.map(|p| p.etype != self.etype).unwrap_or(true) {
95 write!(f, "{}", self.etype.as_str())?
96 }
97
98 if let Some(c) = self.context.as_ref() {
99 write!(f, " context: {}", c.as_str())?;
100 }
101
102 if let Some(c) = self.cause.as_ref() {
103 if let Some(e) = c.downcast_ref::<Box<ExternalError>>() {
104 write!(f, " cause: ")?;
105 e.chain_display(Some(self), f)
106 } else {
107 write!(f, " cause: {c}")
108 }
109 } else {
110 Ok(())
111 }
112 }
113}
114
115impl fmt::Display for ExternalError {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 self.chain_display(None, f)
119 }
120}
121
122impl ErrorTrait for ExternalError {}
124
125pub trait OrErr<T, E> {
127 fn or_err(self, et: ErrorType) -> Result<T, ExternalError>
131 where
132 E: Into<Box<dyn ErrorTrait + Send + Sync>>;
133
134 fn or_context(self, et: ErrorType, context: &'static str) -> Result<T, ExternalError>
135 where
136 E: Into<Box<dyn ErrorTrait + Send + Sync>>;
137}
138
139impl<T, E> OrErr<T, E> for Result<T, E> {
141 fn or_err(self, et: ErrorType) -> Result<T, ExternalError>
142 where
143 E: Into<Box<dyn ErrorTrait + Send + Sync>>,
144 {
145 self.map_err(|err| ExternalError::new(et).with_cause(err.into()))
146 }
147
148 fn or_context(self, et: ErrorType, context: &'static str) -> Result<T, ExternalError>
149 where
150 E: Into<Box<dyn ErrorTrait + Send + Sync>>,
151 {
152 self.map_err(|err| {
153 ExternalError::new(et)
154 .with_cause(err.into())
155 .with_context(context)
156 })
157 }
158}
159
160#[derive(Debug, thiserror::Error)]
162#[error("backend error: {message}")]
163pub struct BackendError {
164 pub message: String,
166
167 pub status_code: Option<reqwest::StatusCode>,
169
170 pub header: Option<reqwest::header::HeaderMap>,
172}
173
174#[derive(Debug, thiserror::Error)]
176#[error("download piece {piece_number} from parent {parent_id} failed")]
177pub struct DownloadFromParentFailed {
178 pub piece_number: u32,
180
181 pub parent_id: String,
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn as_str_names_each_error_type() {
191 let test_cases = vec![
192 (ErrorType::StorageError, "StorageError"),
193 (ErrorType::ConfigError, "ConfigError"),
194 (ErrorType::SerializeError, "SerializeError"),
195 (ErrorType::ValidationError, "ValidationError"),
196 (ErrorType::ParseError, "ParseError"),
197 (ErrorType::CertificateError, "CertificateError"),
198 (ErrorType::TLSConfigError, "TLSConfigError"),
199 (ErrorType::AsyncRuntimeError, "AsyncRuntimeError"),
200 (ErrorType::StreamError, "StreamError"),
201 (ErrorType::ConnectError, "ConnectError"),
202 (ErrorType::PluginError, "PluginError"),
203 ];
204
205 for (etype, expected) in test_cases {
206 assert_eq!(etype.as_str(), expected);
207 }
208 }
209
210 #[test]
211 fn display_chains_type_context_and_cause() {
212 let test_cases = vec![
213 (
214 ExternalError::new(ErrorType::StorageError).with_context("error message"),
215 "StorageError context: error message",
216 ),
217 (
218 ExternalError::new(ErrorType::StorageError)
219 .with_context("error message with owned string".to_string()),
220 "StorageError context: error message with owned string",
221 ),
222 (
223 ExternalError::new(ErrorType::StorageError)
224 .with_context("error message with owned string".to_string())
225 .with_cause(Box::new(std::io::Error::other("inner error"))),
226 "StorageError context: error message with owned string cause: inner error",
227 ),
228 (
229 ExternalError::new(ErrorType::StorageError)
230 .with_context("outer")
231 .with_cause(Box::new(Box::new(
232 ExternalError::new(ErrorType::ConfigError).with_context("inner"),
233 ))),
234 "StorageError context: outer cause: ConfigError context: inner",
235 ),
236 (
237 ExternalError::new(ErrorType::StorageError)
238 .with_context("outer")
239 .with_cause(Box::new(Box::new(
240 ExternalError::new(ErrorType::StorageError).with_context("inner"),
241 ))),
242 "StorageError context: outer cause: context: inner",
243 ),
244 ];
245
246 for (error, expected) in test_cases {
247 assert_eq!(error.to_string(), expected);
248 }
249 }
250
251 #[test]
252 fn or_err_and_or_context_wrap_the_cause() {
253 let test_cases = vec![
254 (
255 Err::<(), _>(std::io::Error::other("inner error")).or_err(ErrorType::StorageError),
256 "StorageError cause: inner error",
257 ),
258 (
259 Err::<(), _>(std::io::Error::other("inner error"))
260 .or_context(ErrorType::StorageError, "error message"),
261 "StorageError context: error message cause: inner error",
262 ),
263 ];
264
265 for (result, expected) in test_cases {
266 let error = result.unwrap_err();
267 assert_eq!(error.to_string(), expected);
268 }
269 }
270}