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}
50
51/// Library error type returned by most public APIs.
52#[derive(Debug, Clone)]
53pub struct MeowError {
54    /// Numeric error code, usually mapped from [`InnerErrorCode`].
55    code: i32,
56    /// Human-readable error message.
57    msg: String,
58    /// Optional chained source error.
59    source: Option<Arc<dyn StdError + Send + Sync>>,
60}
61
62impl MeowError {
63    /// Creates a new error with raw numeric code and message.
64    ///
65    /// # Examples
66    ///
67    /// ```no_run
68    /// use rusty_cat::api::MeowError;
69    ///
70    /// let err = MeowError::new(9999, "custom failure".to_string());
71    /// assert_eq!(err.code(), 9999);
72    /// ```
73    pub fn new(code: i32, msg: String) -> Self {
74        crate::log::emit_lazy(|| {
75            crate::log::Log::debug("error", format!("MeowError::new code={} msg={}", code, msg))
76        });
77        MeowError {
78            code,
79            msg,
80            source: None,
81        }
82    }
83
84    /// Returns numeric error code.
85    ///
86    /// # Examples
87    ///
88    /// ```no_run
89    /// use rusty_cat::api::{InnerErrorCode, MeowError};
90    ///
91    /// let err = MeowError::from_code1(InnerErrorCode::ClientClosed);
92    /// assert_eq!(err.code(), InnerErrorCode::ClientClosed as i32);
93    /// ```
94    pub fn code(&self) -> i32 {
95        self.code
96    }
97
98    /// Returns error message as an owned `String`.
99    ///
100    /// # Examples
101    ///
102    /// ```no_run
103    /// use rusty_cat::api::{InnerErrorCode, MeowError};
104    ///
105    /// let err = MeowError::from_code_str(InnerErrorCode::InvalidRange, "bad range");
106    /// assert_eq!(err.msg(), "bad range".to_string());
107    /// ```
108    pub fn msg(&self) -> String {
109        self.msg.clone()
110    }
111
112    /// Creates an error from [`InnerErrorCode`] with empty message.
113    ///
114    /// # Examples
115    ///
116    /// ```no_run
117    /// use rusty_cat::api::{InnerErrorCode, MeowError};
118    ///
119    /// let err = MeowError::from_code1(InnerErrorCode::ParameterEmpty);
120    /// assert_eq!(err.code(), InnerErrorCode::ParameterEmpty as i32);
121    /// ```
122    pub fn from_code1(code: InnerErrorCode) -> Self {
123        crate::log::emit_lazy(|| {
124            crate::log::Log::debug("error", format!("MeowError::from_code1 code={:?}", code))
125        });
126        MeowError {
127            code: code as i32,
128            msg: String::new(),
129            source: None,
130        }
131    }
132
133    /// Creates an error from [`InnerErrorCode`] and message.
134    ///
135    /// # Examples
136    ///
137    /// ```no_run
138    /// use rusty_cat::api::{InnerErrorCode, MeowError};
139    ///
140    /// let err = MeowError::from_code(InnerErrorCode::EnqueueError, "enqueue failed".to_string());
141    /// assert_eq!(err.code(), InnerErrorCode::EnqueueError as i32);
142    /// ```
143    pub fn from_code(code: InnerErrorCode, msg: String) -> Self {
144        crate::log::emit_lazy(|| {
145            crate::log::Log::debug(
146                "error",
147                format!("MeowError::from_code code={:?} msg={}", code, msg),
148            )
149        });
150        MeowError {
151            code: code as i32,
152            msg,
153            source: None,
154        }
155    }
156
157    /// Creates an error from [`InnerErrorCode`] and `&str` message.
158    ///
159    /// # Examples
160    ///
161    /// ```no_run
162    /// use rusty_cat::api::{InnerErrorCode, MeowError};
163    ///
164    /// let err = MeowError::from_code_str(InnerErrorCode::TaskNotFound, "unknown id");
165    /// assert_eq!(err.code(), InnerErrorCode::TaskNotFound as i32);
166    /// ```
167    pub fn from_code_str(code: InnerErrorCode, msg: &str) -> Self {
168        crate::log::emit_lazy(|| {
169            crate::log::Log::debug(
170                "error",
171                format!("MeowError::from_code_str code={:?} msg={}", code, msg),
172            )
173        });
174        MeowError {
175            code: code as i32,
176            msg: msg.to_string(),
177            source: None,
178        }
179    }
180
181    /// Creates an error with source chaining.
182    ///
183    /// Use this helper to preserve original low-level errors.
184    ///
185    /// # Examples
186    ///
187    /// ```no_run
188    /// use rusty_cat::api::{InnerErrorCode, MeowError};
189    ///
190    /// let source = std::io::Error::other("disk error");
191    /// let err = MeowError::from_source(InnerErrorCode::IoError, "upload failed", source);
192    /// assert_eq!(err.code(), InnerErrorCode::IoError as i32);
193    /// ```
194    pub fn from_source<E>(code: InnerErrorCode, msg: impl Into<String>, source: E) -> Self
195    where
196        E: StdError + Send + Sync + 'static,
197    {
198        let msg = msg.into();
199        let source_preview = source.to_string();
200        crate::log::emit_lazy(|| {
201            crate::log::Log::debug(
202                "error",
203                format!(
204                    "MeowError::from_source code={:?} msg={} source={}",
205                    code, msg, source_preview
206                ),
207            )
208        });
209        MeowError {
210            code: code as i32,
211            msg,
212            source: Some(Arc::new(source)),
213        }
214    }
215}
216
217impl PartialEq for MeowError {
218    fn eq(&self, other: &Self) -> bool {
219        self.code == other.code && self.msg == other.msg
220    }
221}
222
223impl Eq for MeowError {}
224
225impl Display for MeowError {
226    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
227        if self.msg.is_empty() {
228            write!(f, "MeowError(code={})", self.code)
229        } else {
230            write!(f, "MeowError(code={}, msg={})", self.code, self.msg)
231        }
232    }
233}
234
235impl StdError for MeowError {
236    fn source(&self) -> Option<&(dyn StdError + 'static)> {
237        self.source
238            .as_deref()
239            .map(|e| e as &(dyn StdError + 'static))
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::{InnerErrorCode, MeowError};
246
247    #[test]
248    fn meow_error_display_contains_code_and_message() {
249        let err = MeowError::from_code_str(InnerErrorCode::InvalidRange, "bad range");
250        let s = format!("{err}");
251        assert!(s.contains("code="));
252        assert!(s.contains("bad range"));
253    }
254
255    #[test]
256    fn meow_error_source_is_accessible() {
257        let io = std::io::Error::other("disk io");
258        let err = MeowError::from_source(InnerErrorCode::IoError, "io failed", io);
259        assert!(std::error::Error::source(&err).is_some());
260    }
261}