1use prax_query::QueryError;
4use thiserror::Error;
5
6pub type MongoResult<T> = Result<T, MongoError>;
8
9#[derive(Error, Debug)]
11pub enum MongoError {
12 #[error("mongodb error: {0}")]
14 Driver(#[from] mongodb::error::Error),
15
16 #[error("bson error: {0}")]
18 Bson(#[from] bson::ser::Error),
19
20 #[error("bson deserialization error: {0}")]
22 BsonDe(#[from] bson::de::Error),
23
24 #[error("configuration error: {0}")]
26 Config(String),
27
28 #[error("connection error: {0}")]
30 Connection(String),
31
32 #[error("query error: {0}")]
34 Query(String),
35
36 #[error("document not found: {0}")]
38 NotFound(String),
39
40 #[error("serialization error: {0}")]
42 Serialization(String),
43
44 #[error("invalid object id: {0}")]
46 InvalidObjectId(String),
47
48 #[error("operation timed out after {0}ms")]
50 Timeout(u64),
51
52 #[error("internal error: {0}")]
54 Internal(String),
55}
56
57impl MongoError {
58 pub fn config(message: impl Into<String>) -> Self {
60 Self::Config(message.into())
61 }
62
63 pub fn connection(message: impl Into<String>) -> Self {
65 Self::Connection(message.into())
66 }
67
68 pub fn query(message: impl Into<String>) -> Self {
70 Self::Query(message.into())
71 }
72
73 pub fn not_found(message: impl Into<String>) -> Self {
75 Self::NotFound(message.into())
76 }
77
78 pub fn serialization(message: impl Into<String>) -> Self {
80 Self::Serialization(message.into())
81 }
82
83 pub fn invalid_object_id(message: impl Into<String>) -> Self {
85 Self::InvalidObjectId(message.into())
86 }
87
88 pub fn is_connection_error(&self) -> bool {
90 matches!(self, Self::Connection(_))
91 }
92
93 pub fn is_timeout(&self) -> bool {
95 matches!(self, Self::Timeout(_))
96 }
97
98 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 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}