use prax_query::QueryError;
use thiserror::Error;
pub type MongoResult<T> = Result<T, MongoError>;
#[derive(Error, Debug)]
pub enum MongoError {
#[error("mongodb error: {0}")]
Driver(#[from] mongodb::error::Error),
#[error("bson error: {0}")]
Bson(#[from] bson::ser::Error),
#[error("bson deserialization error: {0}")]
BsonDe(#[from] bson::de::Error),
#[error("configuration error: {0}")]
Config(String),
#[error("connection error: {0}")]
Connection(String),
#[error("query error: {0}")]
Query(String),
#[error("document not found: {0}")]
NotFound(String),
#[error("serialization error: {0}")]
Serialization(String),
#[error("invalid object id: {0}")]
InvalidObjectId(String),
#[error("operation timed out after {0}ms")]
Timeout(u64),
#[error("internal error: {0}")]
Internal(String),
}
impl MongoError {
pub fn config(message: impl Into<String>) -> Self {
Self::Config(message.into())
}
pub fn connection(message: impl Into<String>) -> Self {
Self::Connection(message.into())
}
pub fn query(message: impl Into<String>) -> Self {
Self::Query(message.into())
}
pub fn not_found(message: impl Into<String>) -> Self {
Self::NotFound(message.into())
}
pub fn serialization(message: impl Into<String>) -> Self {
Self::Serialization(message.into())
}
pub fn invalid_object_id(message: impl Into<String>) -> Self {
Self::InvalidObjectId(message.into())
}
pub fn is_connection_error(&self) -> bool {
matches!(self, Self::Connection(_))
}
pub fn is_timeout(&self) -> bool {
matches!(self, Self::Timeout(_))
}
pub fn is_not_found(&self) -> bool {
matches!(self, Self::NotFound(_))
}
}
impl From<bson::oid::Error> for MongoError {
fn from(err: bson::oid::Error) -> Self {
MongoError::InvalidObjectId(err.to_string())
}
}
impl From<MongoError> for QueryError {
fn from(err: MongoError) -> Self {
match err {
MongoError::Driver(e) => {
let msg = e.to_string();
if msg.contains("duplicate key") {
return QueryError::constraint_violation("_id", msg);
}
if msg.contains("connection") || msg.contains("timeout") {
return QueryError::connection(msg);
}
QueryError::database(msg)
}
MongoError::Bson(e) => QueryError::serialization(e.to_string()),
MongoError::BsonDe(e) => QueryError::serialization(e.to_string()),
MongoError::Config(msg) => QueryError::connection(msg),
MongoError::Connection(msg) => QueryError::connection(msg),
MongoError::Query(msg) => QueryError::database(msg),
MongoError::NotFound(msg) => QueryError::not_found(&msg),
MongoError::Serialization(msg) => QueryError::serialization(msg),
MongoError::InvalidObjectId(msg) => QueryError::invalid_input("_id", msg),
MongoError::Timeout(ms) => QueryError::timeout(ms),
MongoError::Internal(msg) => QueryError::internal(msg),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = MongoError::config("invalid URI");
assert!(matches!(err, MongoError::Config(_)));
let err = MongoError::connection("connection refused");
assert!(err.is_connection_error());
let err = MongoError::Timeout(5000);
assert!(err.is_timeout());
let err = MongoError::not_found("user");
assert!(err.is_not_found());
}
#[test]
fn test_error_display() {
let err = MongoError::config("test error");
assert_eq!(err.to_string(), "configuration error: test error");
let err = MongoError::NotFound("user".to_string());
assert_eq!(err.to_string(), "document not found: user");
}
#[test]
fn test_into_query_error() {
let mongo_err = MongoError::Timeout(1000);
let query_err: QueryError = mongo_err.into();
assert!(query_err.is_timeout());
let mongo_err = MongoError::not_found("User");
let query_err: QueryError = mongo_err.into();
assert!(query_err.is_not_found());
}
}