torii_core/error/
utilities.rs

1use crate::{
2    Error,
3    error::{StorageError, ValidationError},
4};
5
6/// Extension trait for Result types to simplify database error mapping
7///
8/// This trait provides convenient methods to convert database errors into Torii errors,
9/// reducing boilerplate code throughout the codebase.
10///
11/// # Example
12///
13/// ```rust
14/// use torii_core::error::utilities::DatabaseResultExt;
15///
16/// // Instead of:
17/// // query.execute(&pool).await.map_err(|e| Error::Storage(StorageError::Database(e.to_string())))?;
18///
19/// // Use:
20/// query.execute(&pool).await.map_db_err()?;
21/// ```
22pub trait DatabaseResultExt<T> {
23    /// Convert a database error to a Torii storage error
24    fn map_db_err(self) -> Result<T, Error>;
25
26    /// Convert a database error to a Torii storage error with additional context
27    fn map_db_err_with_context(self, context: &str) -> Result<T, Error>;
28}
29
30impl<T, E: std::fmt::Display> DatabaseResultExt<T> for Result<T, E> {
31    fn map_db_err(self) -> Result<T, Error> {
32        self.map_err(|e| Error::Storage(StorageError::Database(e.to_string())))
33    }
34
35    fn map_db_err_with_context(self, context: &str) -> Result<T, Error> {
36        self.map_err(|e| Error::Storage(StorageError::Database(format!("{context}: {e}"))))
37    }
38}
39
40/// Extension trait for Option types to simplify required field validation
41///
42/// This trait provides convenient methods to convert None values into ValidationError,
43/// reducing boilerplate in builder patterns.
44///
45/// # Example
46///
47/// ```rust
48/// use torii_core::error::utilities::RequiredFieldExt;
49///
50/// // Instead of:
51/// // let email = self.email.ok_or(ValidationError::MissingField("Email is required".to_string()))?;
52///
53/// // Use:
54/// let email = self.email.require_field("Email")?;
55/// ```
56pub trait RequiredFieldExt<T> {
57    /// Convert None to a ValidationError::MissingField
58    fn require_field(self, field_name: &str) -> Result<T, ValidationError>;
59}
60
61impl<T> RequiredFieldExt<T> for Option<T> {
62    fn require_field(self, field_name: &str) -> Result<T, ValidationError> {
63        self.ok_or_else(|| ValidationError::MissingField(format!("{field_name} is required")))
64    }
65}
66
67/// Macro to convert any error to a storage database error
68///
69/// This macro reduces boilerplate when you need to convert various error types
70/// to the standard Torii database error format.
71///
72/// # Example
73///
74/// ```rust
75/// use torii_core::map_storage_err;
76///
77/// // Instead of:
78/// // query.execute(&pool).await.map_err(|e| Error::Storage(StorageError::Database(e.to_string())))?;
79///
80/// // Use:
81/// map_storage_err!(query.execute(&pool).await)?;
82/// ```
83#[macro_export]
84macro_rules! map_storage_err {
85    ($result:expr) => {
86        $result.map_err(|e| {
87            $crate::Error::Storage($crate::error::StorageError::Database(e.to_string()))
88        })
89    };
90}
91
92/// Macro to convert any error to a storage database error with context
93///
94/// # Example
95///
96/// ```rust
97/// use torii_core::map_storage_err_with_context;
98///
99/// map_storage_err_with_context!(query.execute(&pool).await, "Failed to create user")?;
100/// ```
101#[macro_export]
102macro_rules! map_storage_err_with_context {
103    ($result:expr, $context:expr) => {
104        $result.map_err(|e| {
105            $crate::Error::Storage($crate::error::StorageError::Database(format!(
106                "{}: {}",
107                $context, e
108            )))
109        })
110    };
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::error::{StorageError, ValidationError};
117
118    #[test]
119    fn test_database_result_ext() {
120        let error_result: Result<i32, &str> = Err("database connection failed");
121        let mapped = error_result.map_db_err();
122
123        assert!(mapped.is_err());
124        match mapped.unwrap_err() {
125            Error::Storage(StorageError::Database(msg)) => {
126                assert_eq!(msg, "database connection failed");
127            }
128            _ => panic!("Expected storage database error"),
129        }
130    }
131
132    #[test]
133    fn test_database_result_ext_with_context() {
134        let error_result: Result<i32, &str> = Err("timeout");
135        let mapped = error_result.map_db_err_with_context("Failed to save user");
136
137        assert!(mapped.is_err());
138        match mapped.unwrap_err() {
139            Error::Storage(StorageError::Database(msg)) => {
140                assert_eq!(msg, "Failed to save user: timeout");
141            }
142            _ => panic!("Expected storage database error"),
143        }
144    }
145
146    #[test]
147    fn test_required_field_ext_some() {
148        let some_value = Some("test@example.com".to_string());
149        let result = some_value.require_field("Email");
150
151        assert!(result.is_ok());
152        assert_eq!(result.unwrap(), "test@example.com");
153    }
154
155    #[test]
156    fn test_required_field_ext_none() {
157        let none_value: Option<String> = None;
158        let result = none_value.require_field("Email");
159
160        assert!(result.is_err());
161        match result.unwrap_err() {
162            ValidationError::MissingField(msg) => {
163                assert_eq!(msg, "Email is required");
164            }
165            _ => panic!("Expected missing field validation error"),
166        }
167    }
168
169    #[test]
170    fn test_map_storage_err_macro() {
171        let error_result: Result<i32, &str> = Err("query failed");
172        let mapped = map_storage_err!(error_result);
173
174        assert!(mapped.is_err());
175        match mapped.unwrap_err() {
176            Error::Storage(StorageError::Database(msg)) => {
177                assert_eq!(msg, "query failed");
178            }
179            _ => panic!("Expected storage database error"),
180        }
181    }
182
183    #[test]
184    fn test_map_storage_err_with_context_macro() {
185        let error_result: Result<i32, &str> = Err("foreign key constraint");
186        let mapped = map_storage_err_with_context!(error_result, "Creating user");
187
188        assert!(mapped.is_err());
189        match mapped.unwrap_err() {
190            Error::Storage(StorageError::Database(msg)) => {
191                assert_eq!(msg, "Creating user: foreign key constraint");
192            }
193            _ => panic!("Expected storage database error"),
194        }
195    }
196}