use std::sync::Arc;
use bson::{Document, doc};
use mongodb::{Client, Collection, Database};
use tracing::{debug, info};
use crate::config::MongoConfig;
use crate::error::{MongoError, MongoResult};
#[derive(Clone)]
pub struct MongoClient {
client: Client,
database: Database,
config: Arc<MongoConfig>,
}
impl MongoClient {
pub async fn new(config: MongoConfig) -> MongoResult<Self> {
let options = config.to_client_options().await?;
let client = Client::with_options(options)
.map_err(|e| MongoError::connection(format!("failed to create client: {}", e)))?;
let database = client.database(&config.database);
info!(
uri = %config.uri,
database = %config.database,
"MongoDB client created"
);
Ok(Self {
client,
database,
config: Arc::new(config),
})
}
pub fn builder() -> MongoClientBuilder {
MongoClientBuilder::new()
}
pub fn collection<T>(&self, name: &str) -> Collection<T>
where
T: Send + Sync,
{
self.database.collection(name)
}
pub fn collection_doc(&self, name: &str) -> Collection<Document> {
self.database.collection(name)
}
pub fn database(&self) -> &Database {
&self.database
}
pub fn get_database(&self, name: &str) -> Database {
self.client.database(name)
}
pub fn inner(&self) -> &Client {
&self.client
}
pub fn config(&self) -> &MongoConfig {
&self.config
}
pub async fn is_healthy(&self) -> bool {
self.database
.run_command(doc! { "ping": 1 }, None)
.await
.is_ok()
}
pub async fn list_collections(&self) -> MongoResult<Vec<String>> {
let names = self
.database
.list_collection_names(None)
.await
.map_err(MongoError::from)?;
Ok(names)
}
pub async fn drop_collection(&self, name: &str) -> MongoResult<()> {
debug!(collection = %name, "Dropping collection");
self.database
.collection::<Document>(name)
.drop(None)
.await
.map_err(MongoError::from)?;
Ok(())
}
pub async fn create_index(
&self,
collection: &str,
keys: Document,
unique: bool,
) -> MongoResult<String> {
use mongodb::IndexModel;
use mongodb::options::IndexOptions;
let options = IndexOptions::builder().unique(unique).build();
let model = IndexModel::builder().keys(keys).options(options).build();
let result = self
.database
.collection::<Document>(collection)
.create_index(model, None)
.await
.map_err(MongoError::from)?;
Ok(result.index_name)
}
pub async fn run_command(&self, command: Document) -> MongoResult<Document> {
let result = self
.database
.run_command(command, None)
.await
.map_err(MongoError::from)?;
Ok(result)
}
pub async fn start_session(&self) -> MongoResult<mongodb::ClientSession> {
let session = self
.client
.start_session(None)
.await
.map_err(MongoError::from)?;
Ok(session)
}
}
#[derive(Debug, Default)]
pub struct MongoClientBuilder {
uri: Option<String>,
database: Option<String>,
app_name: Option<String>,
max_pool_size: Option<u32>,
min_pool_size: Option<u32>,
connect_timeout: Option<std::time::Duration>,
direct_connection: Option<bool>,
}
impl MongoClientBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn uri(mut self, uri: impl Into<String>) -> Self {
self.uri = Some(uri.into());
self
}
pub fn database(mut self, database: impl Into<String>) -> Self {
self.database = Some(database.into());
self
}
pub fn app_name(mut self, name: impl Into<String>) -> Self {
self.app_name = Some(name.into());
self
}
pub fn max_pool_size(mut self, size: u32) -> Self {
self.max_pool_size = Some(size);
self
}
pub fn min_pool_size(mut self, size: u32) -> Self {
self.min_pool_size = Some(size);
self
}
pub fn connect_timeout(mut self, duration: std::time::Duration) -> Self {
self.connect_timeout = Some(duration);
self
}
pub fn direct_connection(mut self, enabled: bool) -> Self {
self.direct_connection = Some(enabled);
self
}
pub async fn build(self) -> MongoResult<MongoClient> {
let mut config_builder = MongoConfig::builder();
if let Some(uri) = self.uri {
config_builder = config_builder.uri(uri);
}
if let Some(database) = self.database {
config_builder = config_builder.database(database);
}
if let Some(app_name) = self.app_name {
config_builder = config_builder.app_name(app_name);
}
if let Some(max_pool) = self.max_pool_size {
config_builder = config_builder.max_pool_size(max_pool);
}
if let Some(min_pool) = self.min_pool_size {
config_builder = config_builder.min_pool_size(min_pool);
}
if let Some(timeout) = self.connect_timeout {
config_builder = config_builder.connect_timeout(timeout);
}
if let Some(direct) = self.direct_connection {
config_builder = config_builder.direct_connection(direct);
}
let config = config_builder.build()?;
MongoClient::new(config).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_client_builder() {
let builder = MongoClientBuilder::new()
.uri("mongodb://localhost:27017")
.database("test")
.max_pool_size(20);
assert_eq!(builder.uri, Some("mongodb://localhost:27017".to_string()));
assert_eq!(builder.database, Some("test".to_string()));
assert_eq!(builder.max_pool_size, Some(20));
}
}