Skip to main content

qml_rs/storage/
error.rs

1use crate::error::QmlError;
2use thiserror::Error;
3
4/// Storage-specific errors that can occur during job persistence operations.
5///
6/// Every variant that wraps an underlying driver error carries a
7/// `source: Option<Box<dyn Error + Send + Sync>>` field with `#[source]`,
8/// so callers can walk `Error::source()` or downcast to the concrete
9/// type. Earlier revisions stringified the cause into `message` and
10/// dropped the chain; the helper constructors here always preserve it.
11#[derive(Error, Debug)]
12pub enum StorageError {
13    /// Connection-related errors (network, authentication, etc.)
14    #[error("Storage connection error: {message}")]
15    Connection {
16        message: String,
17        #[source]
18        source: Option<Box<dyn std::error::Error + Send + Sync>>,
19    },
20
21    /// Encoding a value (job, state, metadata) into the storage's
22    /// transport format failed.
23    #[error("Serialization error: {message}")]
24    Serialization {
25        message: String,
26        #[source]
27        source: Option<Box<dyn std::error::Error + Send + Sync>>,
28    },
29
30    /// Decoding a value out of the storage's transport format failed.
31    /// Distinct from [`Serialization`] so callers handling a corrupt-row
32    /// recovery path can match precisely.
33    #[error("Deserialization error: {message}")]
34    Deserialization {
35        message: String,
36        #[source]
37        source: Option<Box<dyn std::error::Error + Send + Sync>>,
38    },
39
40    /// Job not found in storage
41    #[error("Job not found: {job_id}")]
42    JobNotFound { job_id: String },
43
44    /// Storage operation timed out
45    #[error("Storage operation timed out after {timeout_ms}ms")]
46    Timeout { timeout_ms: u64 },
47
48    /// Storage is unavailable or down
49    #[error("Storage is unavailable: {reason}")]
50    Unavailable { reason: String },
51
52    /// Configuration errors
53    #[error("Storage configuration error: {message}")]
54    Configuration { message: String },
55
56    /// A storage-engine operation failed for a reason that isn't a
57    /// connection / serialization / not-found / capacity issue. The
58    /// `message` field should already include enough operation context
59    /// (e.g. `"Failed to fetch and lock job"`); the underlying driver
60    /// error is preserved on `source`.
61    #[error("Storage operation failed: {message}")]
62    OperationFailed {
63        message: String,
64        #[source]
65        source: Option<Box<dyn std::error::Error + Send + Sync>>,
66    },
67
68    /// Storage capacity exceeded
69    #[error("Storage capacity exceeded: {message}")]
70    CapacityExceeded { message: String },
71
72    /// Concurrent modification detected
73    #[error("Concurrent modification detected for job: {job_id}")]
74    ConcurrentModification { job_id: String },
75
76    /// Database migration errors
77    #[error("Migration error: {message}")]
78    MigrationError { message: String },
79
80    /// Invalid job data format
81    #[error("Invalid job data: {message}")]
82    InvalidJobData { message: String },
83}
84
85impl StorageError {
86    /// Create a connection error with a message
87    pub fn connection<S: Into<String>>(message: S) -> Self {
88        Self::Connection {
89            message: message.into(),
90            source: None,
91        }
92    }
93
94    /// Create a connection error with a message and source error
95    pub fn connection_with_source<S: Into<String>>(
96        message: S,
97        source: Box<dyn std::error::Error + Send + Sync>,
98    ) -> Self {
99        Self::Connection {
100            message: message.into(),
101            source: Some(source),
102        }
103    }
104
105    /// Create a serialization error with a message
106    pub fn serialization<S: Into<String>>(message: S) -> Self {
107        Self::Serialization {
108            message: message.into(),
109            source: None,
110        }
111    }
112
113    /// Create a serialization error with a message and source error
114    pub fn serialization_with_source<S: Into<String>>(
115        message: S,
116        source: Box<dyn std::error::Error + Send + Sync>,
117    ) -> Self {
118        Self::Serialization {
119            message: message.into(),
120            source: Some(source),
121        }
122    }
123
124    /// Create a deserialization error with a message
125    pub fn deserialization<S: Into<String>>(message: S) -> Self {
126        Self::Deserialization {
127            message: message.into(),
128            source: None,
129        }
130    }
131
132    /// Create a deserialization error with a message and source error
133    pub fn deserialization_with_source<S: Into<String>>(
134        message: S,
135        source: Box<dyn std::error::Error + Send + Sync>,
136    ) -> Self {
137        Self::Deserialization {
138            message: message.into(),
139            source: Some(source),
140        }
141    }
142
143    /// Create a job not found error
144    pub fn job_not_found<S: Into<String>>(job_id: S) -> Self {
145        Self::JobNotFound {
146            job_id: job_id.into(),
147        }
148    }
149
150    /// Create a timeout error
151    pub fn timeout(timeout_ms: u64) -> Self {
152        Self::Timeout { timeout_ms }
153    }
154
155    /// Create an unavailable error
156    pub fn unavailable<S: Into<String>>(reason: S) -> Self {
157        Self::Unavailable {
158            reason: reason.into(),
159        }
160    }
161
162    /// Create a configuration error
163    pub fn configuration<S: Into<String>>(message: S) -> Self {
164        Self::Configuration {
165            message: message.into(),
166        }
167    }
168
169    /// Create an operation-failed error without a source (e.g. when the
170    /// failure is logical, not driven by an underlying driver error).
171    pub fn operation_failed<S: Into<String>>(message: S) -> Self {
172        Self::OperationFailed {
173            message: message.into(),
174            source: None,
175        }
176    }
177
178    /// Create an operation-failed error with a captured source.
179    pub fn operation_failed_with_source<S: Into<String>>(
180        message: S,
181        source: Box<dyn std::error::Error + Send + Sync>,
182    ) -> Self {
183        Self::OperationFailed {
184            message: message.into(),
185            source: Some(source),
186        }
187    }
188
189    /// Create a capacity exceeded error
190    pub fn capacity_exceeded<S: Into<String>>(message: S) -> Self {
191        Self::CapacityExceeded {
192            message: message.into(),
193        }
194    }
195
196    /// Create a concurrent modification error
197    pub fn concurrent_modification<S: Into<String>>(job_id: S) -> Self {
198        Self::ConcurrentModification {
199            job_id: job_id.into(),
200        }
201    }
202
203    /// `Connection` shorthand that takes the raw error as a typed
204    /// generic, boxes it, and attaches it as the source — saves the
205    /// caller a `Box::new(...)`.
206    pub fn conn_err<M, E>(message: M, source: E) -> Self
207    where
208        M: Into<String>,
209        E: std::error::Error + Send + Sync + 'static,
210    {
211        Self::Connection {
212            message: message.into(),
213            source: Some(Box::new(source)),
214        }
215    }
216
217    /// `Serialization` shorthand with a captured source.
218    pub fn ser_err<M, E>(message: M, source: E) -> Self
219    where
220        M: Into<String>,
221        E: std::error::Error + Send + Sync + 'static,
222    {
223        Self::Serialization {
224            message: message.into(),
225            source: Some(Box::new(source)),
226        }
227    }
228
229    /// `Deserialization` shorthand with a captured source.
230    pub fn de_err<M, E>(message: M, source: E) -> Self
231    where
232        M: Into<String>,
233        E: std::error::Error + Send + Sync + 'static,
234    {
235        Self::Deserialization {
236            message: message.into(),
237            source: Some(Box::new(source)),
238        }
239    }
240
241    /// `OperationFailed` shorthand with a captured source.
242    pub fn op_err<M, E>(message: M, source: E) -> Self
243    where
244        M: Into<String>,
245        E: std::error::Error + Send + Sync + 'static,
246    {
247        Self::OperationFailed {
248            message: message.into(),
249            source: Some(Box::new(source)),
250        }
251    }
252}
253
254// Convert StorageError to QmlError for unified error handling
255impl From<StorageError> for QmlError {
256    fn from(err: StorageError) -> Self {
257        match err {
258            StorageError::JobNotFound { job_id } => QmlError::JobNotFound { job_id },
259            // Both Serialization and Deserialization map onto
260            // QmlError::SerializationError. QmlError doesn't currently
261            // separate the two; if it grows a Deserialization variant,
262            // this is the place to split them.
263            StorageError::Serialization { message, .. }
264            | StorageError::Deserialization { message, .. } => {
265                QmlError::SerializationError { message }
266            }
267            _ => QmlError::StorageError {
268                message: err.to_string(),
269            },
270        }
271    }
272}