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 the error message as a borrowed `&str`.
99    ///
100    /// Borrowing avoids an allocation on every call; callers that need an
101    /// owned `String` can do `err.msg().to_owned()` explicitly.
102    ///
103    /// # Examples
104    ///
105    /// ```no_run
106    /// use rusty_cat::api::{InnerErrorCode, MeowError};
107    ///
108    /// let err = MeowError::from_code_str(InnerErrorCode::InvalidRange, "bad range");
109    /// assert_eq!(err.msg(), "bad range");
110    /// ```
111    pub fn msg(&self) -> &str {
112        &self.msg
113    }
114
115    /// Creates an error from [`InnerErrorCode`] with empty message.
116    ///
117    /// # Examples
118    ///
119    /// ```no_run
120    /// use rusty_cat::api::{InnerErrorCode, MeowError};
121    ///
122    /// let err = MeowError::from_code1(InnerErrorCode::ParameterEmpty);
123    /// assert_eq!(err.code(), InnerErrorCode::ParameterEmpty as i32);
124    /// ```
125    pub fn from_code1(code: InnerErrorCode) -> Self {
126        crate::log::emit_lazy(|| {
127            crate::log::Log::debug("error", format!("MeowError::from_code1 code={:?}", code))
128        });
129        MeowError {
130            code: code as i32,
131            msg: String::new(),
132            source: None,
133        }
134    }
135
136    /// Creates an error from [`InnerErrorCode`] and message.
137    ///
138    /// # Examples
139    ///
140    /// ```no_run
141    /// use rusty_cat::api::{InnerErrorCode, MeowError};
142    ///
143    /// let err = MeowError::from_code(InnerErrorCode::EnqueueError, "enqueue failed".to_string());
144    /// assert_eq!(err.code(), InnerErrorCode::EnqueueError as i32);
145    /// ```
146    pub fn from_code(code: InnerErrorCode, msg: String) -> Self {
147        crate::log::emit_lazy(|| {
148            crate::log::Log::debug(
149                "error",
150                format!("MeowError::from_code code={:?} msg={}", code, msg),
151            )
152        });
153        MeowError {
154            code: code as i32,
155            msg,
156            source: None,
157        }
158    }
159
160    /// Creates an error from [`InnerErrorCode`] and `&str` message.
161    ///
162    /// # Examples
163    ///
164    /// ```no_run
165    /// use rusty_cat::api::{InnerErrorCode, MeowError};
166    ///
167    /// let err = MeowError::from_code_str(InnerErrorCode::TaskNotFound, "unknown id");
168    /// assert_eq!(err.code(), InnerErrorCode::TaskNotFound as i32);
169    /// ```
170    pub fn from_code_str(code: InnerErrorCode, msg: &str) -> Self {
171        crate::log::emit_lazy(|| {
172            crate::log::Log::debug(
173                "error",
174                format!("MeowError::from_code_str code={:?} msg={}", code, msg),
175            )
176        });
177        MeowError {
178            code: code as i32,
179            msg: msg.to_string(),
180            source: None,
181        }
182    }
183
184    /// Creates an error with source chaining.
185    ///
186    /// Use this helper to preserve original low-level errors.
187    ///
188    /// # Examples
189    ///
190    /// ```no_run
191    /// use rusty_cat::api::{InnerErrorCode, MeowError};
192    ///
193    /// let source = std::io::Error::other("disk error");
194    /// let err = MeowError::from_source(InnerErrorCode::IoError, "upload failed", source);
195    /// assert_eq!(err.code(), InnerErrorCode::IoError as i32);
196    /// ```
197    pub fn from_source<E>(code: InnerErrorCode, msg: impl Into<String>, source: E) -> Self
198    where
199        E: StdError + Send + Sync + 'static,
200    {
201        let msg = msg.into();
202        let source_preview = source.to_string();
203        crate::log::emit_lazy(|| {
204            crate::log::Log::debug(
205                "error",
206                format!(
207                    "MeowError::from_source code={:?} msg={} source={}",
208                    code, msg, source_preview
209                ),
210            )
211        });
212        MeowError {
213            code: code as i32,
214            msg,
215            source: Some(Arc::new(source)),
216        }
217    }
218}
219
220impl PartialEq for MeowError {
221    fn eq(&self, other: &Self) -> bool {
222        self.code == other.code && self.msg == other.msg
223    }
224}
225
226impl Eq for MeowError {}
227
228impl Display for MeowError {
229    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
230        if self.msg.is_empty() {
231            write!(f, "MeowError(code={})", self.code)
232        } else {
233            write!(f, "MeowError(code={}, msg={})", self.code, self.msg)
234        }
235    }
236}
237
238impl StdError for MeowError {
239    fn source(&self) -> Option<&(dyn StdError + 'static)> {
240        self.source
241            .as_deref()
242            .map(|e| e as &(dyn StdError + 'static))
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::{InnerErrorCode, MeowError};
249
250    #[test]
251    fn meow_error_display_contains_code_and_message() {
252        let err = MeowError::from_code_str(InnerErrorCode::InvalidRange, "bad range");
253        let s = format!("{err}");
254        assert!(s.contains("code="));
255        assert!(s.contains("bad range"));
256    }
257
258    #[test]
259    fn meow_error_source_is_accessible() {
260        let io = std::io::Error::other("disk io");
261        let err = MeowError::from_source(InnerErrorCode::IoError, "io failed", io);
262        assert!(std::error::Error::source(&err).is_some());
263    }
264}