diff --git a/bin/inx-chronicle/config.example.toml b/bin/inx-chronicle/config.template.toml similarity index 51% rename from bin/inx-chronicle/config.example.toml rename to bin/inx-chronicle/config.template.toml index d598d9fce..b81b506b8 100644 --- a/bin/inx-chronicle/config.example.toml +++ b/bin/inx-chronicle/config.template.toml @@ -1,8 +1,8 @@ [mongodb] -location = "mongodb://localhost:27017" +connect_url = "mongodb://localhost:27017" username = "root" password = "root" [inx] -address = "http://localhost:9029" +connect_url = "http://localhost:9029" connection_retry_interval = "5s" diff --git a/bin/inx-chronicle/src/api/mod.rs b/bin/inx-chronicle/src/api/mod.rs index 4b2742f96..331c05aba 100644 --- a/bin/inx-chronicle/src/api/mod.rs +++ b/bin/inx-chronicle/src/api/mod.rs @@ -17,7 +17,7 @@ mod routes; use async_trait::async_trait; use axum::Server; use chronicle::{ - db::MongoDatabase, + db::MongoDb, runtime::actor::{context::ActorContext, Actor}, }; pub use error::ApiError; @@ -32,13 +32,13 @@ pub type ApiResult = Result; /// The Chronicle API actor #[derive(Debug)] pub struct ApiWorker { - db: MongoDatabase, + db: MongoDb, server_handle: Option<(JoinHandle>, oneshot::Sender<()>)>, } impl ApiWorker { /// Create a new Chronicle API actor from a mongo connection. - pub fn new(db: MongoDatabase) -> Self { + pub fn new(db: MongoDb) -> Self { Self { db, server_handle: None, diff --git a/bin/inx-chronicle/src/api/routes.rs b/bin/inx-chronicle/src/api/routes.rs index f6ed76eb4..2275d6a08 100644 --- a/bin/inx-chronicle/src/api/routes.rs +++ b/bin/inx-chronicle/src/api/routes.rs @@ -4,7 +4,7 @@ use axum::{handler::Handler, routing::get, Extension, Router}; use chronicle::db::{ model::sync::{SyncData, SyncRecord}, - MongoDatabase, + MongoDb, }; use futures::TryStreamExt; use hyper::Method; @@ -17,7 +17,7 @@ use tower_http::{ use super::{error::ApiError, responses::*, ApiResult}; -pub fn routes(db: MongoDatabase) -> Router { +pub fn routes(db: MongoDb) -> Router { #[allow(unused_mut)] let mut router = Router::new().route("/info", get(info)).route("/sync", get(sync)); @@ -51,7 +51,7 @@ async fn info() -> InfoResponse { } } -async fn sync(database: Extension) -> ApiResult { +async fn sync(database: Extension) -> ApiResult { let mut res = database .collection::() .find( diff --git a/bin/inx-chronicle/src/api/stardust/analytics/routes.rs b/bin/inx-chronicle/src/api/stardust/analytics/routes.rs index bc81d338f..14d27714d 100644 --- a/bin/inx-chronicle/src/api/stardust/analytics/routes.rs +++ b/bin/inx-chronicle/src/api/stardust/analytics/routes.rs @@ -6,7 +6,7 @@ use chronicle::{ bson::DocExt, db::{ model::{inclusion_state::LedgerInclusionState, stardust::message::MessageRecord}, - MongoDatabase, + MongoDb, }, stardust::payload::TransactionPayload, }; @@ -25,7 +25,7 @@ pub fn routes() -> Router { } async fn address_analytics( - database: Extension, + database: Extension, TimeRange { start_timestamp, end_timestamp, diff --git a/bin/inx-chronicle/src/api/stardust/explorer/routes.rs b/bin/inx-chronicle/src/api/stardust/explorer/routes.rs index a8749845a..c377e7703 100644 --- a/bin/inx-chronicle/src/api/stardust/explorer/routes.rs +++ b/bin/inx-chronicle/src/api/stardust/explorer/routes.rs @@ -6,7 +6,7 @@ use chronicle::{ bson::{BsonExt, DocExt}, db::{ model::{inclusion_state::LedgerInclusionState, stardust::message::MessageRecord}, - MongoDatabase, + MongoDb, }, }; use futures::TryStreamExt; @@ -28,7 +28,7 @@ pub fn routes() -> Router { } async fn transaction_history( - database: Extension, + database: Extension, Path(address): Path, Pagination { page_size, page }: Pagination, TimeRange { diff --git a/bin/inx-chronicle/src/api/stardust/indexer/routes.rs b/bin/inx-chronicle/src/api/stardust/indexer/routes.rs index 9758228fb..97570b298 100644 --- a/bin/inx-chronicle/src/api/stardust/indexer/routes.rs +++ b/bin/inx-chronicle/src/api/stardust/indexer/routes.rs @@ -8,7 +8,7 @@ use chronicle::{ bson::{BsonExt, DocExt}, db::{ model::{inclusion_state::LedgerInclusionState, stardust::message::MessageRecord}, - MongoDatabase, + MongoDb, }, stardust::{output::OutputId, payload::transaction::TransactionId}, }; @@ -33,7 +33,7 @@ pub fn routes() -> Router { } async fn messages_query( - database: Extension, + database: Extension, query: MessagesQuery, Pagination { page_size, page }: Pagination, Expanded { expanded }: Expanded, @@ -91,7 +91,7 @@ async fn messages_query( } async fn outputs_query( - database: Extension, + database: Extension, query: OutputsQuery, Pagination { page_size, page }: Pagination, Expanded { expanded }: Expanded, diff --git a/bin/inx-chronicle/src/api/stardust/mod.rs b/bin/inx-chronicle/src/api/stardust/mod.rs index 388dba811..945b67f99 100644 --- a/bin/inx-chronicle/src/api/stardust/mod.rs +++ b/bin/inx-chronicle/src/api/stardust/mod.rs @@ -4,7 +4,7 @@ use axum::Router; use chronicle::{ bson::DocExt, - db::{model::stardust::milestone::MilestoneRecord, MongoDatabase}, + db::{model::stardust::milestone::MilestoneRecord, MongoDb}, }; use mongodb::{ bson::{doc, DateTime}, @@ -51,7 +51,7 @@ pub fn routes() -> Router { router } -pub(crate) async fn start_milestone(database: &MongoDatabase, start_timestamp: OffsetDateTime) -> ApiResult { +pub(crate) async fn start_milestone(database: &MongoDb, start_timestamp: OffsetDateTime) -> ApiResult { database .doc_collection::() .find( @@ -69,7 +69,7 @@ pub(crate) async fn start_milestone(database: &MongoDatabase, start_timestamp: O .ok_or(ApiError::NotFound) } -pub(crate) async fn end_milestone(database: &MongoDatabase, end_timestamp: OffsetDateTime) -> ApiResult { +pub(crate) async fn end_milestone(database: &MongoDb, end_timestamp: OffsetDateTime) -> ApiResult { database .doc_collection::() .find( diff --git a/bin/inx-chronicle/src/api/stardust/v2/routes.rs b/bin/inx-chronicle/src/api/stardust/v2/routes.rs index 5f392d956..d8c0f6433 100644 --- a/bin/inx-chronicle/src/api/stardust/v2/routes.rs +++ b/bin/inx-chronicle/src/api/stardust/v2/routes.rs @@ -15,7 +15,7 @@ use chronicle::{ inclusion_state::LedgerInclusionState, stardust::{message::MessageRecord, milestone::MilestoneRecord}, }, - MongoDatabase, + MongoDb, }, stardust::output::OutputId, }; @@ -56,7 +56,7 @@ pub fn routes() -> Router { .route("/milestones/:index", get(milestone)) } -async fn message(database: Extension, Path(message_id): Path) -> ApiResult { +async fn message(database: Extension, Path(message_id): Path) -> ApiResult { let mut rec = database .doc_collection::() .find_one(doc! {"message_id": &message_id}, None) @@ -75,7 +75,7 @@ async fn message(database: Extension, Path(message_id): Path, Path(message_id): Path) -> ApiResult> { +async fn message_raw(database: Extension, Path(message_id): Path) -> ApiResult> { let mut rec = database .doc_collection::() .find_one(doc! {"message_id": &message_id}, None) @@ -86,7 +86,7 @@ async fn message_raw(database: Extension, Path(message_id): Path< } async fn message_metadata( - database: Extension, + database: Extension, Path(message_id): Path, ) -> ApiResult { let mut rec = database @@ -138,7 +138,7 @@ async fn message_metadata( } async fn message_children( - database: Extension, + database: Extension, Path(message_id): Path, Pagination { page_size, page }: Pagination, Expanded { expanded }: Expanded, @@ -182,7 +182,7 @@ async fn message_children( }) } -async fn output(database: Extension, Path(output_id): Path) -> ApiResult { +async fn output(database: Extension, Path(output_id): Path) -> ApiResult { let output_id = OutputId::from_str(&output_id).map_err(ApiError::bad_parse)?; output_by_transaction_id( database, @@ -192,7 +192,7 @@ async fn output(database: Extension, Path(output_id): Path, + database: Extension, Path((transaction_id, idx)): Path<(String, u16)>, ) -> ApiResult { let mut output = database @@ -234,7 +234,7 @@ async fn output_by_transaction_id( } async fn transaction_for_message( - database: Extension, + database: Extension, Path(message_id): Path, ) -> ApiResult { let mut rec = database @@ -253,7 +253,7 @@ async fn transaction_for_message( } async fn transaction_included_message( - database: Extension, + database: Extension, Path(transaction_id): Path, ) -> ApiResult { let mut rec = database @@ -281,7 +281,7 @@ async fn transaction_included_message( }) } -async fn milestone(database: Extension, Path(index): Path) -> ApiResult { +async fn milestone(database: Extension, Path(index): Path) -> ApiResult { database .doc_collection::() .find_one(doc! {"milestone_index": &index}, None) diff --git a/bin/inx-chronicle/src/broker.rs b/bin/inx-chronicle/src/broker.rs index 9dd3df29d..d74f3b960 100644 --- a/bin/inx-chronicle/src/broker.rs +++ b/bin/inx-chronicle/src/broker.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; #[cfg(feature = "stardust")] use chronicle::db::model::stardust; use chronicle::{ - db::{MongoDatabase, MongoDbError}, + db::{MongoDb, MongoDbError}, runtime::{ actor::{context::ActorContext, event::HandleEvent, Actor}, error::RuntimeError, @@ -23,11 +23,11 @@ pub enum BrokerError { #[derive(Debug)] pub struct Broker { - db: MongoDatabase, + db: MongoDb, } impl Broker { - pub fn new(db: MongoDatabase) -> Self { + pub fn new(db: MongoDb) -> Self { Self { db } } } diff --git a/bin/inx-chronicle/src/config.rs b/bin/inx-chronicle/src/config.rs index 1129037bb..472e1f422 100644 --- a/bin/inx-chronicle/src/config.rs +++ b/bin/inx-chronicle/src/config.rs @@ -3,7 +3,7 @@ use std::{fs, path::Path}; -use chronicle::db::MongoConfig; +use chronicle::db::MongoDbConfig; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,28 +20,27 @@ pub enum ConfigError { /// Configuration of Chronicle. #[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Config { - pub mongodb: MongoConfig, - #[cfg(feature = "stardust")] +pub struct ChronicleConfig { + pub mongodb: MongoDbConfig, + #[cfg(feature = "inx")] pub inx: InxConfig, } -impl Config { - /// Reads a configuration file in `.toml` format. +impl ChronicleConfig { pub fn from_file(path: impl AsRef) -> Result { fs::read_to_string(&path) .map_err(ConfigError::FileRead) .and_then(|contents| toml::from_str::(&contents).map_err(ConfigError::TomlDeserialization)) } - /// Applies the appropriate command line arguments to the [`Config`]. + /// Applies the appropriate command line arguments to the [`ChronicleConfig`]. pub fn apply_cli_args(&mut self, args: super::CliArgs) { #[cfg(feature = "stardust")] if let Some(inx) = args.inx { self.inx = InxConfig::new(inx); } - if let Some(db) = args.db { - self.mongodb = MongoConfig::new(db); + if let Some(connect_url) = args.db { + self.mongodb = MongoDbConfig::new().with_connect_url(connect_url); } } } @@ -52,9 +51,9 @@ mod test { #[test] fn config_file_conformity() -> Result<(), ConfigError> { - let _ = Config::from_file(concat!( + let _ = ChronicleConfig::from_file(concat!( env!("CARGO_MANIFEST_DIR"), - "/bin/inx-chronicle/config.example.toml" + "/bin/inx-chronicle/config.template.toml" ))?; Ok(()) diff --git a/bin/inx-chronicle/src/inx/config.rs b/bin/inx-chronicle/src/inx/config.rs index 3886e27ec..63f776bc6 100644 --- a/bin/inx-chronicle/src/inx/config.rs +++ b/bin/inx-chronicle/src/inx/config.rs @@ -3,7 +3,6 @@ use std::time::Duration; -use inx::{client::InxClient, tonic::Channel}; use serde::{Deserialize, Serialize}; pub use super::InxWorkerError; @@ -13,7 +12,7 @@ pub use super::InxWorkerError; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct InxConfig { /// The bind address of node's INX interface. - pub address: String, + pub connect_url: String, /// The time that has to pass until a new connection attempt is made. #[serde(with = "humantime_serde")] pub connection_retry_interval: Duration, @@ -22,7 +21,7 @@ pub struct InxConfig { impl Default for InxConfig { fn default() -> Self { Self { - address: "http://localhost:9029".into(), + connect_url: "http://localhost:9029".into(), connection_retry_interval: Duration::from_secs(5), } } @@ -32,21 +31,8 @@ impl InxConfig { /// Creates a new [`InxConfig`]. The `address` is the address of the node's INX interface. pub fn new(address: impl Into) -> Self { Self { - address: address.into(), + connect_url: address.into(), ..Default::default() } } - - /// Constructs an [`InxClient`] by consuming the [`InxConfig`]. - pub async fn build(&self) -> Result, InxWorkerError> { - let url = url::Url::parse(&self.address)?; - - if url.scheme() != "http" { - return Err(InxWorkerError::InvalidAddress(self.address.clone())); - } - - InxClient::connect(self.address.clone()) - .await - .map_err(InxWorkerError::ConnectionError) - } } diff --git a/bin/inx-chronicle/src/inx/listener.rs b/bin/inx-chronicle/src/inx/listener.rs index 7f29a157c..a557eb944 100644 --- a/bin/inx-chronicle/src/inx/listener.rs +++ b/bin/inx-chronicle/src/inx/listener.rs @@ -56,6 +56,7 @@ impl Actor for InxListener { async fn init(&mut self, cx: &mut ActorContext) -> Result { let message_stream = self.inx.listen_to_messages(MessageFilter {}).await?.into_inner(); + cx.spawn_actor_supervised::( InxStreamListener::new(self.broker_addr.clone())?.with_stream(message_stream), ) diff --git a/bin/inx-chronicle/src/inx/worker.rs b/bin/inx-chronicle/src/inx/worker.rs index 8335a1029..bd8e6f242 100644 --- a/bin/inx-chronicle/src/inx/worker.rs +++ b/bin/inx-chronicle/src/inx/worker.rs @@ -28,14 +28,31 @@ impl InxWorker { } } +pub struct Inx; + +impl Inx { + /// Creates an [`InxClient`] by connecting to the endpoint specified in `inx_config`. + async fn connect(inx_config: &InxConfig) -> Result, InxWorkerError> { + let url = url::Url::parse(&inx_config.connect_url)?; + + if url.scheme() != "http" { + return Err(InxWorkerError::InvalidAddress(inx_config.connect_url.clone())); + } + + InxClient::connect(inx_config.connect_url.clone()) + .await + .map_err(InxWorkerError::ConnectionError) + } +} + #[async_trait] impl Actor for InxWorker { type State = InxClient; type Error = InxWorkerError; async fn init(&mut self, cx: &mut ActorContext) -> Result { - log::info!("Connecting to INX at bind address `{}`.", self.config.address); - let mut inx = self.config.build().await?; + log::info!("Connecting to INX at bind address `{}`.", self.config.connect_url); + let mut inx = Inx::connect(&self.config).await?; log::info!("Connected to INX."); let node_status = inx.read_node_status(NoParams {}).await?.into_inner(); diff --git a/bin/inx-chronicle/src/main.rs b/bin/inx-chronicle/src/main.rs index 976313400..70320fbad 100644 --- a/bin/inx-chronicle/src/main.rs +++ b/bin/inx-chronicle/src/main.rs @@ -17,7 +17,7 @@ use std::{error::Error, ops::Deref}; use async_trait::async_trait; #[cfg(feature = "stardust")] use chronicle::{ - db::MongoDbError, + db::{MongoDb, MongoDbError}, runtime::{ actor::{ addr::Addr, context::ActorContext, error::ActorError, event::HandleEvent, report::Report, util::SpawnActor, @@ -39,7 +39,7 @@ use self::inx::{InxWorker, InxWorkerError}; use self::{ broker::{Broker, BrokerError}, cli::CliArgs, - config::{Config, ConfigError}, + config::{ChronicleConfig, ConfigError}, }; #[derive(Debug, Error)] @@ -58,24 +58,24 @@ pub struct Launcher; #[async_trait] impl Actor for Launcher { - type State = (Config, Addr); + type State = (ChronicleConfig, Addr); type Error = LauncherError; async fn init(&mut self, cx: &mut ActorContext) -> Result { let cli_args = CliArgs::parse(); let mut config = match &cli_args.config { - Some(path) => config::Config::from_file(path)?, + Some(path) => config::ChronicleConfig::from_file(path)?, None => { if let Ok(path) = std::env::var("CONFIG_PATH") { - config::Config::from_file(path)? + ChronicleConfig::from_file(path)? } else { - Config::default() + ChronicleConfig::default() } } }; config.apply_cli_args(cli_args); - let db = config.mongodb.clone().build().await?; + let db = MongoDb::connect(&config.mongodb).await?; let broker_addr = cx.spawn_actor_supervised(Broker::new(db.clone())).await; #[cfg(feature = "inx")] cx.spawn_actor_supervised(InxWorker::new(config.inx.clone(), broker_addr.clone())) @@ -108,7 +108,7 @@ impl HandleEvent> for Launcher { chronicle::db::MongoDbError::DatabaseError(e) => match e.kind.as_ref() { // Only a few possible errors we could potentially recover from ErrorKind::Io(_) | ErrorKind::ServerSelection { message: _, .. } => { - let db = config.mongodb.clone().build().await?; + let db = MongoDb::connect(&config.mongodb).await?; let handle = cx.spawn_actor_supervised(Broker::new(db)).await; *broker_addr = handle; } @@ -215,7 +215,7 @@ impl HandleEvent> for Launcher { } Err(e) => match e.error { ActorError::Result(_) => { - let db = config.mongodb.clone().build().await?; + let db = MongoDb::connect(&config.mongodb).await?; cx.spawn_actor_supervised(ApiWorker::new(db)).await; } ActorError::Panic | ActorError::Aborted => { diff --git a/src/db/mod.rs b/src/db/mod.rs index 9ca62fa9f..e12d94096 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -4,109 +4,9 @@ mod error; /// Module containing database record models. pub mod model; +pub mod mongodb; -pub use error::MongoDbError; -use mongodb::{ - bson::{doc, Document}, - options::{ClientOptions, Credential, UpdateOptions}, - Client, Collection, +pub use self::{ + error::MongoDbError, + mongodb::{MongoDb, MongoDbConfig}, }; -use serde::{Deserialize, Serialize}; - -use self::model::Model; - -/// Name of the MongoDB database. -pub const DB_NAME: &str = "chronicle-test"; - -/// A builder to establish a connection to the database. -#[must_use] -#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub struct MongoConfig { - location: String, - username: Option, - password: Option, -} - -impl Default for MongoConfig { - fn default() -> Self { - Self { - location: "mongodb://localhost:27017".into(), - username: None, - password: None, - } - } -} - -impl MongoConfig { - /// Creates a new [`MongoConfig`]. The `location` is the address of the MongoDB instance. - pub fn new(location: impl Into) -> Self { - Self { - location: location.into(), - username: None, - password: None, - } - } - - /// Sets the username. - pub fn with_username(mut self, username: impl Into) -> Self { - self.username = Some(username.into()); - self - } - - /// Sets the password. - pub fn with_password(mut self, password: impl Into) -> Self { - self.password = Some(password.into()); - self - } - - /// Constructs a [`MongoDatabase`] by consuming the [`MongoConfig`]. - pub async fn build(&self) -> Result { - let mut client_options = ClientOptions::parse(&self.location).await?; - - client_options.app_name = Some("Chronicle".to_string()); - - if let (Some(username), Some(password)) = (&self.username, &self.password) { - let credential = Credential::builder() - .username(username.clone()) - .password(password.clone()) - .build(); - client_options.credential = Some(credential); - } - - let client = Client::with_options(client_options)?; - let db = client.database(DB_NAME); - Ok(MongoDatabase { db }) - } -} - -/// A handle to the underlying MongoDB database. -#[derive(Clone, Debug)] -pub struct MongoDatabase { - db: mongodb::Database, -} - -impl MongoDatabase { - /// Inserts a record of a [`Model`] into the database. - pub async fn upsert_one(&self, model: M) -> Result<(), MongoDbError> { - let doc = crate::bson::to_document(&model)?; - self.db - .collection::(M::COLLECTION) - .update_one( - model.key(), - doc! { "$set": doc }, - UpdateOptions::builder().upsert(true).build(), - ) - .await?; - Ok(()) - } - - /// Gets a model type's collection. - pub fn collection(&self) -> Collection { - self.db.collection(M::COLLECTION) - } - - /// Gets a model type's collection. - pub fn doc_collection(&self) -> Collection { - self.db.collection(M::COLLECTION) - } -} diff --git a/src/db/mongodb.rs b/src/db/mongodb.rs new file mode 100644 index 000000000..e1ade5cc6 --- /dev/null +++ b/src/db/mongodb.rs @@ -0,0 +1,111 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! Holds the `MongoDb` type and its config. + +use mongodb::{ + bson::{doc, Document}, + options::{ClientOptions, Credential, UpdateOptions}, + Client, Collection, +}; +use serde::{Deserialize, Serialize}; + +use super::{model::Model, MongoDbError}; + +/// Name of the MongoDB database. +pub const DB_NAME: &str = "chronicle-test"; +const CONNECT_URL_DEFAULT: &str = "mongodb://localhost:27017"; + +/// A handle to the underlying `MongoDB` database. +#[derive(Clone, Debug)] +pub struct MongoDb(mongodb::Database); + +impl MongoDb { + /// Constructs a [`MongoDb`] by connecting to a MongoDB instance. + pub async fn connect(config: &MongoDbConfig) -> Result { + let mut client_options = ClientOptions::parse(&config.connect_url).await?; + + client_options.app_name = Some("Chronicle".to_string()); + + if let (Some(username), Some(password)) = (&config.username, &config.password) { + let credential = Credential::builder() + .username(username.clone()) + .password(password.clone()) + .build(); + client_options.credential = Some(credential); + } + + let client = Client::with_options(client_options)?; + let db = client.database(DB_NAME); + + Ok(MongoDb(db)) + } + + /// Inserts a record of a [`Model`] into the database. + pub async fn upsert_one(&self, model: M) -> Result<(), MongoDbError> { + let doc = crate::bson::to_document(&model)?; + self.0 + .collection::(M::COLLECTION) + .update_one( + model.key(), + doc! { "$set": doc }, + UpdateOptions::builder().upsert(true).build(), + ) + .await?; + Ok(()) + } + + /// Gets a model type's collection. + pub fn collection(&self) -> Collection { + self.0.collection(M::COLLECTION) + } + + /// Gets a model type's collection. + pub fn doc_collection(&self) -> Collection { + self.0.collection(M::COLLECTION) + } +} + +/// The [`MongoDb`] config. +#[must_use] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub struct MongoDbConfig { + pub(crate) connect_url: String, + pub(crate) username: Option, + pub(crate) password: Option, +} + +impl MongoDbConfig { + /// Creates a new [`MongoDbConfig`]. + pub fn new() -> Self { + Self::default() + } + + /// Sets the connect URL. + pub fn with_connect_url(mut self, connect_url: impl Into) -> Self { + self.connect_url = connect_url.into(); + self + } + + /// Sets the username. + pub fn with_username(mut self, username: impl Into) -> Self { + self.username = Some(username.into()); + self + } + + /// Sets the password. + pub fn with_password(mut self, password: impl Into) -> Self { + self.password = Some(password.into()); + self + } +} + +impl Default for MongoDbConfig { + fn default() -> Self { + Self { + connect_url: CONNECT_URL_DEFAULT.to_string(), + username: None, + password: None, + } + } +}