use std::time::Duration;
use mongodb::options::ClientOptions;
use crate::error::{MongoError, MongoResult};
#[derive(Debug, Clone)]
pub struct MongoConfig {
pub uri: String,
pub database: String,
pub app_name: Option<String>,
pub min_pool_size: Option<u32>,
pub max_pool_size: Option<u32>,
pub max_idle_time: Option<Duration>,
pub connect_timeout: Option<Duration>,
pub server_selection_timeout: Option<Duration>,
pub socket_timeout: Option<Duration>,
pub compressors: Option<Vec<String>>,
pub read_preference: Option<ReadPreference>,
pub write_concern: Option<WriteConcern>,
pub retry_writes: Option<bool>,
pub retry_reads: Option<bool>,
pub direct_connection: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ReadPreference {
#[default]
Primary,
PrimaryPreferred,
Secondary,
SecondaryPreferred,
Nearest,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WriteConcern {
W(u32),
Majority,
Custom(String),
}
impl Default for MongoConfig {
fn default() -> Self {
Self {
uri: "mongodb://localhost:27017".to_string(),
database: String::new(),
app_name: Some("prax".to_string()),
min_pool_size: None,
max_pool_size: Some(10),
max_idle_time: Some(Duration::from_secs(300)),
connect_timeout: Some(Duration::from_secs(10)),
server_selection_timeout: Some(Duration::from_secs(30)),
socket_timeout: None,
compressors: None,
read_preference: Some(ReadPreference::Primary),
write_concern: None,
retry_writes: Some(true),
retry_reads: Some(true),
direct_connection: None,
}
}
}
impl MongoConfig {
pub fn from_uri(uri: impl Into<String>, database: impl Into<String>) -> Self {
Self {
uri: uri.into(),
database: database.into(),
..Self::default()
}
}
pub fn builder() -> MongoConfigBuilder {
MongoConfigBuilder::new()
}
pub async fn to_client_options(&self) -> MongoResult<ClientOptions> {
let mut options = ClientOptions::parse(&self.uri)
.await
.map_err(|e| MongoError::config(format!("failed to parse URI: {}", e)))?;
if let Some(ref app_name) = self.app_name {
options.app_name = Some(app_name.clone());
}
if let Some(min_pool) = self.min_pool_size {
options.min_pool_size = Some(min_pool);
}
if let Some(max_pool) = self.max_pool_size {
options.max_pool_size = Some(max_pool);
}
if let Some(max_idle) = self.max_idle_time {
options.max_idle_time = Some(max_idle);
}
if let Some(connect_timeout) = self.connect_timeout {
options.connect_timeout = Some(connect_timeout);
}
if let Some(selection_timeout) = self.server_selection_timeout {
options.server_selection_timeout = Some(selection_timeout);
}
let _ = self.socket_timeout;
let _ = &self.compressors;
if let Some(ref read_pref) = self.read_preference {
options.selection_criteria = Some(match read_pref {
ReadPreference::Primary => mongodb::options::SelectionCriteria::ReadPreference(
mongodb::options::ReadPreference::Primary,
),
ReadPreference::PrimaryPreferred => {
mongodb::options::SelectionCriteria::ReadPreference(
mongodb::options::ReadPreference::PrimaryPreferred {
options: Default::default(),
},
)
}
ReadPreference::Secondary => mongodb::options::SelectionCriteria::ReadPreference(
mongodb::options::ReadPreference::Secondary {
options: Default::default(),
},
),
ReadPreference::SecondaryPreferred => {
mongodb::options::SelectionCriteria::ReadPreference(
mongodb::options::ReadPreference::SecondaryPreferred {
options: Default::default(),
},
)
}
ReadPreference::Nearest => mongodb::options::SelectionCriteria::ReadPreference(
mongodb::options::ReadPreference::Nearest {
options: Default::default(),
},
),
});
}
if let Some(ref wc) = self.write_concern {
options.write_concern = Some(match wc {
WriteConcern::W(n) => mongodb::options::WriteConcern::builder()
.w(mongodb::options::Acknowledgment::Nodes(*n))
.build(),
WriteConcern::Majority => mongodb::options::WriteConcern::builder()
.w(mongodb::options::Acknowledgment::Majority)
.build(),
WriteConcern::Custom(tag) => mongodb::options::WriteConcern::builder()
.w(mongodb::options::Acknowledgment::Custom(tag.clone()))
.build(),
});
}
if let Some(retry_writes) = self.retry_writes {
options.retry_writes = Some(retry_writes);
}
if let Some(retry_reads) = self.retry_reads {
options.retry_reads = Some(retry_reads);
}
if let Some(direct) = self.direct_connection {
options.direct_connection = Some(direct);
}
Ok(options)
}
}
#[derive(Debug, Default)]
pub struct MongoConfigBuilder {
uri: Option<String>,
database: Option<String>,
app_name: Option<String>,
min_pool_size: Option<u32>,
max_pool_size: Option<u32>,
max_idle_time: Option<Duration>,
connect_timeout: Option<Duration>,
server_selection_timeout: Option<Duration>,
socket_timeout: Option<Duration>,
compressors: Option<Vec<String>>,
read_preference: Option<ReadPreference>,
write_concern: Option<WriteConcern>,
retry_writes: Option<bool>,
retry_reads: Option<bool>,
direct_connection: Option<bool>,
}
impl MongoConfigBuilder {
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 min_pool_size(mut self, size: u32) -> Self {
self.min_pool_size = Some(size);
self
}
pub fn max_pool_size(mut self, size: u32) -> Self {
self.max_pool_size = Some(size);
self
}
pub fn max_idle_time(mut self, duration: Duration) -> Self {
self.max_idle_time = Some(duration);
self
}
pub fn connect_timeout(mut self, duration: Duration) -> Self {
self.connect_timeout = Some(duration);
self
}
pub fn server_selection_timeout(mut self, duration: Duration) -> Self {
self.server_selection_timeout = Some(duration);
self
}
pub fn socket_timeout(mut self, duration: Duration) -> Self {
self.socket_timeout = Some(duration);
self
}
pub fn compressors(mut self, compressors: Vec<String>) -> Self {
self.compressors = Some(compressors);
self
}
pub fn read_preference(mut self, pref: ReadPreference) -> Self {
self.read_preference = Some(pref);
self
}
pub fn write_concern(mut self, wc: WriteConcern) -> Self {
self.write_concern = Some(wc);
self
}
pub fn retry_writes(mut self, enabled: bool) -> Self {
self.retry_writes = Some(enabled);
self
}
pub fn retry_reads(mut self, enabled: bool) -> Self {
self.retry_reads = Some(enabled);
self
}
pub fn direct_connection(mut self, enabled: bool) -> Self {
self.direct_connection = Some(enabled);
self
}
pub fn build(self) -> MongoResult<MongoConfig> {
let database = self
.database
.ok_or_else(|| MongoError::config("database name is required"))?;
Ok(MongoConfig {
uri: self
.uri
.unwrap_or_else(|| "mongodb://localhost:27017".to_string()),
database,
app_name: self.app_name.or(Some("prax".to_string())),
min_pool_size: self.min_pool_size,
max_pool_size: self.max_pool_size.or(Some(10)),
max_idle_time: self.max_idle_time.or(Some(Duration::from_secs(300))),
connect_timeout: self.connect_timeout.or(Some(Duration::from_secs(10))),
server_selection_timeout: self
.server_selection_timeout
.or(Some(Duration::from_secs(30))),
socket_timeout: self.socket_timeout,
compressors: self.compressors,
read_preference: self.read_preference.or(Some(ReadPreference::Primary)),
write_concern: self.write_concern,
retry_writes: self.retry_writes.or(Some(true)),
retry_reads: self.retry_reads.or(Some(true)),
direct_connection: self.direct_connection,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_from_uri() {
let config = MongoConfig::from_uri("mongodb://localhost:27017", "mydb");
assert_eq!(config.uri, "mongodb://localhost:27017");
assert_eq!(config.database, "mydb");
}
#[test]
fn test_config_builder() {
let config = MongoConfig::builder()
.uri("mongodb://localhost:27017")
.database("mydb")
.app_name("test-app")
.max_pool_size(20)
.build()
.unwrap();
assert_eq!(config.database, "mydb");
assert_eq!(config.app_name, Some("test-app".to_string()));
assert_eq!(config.max_pool_size, Some(20));
}
#[test]
fn test_config_builder_missing_database() {
let result = MongoConfig::builder()
.uri("mongodb://localhost:27017")
.build();
assert!(result.is_err());
}
#[test]
fn test_read_preference_default() {
let pref: ReadPreference = Default::default();
assert_eq!(pref, ReadPreference::Primary);
}
}