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/// ErrorType is 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/// ErrorType implements the display for the error type.
38impl ErrorType {
39    /// as_str 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/// ExternalError is 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/// ExternalError implements the error trait.
66impl ExternalError {
67    /// new 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    /// with_context 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    /// with_cause 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    /// chain_display 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/// ExternalError 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/// ExternalError implements the error trait.
123impl ErrorTrait for ExternalError {}
124
125/// OrErr is 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/// OrErr 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/// BackendError is the error for backend.
161#[derive(Debug, thiserror::Error)]
162#[error("backend error: {message}")]
163pub struct BackendError {
164    /// message is the error message.
165    pub message: String,
166
167    /// status_code is the status code of the response.
168    pub status_code: Option<reqwest::StatusCode>,
169
170    /// header is the headers of the response.
171    pub header: Option<reqwest::header::HeaderMap>,
172}
173
174/// DownloadFromParentFailed is 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    /// piece_number is the number of the piece.
179    pub piece_number: u32,
180
181    /// parent_id is 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 should_create_error() {
191        let error = ExternalError::new(ErrorType::StorageError).with_context("error message");
192        assert_eq!(format!("{}", error), "StorageError context: error message");
193
194        let error = ExternalError::new(ErrorType::StorageError)
195            .with_context(format!("error message {}", "with owned string"));
196        assert_eq!(
197            format!("{}", error),
198            "StorageError context: error message with owned string"
199        );
200
201        let error = ExternalError::new(ErrorType::StorageError)
202            .with_context(format!("error message {}", "with owned string"))
203            .with_cause(Box::new(std::io::Error::new(
204                std::io::ErrorKind::Other,
205                "inner error",
206            )));
207
208        assert_eq!(
209            format!("{}", error),
210            "StorageError context: error message with owned string cause: inner error"
211        );
212    }
213
214    #[test]
215    fn should_extend_result_with_error() {
216        let result: Result<(), std::io::Error> = Err(std::io::Error::new(
217            std::io::ErrorKind::Other,
218            "inner error",
219        ));
220
221        let error = result.or_err(ErrorType::StorageError).unwrap_err();
222        assert_eq!(format!("{}", error), "StorageError cause: inner error");
223
224        let result: Result<(), std::io::Error> = Err(std::io::Error::new(
225            std::io::ErrorKind::Other,
226            "inner error",
227        ));
228
229        let error = result
230            .or_context(ErrorType::StorageError, "error message")
231            .unwrap_err();
232
233        assert_eq!(
234            format!("{}", error),
235            "StorageError context: error message cause: inner error"
236        );
237    }
238}