Skip to main content

dragonfly_client_core/error/
errors.rs

1/*
2 *     Copyright 2024 The Dragonfly Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use std::{error::Error as ErrorTrait, fmt};
18
19use super::message::Message;
20
21/// The type of the error.
22#[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
37/// Implements the display for the error type.
38impl ErrorType {
39    /// Returns the string of the error type.
40    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/// The external error.
58#[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
65/// Implements the error trait.
66impl ExternalError {
67    /// Returns a new ExternalError.
68    pub fn new(etype: ErrorType) -> Self {
69        ExternalError {
70            etype,
71            cause: None,
72            context: None,
73        }
74    }
75
76    /// Returns a new ExternalError with the context.
77    pub fn with_context(mut self, message: impl Into<Message>) -> Self {
78        self.context = Some(message.into());
79        self
80    }
81
82    /// Returns a new ExternalError with the cause.
83    pub fn with_cause(mut self, cause: Box<dyn ErrorTrait + Send + Sync>) -> Self {
84        self.cause = Some(cause);
85        self
86    }
87
88    /// Returns the display of the error with the previous error.
89    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
115/// Implements the display for the error.
116impl fmt::Display for ExternalError {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        self.chain_display(None, f)
119    }
120}
121
122/// Implements the error trait.
123impl ErrorTrait for ExternalError {}
124
125/// The trait to extend the result with error.
126pub trait OrErr<T, E> {
127    /// Wrap the E in [Result] with new [ErrorType] and context, the existing E will be the cause.
128    ///
129    /// This is a shortcut for map_err() + because()
130    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
139/// Implements the OrErr for Result.
140impl<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/// The error for backend.
161#[derive(Debug, thiserror::Error)]
162#[error("backend error: {message}")]
163pub struct BackendError {
164    /// The error message.
165    pub message: String,
166
167    /// The status code of the response.
168    pub status_code: Option<reqwest::StatusCode>,
169
170    /// The headers of the response.
171    pub header: Option<reqwest::header::HeaderMap>,
172}
173
174/// The error when the download from parent is failed.
175#[derive(Debug, thiserror::Error)]
176#[error("download piece {piece_number} from parent {parent_id} failed")]
177pub struct DownloadFromParentFailed {
178    /// The number of the piece.
179    pub piece_number: u32,
180
181    /// The parent id of the piece.
182    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}