condow_core/
errors.rs

1//! Error types returned by Condow
2use std::{fmt, io};
3
4use thiserror::Error;
5
6/// The error type used by `condow`
7///
8/// Further information is encoded with [CondowErrorKind].
9#[derive(Error, Debug)]
10pub struct CondowError {
11    msg: String,
12    #[source]
13    source: Option<anyhow::Error>,
14    kind: CondowErrorKind,
15}
16
17impl CondowError {
18    pub fn new<T: Into<String>>(msg: T, kind: CondowErrorKind) -> Self {
19        Self {
20            msg: msg.into(),
21            source: None,
22            kind,
23        }
24    }
25    pub fn new_invalid_range<T: Into<String>>(msg: T) -> Self {
26        Self::new(msg, CondowErrorKind::InvalidRange)
27    }
28
29    pub fn new_not_found<T: Into<String>>(msg: T) -> Self {
30        Self::new(msg, CondowErrorKind::NotFound)
31    }
32
33    pub fn new_access_denied<T: Into<String>>(msg: T) -> Self {
34        Self::new(msg, CondowErrorKind::AccessDenied)
35    }
36
37    pub fn new_remote<T: Into<String>>(msg: T) -> Self {
38        Self::new(msg, CondowErrorKind::Remote)
39    }
40
41    pub fn new_io<T: Into<String>>(msg: T) -> Self {
42        Self::new(msg, CondowErrorKind::Io)
43    }
44
45    pub fn new_other<T: Into<String>>(msg: T) -> Self {
46        Self::new(msg, CondowErrorKind::Other)
47    }
48
49    pub fn with_source<E: Into<anyhow::Error>>(mut self, err: E) -> Self {
50        self.source = Some(err.into());
51        self
52    }
53
54    pub fn msg(&self) -> &str {
55        &self.msg
56    }
57
58    pub fn kind(&self) -> CondowErrorKind {
59        self.kind
60    }
61
62    pub fn is_retryable(&self) -> bool {
63        self.kind.is_retryable()
64    }
65}
66
67impl fmt::Display for CondowError {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        write!(f, "{}", self.msg)
70    }
71}
72
73/// Specifies the kind of a [CondowError]
74#[derive(Debug, Copy, Clone, PartialEq, Eq)]
75pub enum CondowErrorKind {
76    /// An invalid range was encountered.
77    ///
78    /// Errors with this kind are **not retryable**
79    InvalidRange,
80    /// The BLOB could not be found at a given location.
81    ///
82    /// Errors with this kind are **not retryable**
83    NotFound,
84    /// Access was denied to the BLOB at a given location.
85    ///
86    /// Could be "unauthenticated", "unauthorized" or anything else
87    /// regarding credentials
88    ///
89    /// Errors with this kind are **not retryable**
90    AccessDenied,
91    /// The resource providing the BLOB encountered an error
92    ///
93    /// Errors with this kind are **retryable**
94    Remote,
95    /// Something went wrong with our data "on the wire"
96    ///
97    /// Errors with this kind are **retryable**
98    Io,
99    /// Anything else which does not fall under one of the other categories
100    ///
101    /// Errors with this kind are **not retryable**
102    Other,
103}
104
105impl CondowErrorKind {
106    pub fn is_retryable(self) -> bool {
107        use CondowErrorKind::*;
108
109        match self {
110            InvalidRange => false,
111            NotFound => false,
112            AccessDenied => false,
113            Remote => true,
114            Io => true,
115            Other => false,
116        }
117    }
118}
119
120impl From<CondowErrorKind> for CondowError {
121    fn from(error_kind: CondowErrorKind) -> Self {
122        CondowError::new(format!("An error occurred: {:?}", error_kind), error_kind)
123    }
124}
125
126impl From<io::Error> for CondowError {
127    fn from(io_err: io::Error) -> Self {
128        use io::ErrorKind;
129        match io_err.kind() {
130            ErrorKind::NotFound => CondowError::new_not_found(format!("io error: {io_err}")),
131            ErrorKind::PermissionDenied => {
132                CondowError::new_access_denied(format!("permission denied: {io_err}"))
133            }
134            _ => CondowError::new_io(format!("io error: {io_err}")),
135        }
136        .with_source(io_err)
137    }
138}
139
140impl From<CondowError> for io::Error {
141    fn from(err: CondowError) -> Self {
142        match err.kind() {
143            CondowErrorKind::NotFound => io::Error::new(io::ErrorKind::NotFound, err),
144            CondowErrorKind::AccessDenied => io::Error::new(io::ErrorKind::PermissionDenied, err),
145            _ => io::Error::new(io::ErrorKind::Other, err),
146        }
147    }
148}
149
150/// Utility function to convert an error HTTP response to a CondowError.
151pub fn http_status_to_error(
152    status_code: u16,
153    status_str: &str,
154    is_server_error: bool,
155    body: &[u8],
156) -> CondowError {
157    // Ideally this fn should take the StatusCode from http crate,
158    // but that would make multiple crates depend on the same http crate, possibly with different versions.
159    let message = if let Ok(body_str) = std::str::from_utf8(body) {
160        body_str
161    } else {
162        "<<< response body is not valid UTF-8 >>>"
163    };
164    let message = format!("{} - {}", status_str, message);
165    match status_code {
166        404 => CondowError::new_not_found(message),
167        401 | 403 => CondowError::new_access_denied(message),
168        _ => {
169            if is_server_error {
170                CondowError::new_remote(message)
171            } else {
172                CondowError::new_other(message)
173            }
174        }
175    }
176}