toasty_core/
error.rs

1mod adhoc;
2mod condition_failed;
3mod connection_pool;
4mod driver_operation_failed;
5mod expression_evaluation_failed;
6mod invalid_connection_url;
7mod invalid_driver_configuration;
8mod invalid_record_count;
9mod invalid_result;
10mod invalid_schema;
11mod invalid_statement;
12mod invalid_type_conversion;
13mod read_only_transaction;
14mod record_not_found;
15mod serialization_failure;
16mod transaction_timeout;
17mod unsupported_feature;
18mod validation;
19
20use adhoc::Adhoc;
21use condition_failed::ConditionFailed;
22use connection_pool::ConnectionPool;
23use driver_operation_failed::DriverOperationFailed;
24use expression_evaluation_failed::ExpressionEvaluationFailed;
25use invalid_connection_url::InvalidConnectionUrl;
26use invalid_driver_configuration::InvalidDriverConfiguration;
27use invalid_record_count::InvalidRecordCount;
28use invalid_result::InvalidResult;
29use invalid_schema::InvalidSchema;
30use invalid_statement::InvalidStatement;
31use invalid_type_conversion::InvalidTypeConversion;
32use read_only_transaction::ReadOnlyTransaction;
33use record_not_found::RecordNotFound;
34use serialization_failure::SerializationFailure;
35use std::sync::Arc;
36use transaction_timeout::TransactionTimeout;
37use unsupported_feature::UnsupportedFeature;
38use validation::ValidationFailed;
39
40/// An error that can occur in Toasty.
41#[derive(Clone)]
42pub struct Error {
43    inner: Arc<ErrorInner>,
44}
45
46/// Trait for types that can be converted into an Error.
47pub trait IntoError {
48    /// Converts this type into an Error.
49    fn into_error(self) -> Error;
50}
51
52#[derive(Debug)]
53struct ErrorInner {
54    kind: ErrorKind,
55    cause: Option<Error>,
56}
57
58#[derive(Debug)]
59enum ErrorKind {
60    Adhoc(Adhoc),
61    DriverOperationFailed(DriverOperationFailed),
62    ConnectionPool(ConnectionPool),
63    ExpressionEvaluationFailed(ExpressionEvaluationFailed),
64    InvalidConnectionUrl(InvalidConnectionUrl),
65    InvalidDriverConfiguration(InvalidDriverConfiguration),
66    InvalidTypeConversion(InvalidTypeConversion),
67    InvalidRecordCount(InvalidRecordCount),
68    RecordNotFound(RecordNotFound),
69    InvalidResult(InvalidResult),
70    InvalidSchema(InvalidSchema),
71    InvalidStatement(InvalidStatement),
72    ReadOnlyTransaction(ReadOnlyTransaction),
73    SerializationFailure(SerializationFailure),
74    TransactionTimeout(TransactionTimeout),
75    UnsupportedFeature(UnsupportedFeature),
76    ValidationFailed(ValidationFailed),
77    ConditionFailed(ConditionFailed),
78}
79
80impl Error {
81    /// Adds context to this error.
82    ///
83    /// Context is displayed in reverse order: the most recently added context is shown first,
84    /// followed by earlier context, ending with the root cause.
85    pub fn context(self, consequent: impl IntoError) -> Error {
86        self.context_impl(consequent.into_error())
87    }
88
89    fn context_impl(self, consequent: Error) -> Error {
90        let mut err = consequent;
91        let inner = Arc::get_mut(&mut err.inner).unwrap();
92        assert!(
93            inner.cause.is_none(),
94            "consequent error must not already have a cause"
95        );
96        inner.cause = Some(self);
97        err
98    }
99
100    fn chain(&self) -> impl Iterator<Item = &Error> {
101        let mut err = self;
102        core::iter::once(err).chain(core::iter::from_fn(move || {
103            err = err.inner.cause.as_ref()?;
104            Some(err)
105        }))
106    }
107
108    fn kind(&self) -> &ErrorKind {
109        &self.inner.kind
110    }
111}
112
113impl std::error::Error for Error {
114    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
115        match self.kind() {
116            ErrorKind::DriverOperationFailed(err) => Some(err),
117            ErrorKind::ConnectionPool(err) => Some(err),
118            _ => None,
119        }
120    }
121}
122
123impl core::fmt::Display for Error {
124    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
125        let mut it = self.chain().peekable();
126        while let Some(err) = it.next() {
127            core::fmt::Display::fmt(err.kind(), f)?;
128            if it.peek().is_some() {
129                f.write_str(": ")?;
130            }
131        }
132        Ok(())
133    }
134}
135
136impl core::fmt::Debug for Error {
137    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
138        if !f.alternate() {
139            core::fmt::Display::fmt(self, f)
140        } else {
141            f.debug_struct("Error")
142                .field("kind", &self.inner.kind)
143                .field("cause", &self.inner.cause)
144                .finish()
145        }
146    }
147}
148
149impl core::fmt::Display for ErrorKind {
150    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
151        use self::ErrorKind::*;
152
153        match self {
154            Adhoc(err) => core::fmt::Display::fmt(err, f),
155            DriverOperationFailed(err) => core::fmt::Display::fmt(err, f),
156            ConnectionPool(err) => core::fmt::Display::fmt(err, f),
157            ExpressionEvaluationFailed(err) => core::fmt::Display::fmt(err, f),
158            InvalidConnectionUrl(err) => core::fmt::Display::fmt(err, f),
159            InvalidDriverConfiguration(err) => core::fmt::Display::fmt(err, f),
160            InvalidTypeConversion(err) => core::fmt::Display::fmt(err, f),
161            InvalidRecordCount(err) => core::fmt::Display::fmt(err, f),
162            RecordNotFound(err) => core::fmt::Display::fmt(err, f),
163            InvalidResult(err) => core::fmt::Display::fmt(err, f),
164            InvalidSchema(err) => core::fmt::Display::fmt(err, f),
165            InvalidStatement(err) => core::fmt::Display::fmt(err, f),
166            ReadOnlyTransaction(err) => core::fmt::Display::fmt(err, f),
167            SerializationFailure(err) => core::fmt::Display::fmt(err, f),
168            TransactionTimeout(err) => core::fmt::Display::fmt(err, f),
169            UnsupportedFeature(err) => core::fmt::Display::fmt(err, f),
170            ValidationFailed(err) => core::fmt::Display::fmt(err, f),
171            ConditionFailed(err) => core::fmt::Display::fmt(err, f),
172        }
173    }
174}
175
176impl From<ErrorKind> for Error {
177    fn from(kind: ErrorKind) -> Error {
178        Error {
179            inner: Arc::new(ErrorInner { kind, cause: None }),
180        }
181    }
182}
183
184impl IntoError for Error {
185    fn into_error(self) -> Error {
186        self
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn error_size() {
196        // Ensure Error stays at one word (size of pointer/Arc)
197        let expected_size = core::mem::size_of::<usize>();
198        assert_eq!(expected_size, core::mem::size_of::<Error>());
199    }
200
201    #[test]
202    fn error_from_args() {
203        let err = Error::from_args(format_args!("test error: {}", 42));
204        assert_eq!(err.to_string(), "test error: 42");
205    }
206
207    #[test]
208    fn error_chain_display() {
209        let root = Error::from_args(format_args!("root cause"));
210        let mid = Error::from_args(format_args!("middle context"));
211        let top = Error::from_args(format_args!("top context"));
212
213        let chained = root.context(mid).context(top);
214        assert_eq!(
215            chained.to_string(),
216            "top context: middle context: root cause"
217        );
218    }
219
220    #[test]
221    fn type_conversion_error() {
222        let value = crate::stmt::Value::I64(42);
223        let err = Error::type_conversion(value, "String");
224        assert_eq!(err.to_string(), "cannot convert I64 to String");
225    }
226
227    #[test]
228    fn type_conversion_error_range() {
229        // Simulates usize conversion failure due to range
230        let value = crate::stmt::Value::U64(u64::MAX);
231        let err = Error::type_conversion(value, "usize");
232        assert_eq!(err.to_string(), "cannot convert U64 to usize");
233    }
234
235    #[test]
236    fn record_not_found_with_immediate_context() {
237        let err = Error::record_not_found("table=users key={id: 123}");
238        assert_eq!(
239            err.to_string(),
240            "record not found: table=users key={id: 123}"
241        );
242    }
243
244    #[test]
245    fn record_not_found_with_context_chain() {
246        let err = Error::record_not_found("table=users key={id: 123}")
247            .context(Error::from_args(format_args!("update query failed")))
248            .context(Error::from_args(format_args!("User.update() operation")));
249
250        assert_eq!(
251            err.to_string(),
252            "User.update() operation: update query failed: record not found: table=users key={id: 123}"
253        );
254    }
255
256    #[test]
257    fn invalid_record_count_with_context() {
258        let err = Error::invalid_record_count("expected 1 record, found multiple");
259        assert_eq!(
260            err.to_string(),
261            "invalid record count: expected 1 record, found multiple"
262        );
263    }
264
265    #[test]
266    fn invalid_result_error() {
267        let err = Error::invalid_result("expected Stream, got Count");
268        assert_eq!(
269            err.to_string(),
270            "invalid result: expected Stream, got Count"
271        );
272    }
273
274    #[test]
275    fn validation_length_too_short() {
276        let err = Error::validation_length(3, Some(5), Some(10));
277        assert_eq!(err.to_string(), "value length 3 is too short (minimum: 5)");
278    }
279
280    #[test]
281    fn validation_length_too_long() {
282        let err = Error::validation_length(15, Some(5), Some(10));
283        assert_eq!(err.to_string(), "value length 15 is too long (maximum: 10)");
284    }
285
286    #[test]
287    fn validation_length_exact_mismatch() {
288        let err = Error::validation_length(3, Some(5), Some(5));
289        assert_eq!(
290            err.to_string(),
291            "value length 3 does not match required length 5"
292        );
293    }
294
295    #[test]
296    fn validation_length_min_only() {
297        let err = Error::validation_length(3, Some(5), None);
298        assert_eq!(err.to_string(), "value length 3 is too short (minimum: 5)");
299    }
300
301    #[test]
302    fn validation_length_max_only() {
303        let err = Error::validation_length(15, None, Some(10));
304        assert_eq!(err.to_string(), "value length 15 is too long (maximum: 10)");
305    }
306
307    #[test]
308    fn condition_failed_with_context() {
309        let err = Error::condition_failed("optimistic lock version mismatch");
310        assert_eq!(
311            err.to_string(),
312            "condition failed: optimistic lock version mismatch"
313        );
314    }
315
316    #[test]
317    fn condition_failed_with_format() {
318        let expected = 1;
319        let actual = 0;
320        let err = Error::condition_failed(format!(
321            "expected {} row affected, got {}",
322            expected, actual
323        ));
324        assert_eq!(
325            err.to_string(),
326            "condition failed: expected 1 row affected, got 0"
327        );
328    }
329
330    #[test]
331    fn invalid_schema_error() {
332        let err = Error::invalid_schema("duplicate index name `idx_users`");
333        assert_eq!(
334            err.to_string(),
335            "invalid schema: duplicate index name `idx_users`"
336        );
337    }
338
339    #[test]
340    fn invalid_schema_with_context() {
341        let err = Error::invalid_schema(
342            "auto_increment column `id` in table `users` must have a numeric type, found String",
343        )
344        .context(Error::from_args(format_args!("schema verification failed")));
345        assert_eq!(
346            err.to_string(),
347            "schema verification failed: invalid schema: auto_increment column `id` in table `users` must have a numeric type, found String"
348        );
349    }
350
351    #[test]
352    fn expression_evaluation_failed() {
353        let err = Error::expression_evaluation_failed("failed to resolve argument");
354        assert_eq!(
355            err.to_string(),
356            "expression evaluation failed: failed to resolve argument"
357        );
358    }
359
360    #[test]
361    fn expression_evaluation_failed_with_context() {
362        let err = Error::expression_evaluation_failed("expected boolean value")
363            .context(Error::from_args(format_args!("query execution failed")));
364        assert_eq!(
365            err.to_string(),
366            "query execution failed: expression evaluation failed: expected boolean value"
367        );
368    }
369
370    #[test]
371    fn unsupported_feature() {
372        let err = Error::unsupported_feature("VARCHAR type is not supported by this database");
373        assert_eq!(
374            err.to_string(),
375            "unsupported feature: VARCHAR type is not supported by this database"
376        );
377    }
378
379    #[test]
380    fn unsupported_feature_with_context() {
381        let err = Error::unsupported_feature("type List is not supported by this database")
382            .context(Error::from_args(format_args!("schema creation failed")));
383        assert_eq!(
384            err.to_string(),
385            "schema creation failed: unsupported feature: type List is not supported by this database"
386        );
387    }
388
389    #[test]
390    fn invalid_driver_configuration() {
391        let err = Error::invalid_driver_configuration(
392            "native_varchar is true but storage_types.varchar is None",
393        );
394        assert_eq!(
395            err.to_string(),
396            "invalid driver configuration: native_varchar is true but storage_types.varchar is None"
397        );
398    }
399
400    #[test]
401    fn invalid_driver_configuration_with_context() {
402        let err = Error::invalid_driver_configuration("inconsistent capability flags").context(
403            Error::from_args(format_args!("driver initialization failed")),
404        );
405        assert_eq!(
406            err.to_string(),
407            "driver initialization failed: invalid driver configuration: inconsistent capability flags"
408        );
409    }
410
411    #[test]
412    fn invalid_statement_error() {
413        let err = Error::invalid_statement("field `unknown_field` does not exist on model `User`");
414        assert_eq!(
415            err.to_string(),
416            "invalid statement: field `unknown_field` does not exist on model `User`"
417        );
418    }
419
420    #[test]
421    fn invalid_statement_with_context() {
422        let err = Error::invalid_statement("cannot update primary key field `id`")
423            .context(Error::from_args(format_args!("statement lowering failed")));
424        assert_eq!(
425            err.to_string(),
426            "statement lowering failed: invalid statement: cannot update primary key field `id`"
427        );
428    }
429
430    #[test]
431    fn read_only_transaction_display() {
432        let err = Error::read_only_transaction("cannot execute UPDATE in a read-only transaction");
433        assert_eq!(
434            err.to_string(),
435            "read-only transaction: cannot execute UPDATE in a read-only transaction"
436        );
437    }
438
439    #[test]
440    fn read_only_transaction_is_predicate() {
441        let err = Error::read_only_transaction("write not allowed");
442        assert!(err.is_read_only_transaction());
443    }
444
445    #[test]
446    fn read_only_transaction_predicate_false_for_other_errors() {
447        let err = Error::serialization_failure("concurrent update conflict");
448        assert!(!err.is_read_only_transaction());
449    }
450
451    #[test]
452    fn read_only_transaction_with_context() {
453        let err = Error::read_only_transaction("INSERT not allowed")
454            .context(Error::from_args(format_args!("create user failed")));
455        assert_eq!(
456            err.to_string(),
457            "create user failed: read-only transaction: INSERT not allowed"
458        );
459    }
460}