1use std::{fmt, io};
3
4use thiserror::Error;
5
6#[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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
75pub enum CondowErrorKind {
76 InvalidRange,
80 NotFound,
84 AccessDenied,
91 Remote,
95 Io,
99 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
150pub fn http_status_to_error(
152 status_code: u16,
153 status_str: &str,
154 is_server_error: bool,
155 body: &[u8],
156) -> CondowError {
157 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}