config-injection
Injects application configuration from environment variables or config files using
SKILL.mdskills/config-injection/SKILL.md
GitHubConfiguration Injection
Env var pattern
use injectable::prelude::*;
#[derive(Debug, Clone)]
struct AppConfig {
pub database_url: String,
pub port: u16,
pub api_key: String,
}
#[injectable]
impl AppConfig {
#[injectable(ctor)]
fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
Ok(Self {
database_url: std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "sqlite::memory:".into()),
port: std::env::var("PORT")
.ok().and_then(|p| p.parse().ok()).unwrap_or(3000),
api_key: std::env::var("API_KEY")
.map_err(|_| "API_KEY env var required")?,
})
}
}
Consuming config in other services
#[injectable(factory)]
async fn make_pool(cfg: Inject<AppConfig>) -> Result<sqlx::SqlitePool, sqlx::Error> {
sqlx::SqlitePool::connect(&cfg.database_url).await
}
#[injectable]
struct Database {
#[injectable(inject(use_factory_async = self::make_pool))]
pool: sqlx::SqlitePool,
}
// Or via constructor:
#[injectable]
impl ApiClient {
#[injectable(ctor)]
fn new(cfg: Inject<AppConfig>) -> Self {
Self {
base_url: format!("https://api.example.com"),
api_key: cfg.api_key.clone(),
}
}
}
Config as singleton
AppConfig is singleton by default — all services share the same config instance.
let ctx = container.context();
let cfg: Inject<AppConfig> = ctx.extract().await?;
println!("Port: {}", cfg.port);
Testing with custom config
// Override via environment before building:
std::env::set_var("DATABASE_URL", "sqlite::memory:");
std::env::set_var("API_KEY", "test-key");
let container = Container::builder().build().await.unwrap();