Skip to main content

prax_mongodb/
error.rs

1//! Error types for MongoDB operations.
2
3use prax_query::QueryError;
4use thiserror::Error;
5
6/// Result type for MongoDB operations.
7pub type MongoResult<T> = Result<T, MongoError>;
8
9/// Errors that can occur during MongoDB operations.
10#[derive(Error, Debug)]
11pub enum MongoError {
12    /// MongoDB driver error.
13    #[error("mongodb error: {0}")]
14    Driver(#[from] mongodb::error::Error),
15
16    /// BSON serialization/deserialization error.
17    #[error("bson error: {0}")]
18    Bson(#[from] bson::ser::Error),
19
20    /// BSON deserialization error.
21    #[error("bson deserialization error: {0}")]
22    BsonDe(#[from] bson::de::Error),
23
24    /// Configuration error.
25    #[error("configuration error: {0}")]
26    Config(String),
27
28    /// Connection error.
29    #[error("connection error: {0}")]
30    Connection(String),
31
32    /// Query execution error.
33    #[error("query error: {0}")]
34    Query(String),
35
36    /// Document not found.
37    #[error("document not found: {0}")]
38    NotFound(String),
39
40    /// Document serialization error.
41    #[error("serialization error: {0}")]
42    Serialization(String),
43
44    /// Invalid ObjectId.
45    #[error("invalid object id: {0}")]
46    InvalidObjectId(String),
47
48    /// Timeout error.
49    #[error("operation timed out after {0}ms")]
50    Timeout(u64),
51
52    /// Internal error.
53    #[error("internal error: {0}")]
54    Internal(String),
55}
56
57impl MongoError {
58    /// Create a configuration error.
59    pub fn config(message: impl Into<String>) -> Self {
60        Self::Config(message.into())
61    }
62
63    /// Create a connection error.
64    pub fn connection(message: impl Into<String>) -> Self {
65        Self::Connection(message.into())
66    }
67
68    /// Create a query error.
69    pub fn query(message: impl Into<String>) -> Self {
70        Self::Query(message.into())
71    }
72
73    /// Create a not found error.
74    pub fn not_found(message: impl Into<String>) -> Self {
75        Self::NotFound(message.into())
76    }
77
78    /// Create a serialization error.
79    pub fn serialization(message: impl Into<String>) -> Self {
80        Self::Serialization(message.into())
81    }
82
83    /// Create an invalid object id error.
84    pub fn invalid_object_id(message: impl Into<String>) -> Self {
85        Self::InvalidObjectId(message.into())
86    }
87
88    /// Check if this is a connection error.
89    pub fn is_connection_error(&self) -> bool {
90        matches!(self, Self::Connection(_))
91    }
92
93    /// Check if this is a timeout error.
94    pub fn is_timeout(&self) -> bool {
95        matches!(self, Self::Timeout(_))
96    }
97
98    /// Check if this is a not found error.
99    pub fn is_not_found(&self) -> bool {
100        matches!(self, Self::NotFound(_))
101    }
102}
103
104impl From<bson::oid::Error> for MongoError {
105    fn from(err: bson::oid::Error) -> Self {
106        MongoError::InvalidObjectId(err.to_string())
107    }
108}
109
110impl From<MongoError> for QueryError {
111    fn from(err: MongoError) -> Self {
112        match err {
113            MongoError::Driver(e) => {
114                let msg = e.to_string();
115
116                // Check for specific MongoDB error types
117                if msg.contains("duplicate key") {
118                    return QueryError::constraint_violation("_id", msg);
119                }
120                if msg.contains("connection") || msg.contains("timeout") {
121                    return QueryError::connection(msg);
122                }
123
124                QueryError::database(msg)
125            }
126            MongoError::Bson(e) => QueryError::serialization(e.to_string()),
127            MongoError::BsonDe(e) => QueryError::serialization(e.to_string()),
128            MongoError::Config(msg) => QueryError::connection(msg),
129            MongoError::Connection(msg) => QueryError::connection(msg),
130            MongoError::Query(msg) => QueryError::database(msg),
131            MongoError::NotFound(msg) => QueryError::not_found(&msg),
132            MongoError::Serialization(msg) => QueryError::serialization(msg),
133            MongoError::InvalidObjectId(msg) => QueryError::invalid_input("_id", msg),
134            MongoError::Timeout(ms) => QueryError::timeout(ms),
135            MongoError::Internal(msg) => QueryError::internal(msg),
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_error_creation() {
146        let err = MongoError::config("invalid URI");
147        assert!(matches!(err, MongoError::Config(_)));
148
149        let err = MongoError::connection("connection refused");
150        assert!(err.is_connection_error());
151
152        let err = MongoError::Timeout(5000);
153        assert!(err.is_timeout());
154
155        let err = MongoError::not_found("user");
156        assert!(err.is_not_found());
157    }
158
159    #[test]
160    fn test_error_display() {
161        let err = MongoError::config("test error");
162        assert_eq!(err.to_string(), "configuration error: test error");
163
164        let err = MongoError::NotFound("user".to_string());
165        assert_eq!(err.to_string(), "document not found: user");
166    }
167
168    #[test]
169    fn test_into_query_error() {
170        let mongo_err = MongoError::Timeout(1000);
171        let query_err: QueryError = mongo_err.into();
172        assert!(query_err.is_timeout());
173
174        let mongo_err = MongoError::not_found("User");
175        let query_err: QueryError = mongo_err.into();
176        assert!(query_err.is_not_found());
177    }
178}