how to add db_connection to ServiceCollection by specifying database_url? so that the database_url is not empty when get_pool is called #17
Replies: 5 comments 3 replies
-
|
Great question! At some point, many injectable components will use a set of primitives. While injecting primitives is possible, they become difficult to manage since they cannot use macros and are not unique. There are several solutions, but here are a few. Option 1The first thing you likely want is a container for the options (e.g. settings) for the connection. pub struct DbConnectionOptions {
pub database_url: String,
}You can imagine that this struct might also have additional members such as use di::*;
#[injectable(DbContext)]
pub struct DbConnection {
options: Ref<DbConnectionOptions>,
}
pub trait DbContext {
fn get_pool(&self) -> DbPool;
}
impl DbContext for DbConnection {
fn get_pool(&self) -> DbPool {
let manager = ConnectionManager::<PgConnection>::new(&self.options.database_url);
r2d2::Pool::new(manager).unwrap()
}
}Setting it up is pretty straight forward, but use di::*;
let provider = ServiceCollection::new()
.add(DbConnection::transient())
.add(transient_as_self::<DbConnectionOptions>()
.from(|_| Ref::new(DbConnectionOptions {
database_url: String::from("<str>")
}))
.build_provider()
.unwrap();Option 2The previous setup is a common scenario, which is the primary purpose of the more-options sister crate. The definition using Options looks very similar: use di::*;
use options::Options;
#[injectable(DbContext)]
pub struct DbConnection {
options: Ref<dyn Options<DbConnectionOptions>>,
}
pub trait DbContext {
fn get_pool(&self) -> DbPool;
}
impl DbContext for DbConnection {
fn get_pool(&self) -> DbPool {
r2d2::Pool::new(
ConnectionManager::<PgConnection>::new(
&self.options.value().database_url)
).unwrap()
}
}The more interesting part is how things are configured. Options can be configured as part of DI. You can even add validation. I haven't figured out how to avoid a use di::*;
use options::{*, ext::*};
let provider = ServiceCollection::new()
.add_options::<DbConnectionOptions>()
.configure(|options| options.database_url = "<url>")
.validate(|options| !options.database_url.is_empty(), "Database URL is unset")
.add(DbConnection::transient())
.build_provider()
.unwrap();That's all fine and well, but it's often the case that you'll configure something like a database connection from configuration. The more-config sister crate can be combined with Let's say you have the configuration file (but it could be any source) {
"data": {
"database_url": "<str>"
}
}We need a minor refactoring of our options so they can be deserialized via use serde::Deserialize;
#[default(Deserialize)]
pub struct DbConnectionOptions {
pub database_url: String,
}We can combine all of these together as follows: use config::{*, ext::*};
use di::*;
use options::{*, ext::*};
let config = DefaultConfigurationBuilder::new()
.add_json_file("appsettings.json")
.build()
.unwrap()
.as_config();
let provider = ServiceCollection::new()
.add(DbConnection::transient())
.apply_config_at::<DbConnectionOptions>(config, "data")
.validate(|options| !options.database_url.is_empty(), "Database URL is unset")
.build_provider()
.unwrap();This setup will load a configuration from a single JSON file (multiple sources are supported) and then register Naturally, you are not required to do that, but all of these capabilities were meant to be complementary. |
Beta Was this translation helpful? Give feedback.
-
|
cargo install more-config --all-features how to fix ? and if I add with cargo add, it doesn't work with di container. it doesn't find the function apply_config_at let provider = ServiceCollection::new()
.add(DbConnection::transient())
.apply_config_at::<DbConnectionOptions>(config, "data")
.validate(|options| !options.database_url.is_empty(), "Database URL is unset")
.build_provider()
.unwrap(); |
Beta Was this translation helpful? Give feedback.
-
|
main.rs let configuration = DefaultConfigurationBuilder::new()
.add_json_file("appsettings.json".is().optional())
.build()
.unwrap()
.as_config();
println!("database_url = {}", configuration.get("database_url").unwrap().as_str());
let builder = ServiceBuilder::new();
let mut provider = ServiceCollection::new()
.add(DbConnection::transient())
.apply_config::<DbConnectionOptions>(configuration, "database_url")
.build_provider()
.unwrap();cargo.toml more-options = "3.0.0"
more-config = { version = "2.0.0", features = ["serde_json", "json", "serde", "mem"] }
more-di = "3.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" |
Beta Was this translation helpful? Give feedback.
-
|
ok, now error is here let config : Rc<dyn Configuration> =Rc::from(
DefaultConfigurationBuilder::new()
.add_json_file("appsettings.json")
.build()
.unwrap()
.as_config());
let builder = ServiceBuilder::new();
let provider = builder
.add_adapters()
.add_infrastructure()
.add_application()
.build()
.apply_config_at::<DbConnectionOptions>(config, "data")
.build_provider().unwrap();
let service = provider.get_required::<UserService>();
service.save(CreateUserRequest::new("stas", "test"));
Ok(())thread 'main' panicked at /home/administrator/.cargo/registry/src/index.crates.io-6f17d22bba15001f/more-options-3.0.0/src/builder.rs:580:36: |
Beta Was this translation helpful? Give feedback.
-
|
Let's say you have the following {
"data": {
"databaseUrl": "https://my.db.com/data"
}
}This is proper Camel-casing for JSON. You'd likely have your options struct like: #[derive(Default, Deserialize)]
pub struct DbOptions {
#[serde(alias = "DatabaseUrl")]
pub database_url: String,
}
Unless you copy the fn main() {
let file = std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.join("../../appsettings.json");
let config = DefaultConfigurationBuilder::new()
.add_json_file(file)
.build()
.unwrap();
let provider = ServiceCollection::new()
.apply_config::<DbOptions>(config.section("Data").as_config().into())
.validate(
|options| !options.database_url.is_empty(),
"The database URL must be set.",
)
.add(DbConnection::transient())
.build_provider()
.unwrap();
let db = provider.get_required::<DbConnection>();
println!("Connecting to '{}'...", &db.database_url());
}The following is a working E2E example: You should be able to extract this file and simply run: If you remove I hope that helps and unblocks you. Let me know if you have more questions or issues. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Beta Was this translation helpful? Give feedback.
All reactions