Skip to main content

rusty_cat/
error.rs

1use std::error::Error as StdError;
2use std::fmt::{Display, Formatter};
3use std::sync::Arc;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum InnerErrorCode {
7    /// Unknown/unclassified error.
8    Unknown = -1,
9    /// Success (non-error sentinel).
10    Success = 0,
11    /// Runtime creation failed.
12    RuntimeCreationFailedError = 101,
13    /// Required parameter is empty or invalid.
14    ParameterEmpty = 102,
15    /// The same file/task is already queued or running.
16    DuplicateTaskError = 103,
17    /// Failed to enqueue task.
18    EnqueueError = 104,
19    /// Local I/O operation failed.
20    IoError = 105,
21    /// HTTP request/response operation failed.
22    HttpError = 106,
23    /// Client has already been closed and can no longer accept operations.
24    ClientClosed = 107,
25    /// Unknown task ID in control API.
26    TaskNotFound = 108,
27    /// HTTP response status is not expected.
28    ResponseStatusError = 109,
29    /// `Content-Length` from HEAD is missing or invalid.
30    MissingOrInvalidContentLengthFromHead = 110,
31    /// Failed to send command to scheduler thread.
32    CommandSendFailed = 111,
33    /// Command response channel closed unexpectedly.
34    CommandResponseFailed = 112,
35    /// Failed to parse response payload (for example JSON).
36    ResponseParseError = 113,
37    /// Invalid HTTP range semantics or headers.
38    InvalidRange = 114,
39    /// Local file does not exist.
40    FileNotFound = 115,
41    /// File checksum/signature does not match expected value.
42    ChecksumMismatch = 116,
43    /// Current task state does not allow requested operation.
44    InvalidTaskState = 117,
45    /// Internal lock is poisoned.
46    LockPoisoned = 118,
47    /// Failed to build internal HTTP client.
48    HttpClientBuildFailed = 119,
49    /// Task was canceled before reaching `Complete`.
50    TaskCanceled = 120,
51}
52
53/// Library error type returned by most public APIs.
54#[derive(Debug, Clone)]
55pub struct MeowError {
56    /// Numeric error code, usually mapped from [`InnerErrorCode`].
57    code: i32,
58    /// Human-readable error message.
59    msg: String,
60    /// Optional chained source error.
61    source: Option<Arc<dyn StdError + Send + Sync>>,
62}
63
64impl MeowError {
65    /// Creates a new error with raw numeric code and message.
66    ///
67    /// # Examples
68    ///
69    /// ```no_run
70    /// use rusty_cat::api::MeowError;
71    ///
72    /// let err = MeowError::new(9999, "custom failure".to_string());
73    /// assert_eq!(err.code(), 9999);
74    /// ```
75    pub fn new(code: i32, msg: String) -> Self {
76        crate::log::emit_lazy(|| {
77            crate::log::Log::debug("error", format!("MeowError::new code={} msg={}", code, msg))
78        });
79        MeowError {
80            code,
81            msg,
82            source: None,
83        }
84    }
85
86    /// Returns numeric error code.
87    ///
88    /// # Examples
89    ///
90    /// ```no_run
91    /// use rusty_cat::api::{InnerErrorCode, MeowError};
92    ///
93    /// let err = MeowError::from_code1(InnerErrorCode::ClientClosed);
94    /// assert_eq!(err.code(), InnerErrorCode::ClientClosed as i32);
95    /// ```
96    pub fn code(&self) -> i32 {
97        self.code
98    }
99
100    /// Returns the error message as a borrowed `&str`.
101    ///
102    /// Borrowing avoids an allocation on every call; callers that need an
103    /// owned `String` can do `err.msg().to_owned()` explicitly.
104    ///
105    /// # Examples
106    ///
107    /// ```no_run
108    /// use rusty_cat::api::{InnerErrorCode, MeowError};
109    ///
110    /// let err = MeowError::from_code_str(InnerErrorCode::InvalidRange, "bad range");
111    /// assert_eq!(err.msg(), "bad range");
112    /// ```
113    pub fn msg(&self) -> &str {
114        &self.msg
115    }
116
117    /// Creates an error from [`InnerErrorCode`] with empty message.
118    ///
119    /// # Examples
120    ///
121    /// ```no_run
122    /// use rusty_cat::api::{InnerErrorCode, MeowError};
123    ///
124    /// let err = MeowError::from_code1(InnerErrorCode::ParameterEmpty);
125    /// assert_eq!(err.code(), InnerErrorCode::ParameterEmpty as i32);
126    /// ```
127    pub fn from_code1(code: InnerErrorCode) -> Self {
128        crate::log::emit_lazy(|| {
129            crate::log::Log::debug("error", format!("MeowError::from_code1 code={:?}", code))
130        });
131        MeowError {
132            code: code as i32,
133            msg: String::new(),
134            source: None,
135        }
136    }
137
138    /// Creates an error from [`InnerErrorCode`] and message.
139    ///
140    /// # Examples
141    ///
142    /// ```no_run
143    /// use rusty_cat::api::{InnerErrorCode, MeowError};
144    ///
145    /// let err = MeowError::from_code(InnerErrorCode::EnqueueError, "enqueue failed".to_string());
146    /// assert_eq!(err.code(), InnerErrorCode::EnqueueError as i32);
147    /// ```
148    pub fn from_code(code: InnerErrorCode, msg: String) -> Self {
149        crate::log::emit_lazy(|| {
150            crate::log::Log::debug(
151                "error",
152                format!("MeowError::from_code code={:?} msg={}", code, msg),
153            )
154        });
155        MeowError {
156            code: code as i32,
157            msg,
158            source: None,
159        }
160    }
161
162    /// Creates an error from [`InnerErrorCode`] and `&str` message.
163    ///
164    /// # Examples
165    ///
166    /// ```no_run
167    /// use rusty_cat::api::{InnerErrorCode, MeowError};
168    ///
169    /// let err = MeowError::from_code_str(InnerErrorCode::TaskNotFound, "unknown id");
170    /// assert_eq!(err.code(), InnerErrorCode::TaskNotFound as i32);
171    /// ```
172    pub fn from_code_str(code: InnerErrorCode, msg: &str) -> Self {
173        crate::log::emit_lazy(|| {
174            crate::log::Log::debug(
175                "error",
176                format!("MeowError::from_code_str code={:?} msg={}", code, msg),
177            )
178        });
179        MeowError {
180            code: code as i32,
181            msg: msg.to_string(),
182            source: None,
183        }
184    }
185
186    /// Creates an error with source chaining.
187    ///
188    /// Use this helper to preserve original low-level errors.
189    ///
190    /// # Examples
191    ///
192    /// ```no_run
193    /// use rusty_cat::api::{InnerErrorCode, MeowError};
194    ///
195    /// let source = std::io::Error::other("disk error");
196    /// let err = MeowError::from_source(InnerErrorCode::IoError, "upload failed", source);
197    /// assert_eq!(err.code(), InnerErrorCode::IoError as i32);
198    /// ```
199    pub fn from_source<E>(code: InnerErrorCode, msg: impl Into<String>, source: E) -> Self
200    where
201        E: StdError + Send + Sync + 'static,
202    {
203        let msg = msg.into();
204        let source_preview = source.to_string();
205        crate::log::emit_lazy(|| {
206            crate::log::Log::debug(
207                "error",
208                format!(
209                    "MeowError::from_source code={:?} msg={} source={}",
210                    code, msg, source_preview
211                ),
212            )
213        });
214        MeowError {
215            code: code as i32,
216            msg,
217            source: Some(Arc::new(source)),
218        }
219    }
220}
221
222impl PartialEq for MeowError {
223    fn eq(&self, other: &Self) -> bool {
224        self.code == other.code && self.msg == other.msg
225    }
226}
227
228impl Eq for MeowError {}
229
230impl Display for MeowError {
231    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
232        if self.msg.is_empty() {
233            write!(f, "MeowError(code={})", self.code)
234        } else {
235            write!(f, "MeowError(code={}, msg={})", self.code, self.msg)
236        }
237    }
238}
239
240impl StdError for MeowError {
241    fn source(&self) -> Option<&(dyn StdError + 'static)> {
242        self.source
243            .as_deref()
244            .map(|e| e as &(dyn StdError + 'static))
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::{InnerErrorCode, MeowError};
251
252    #[test]
253    fn meow_error_display_contains_code_and_message() {
254        let err = MeowError::from_code_str(InnerErrorCode::InvalidRange, "bad range");
255        let s = format!("{err}");
256        assert!(s.contains("code="));
257        assert!(s.contains("bad range"));
258    }
259
260    #[test]
261    fn meow_error_source_is_accessible() {
262        let io = std::io::Error::other("disk io");
263        let err = MeowError::from_source(InnerErrorCode::IoError, "io failed", io);
264        assert!(std::error::Error::source(&err).is_some());
265    }
266}