Providing Services — ProviderSpec Implementations
You have a trait and an implementation. A provider wires the impl into Env.
Production provider
#![allow(unused)] fn main() { use id_effect::{Env, ProviderSpecDerive, caps, effect, provide, run_with}; use std::sync::Arc; struct PostgresUserRepository { pool: Pool } impl UserRepository for PostgresUserRepository { fn get_user(&self, id: u64) -> Effect<User, DbError, ()> { effect! { /* query via self.pool */ } } } #[derive(ProviderSpecDerive)] #[provides(UserRepo)] struct UserRepoLive; impl UserRepoLive { fn new(deps: &Env) -> Arc<dyn UserRepository> { let pool = deps.get::<Cap<Database>>().clone(); Arc::new(PostgresUserRepository { pool }) } } }
Register alongside dependencies:
#![allow(unused)] fn main() { run_with( [provide!(ConfigLive), provide!(DatabaseLive), provide!(UserRepoLive)], get_user_profile(42), )?; }
Mock provider
provide! takes a type (provide!(MockUserRepoLive)), not a value. For custom fixture data, insert into Env directly:
#![allow(unused)] fn main() { let mut env = Env::new(); env.insert::<Cap<UserRepo>>(Arc::new(MockUserRepository { users: test_data() })); run_blocking(get_user(1), env)?; }
For a fixed zero-config mock, use a unit struct:
#![allow(unused)] fn main() { #[derive(ProviderSpecDerive)] #[provides(UserRepo)] struct MockUserRepoLive; impl MockUserRepoLive { fn new() -> Arc<dyn UserRepository> { Arc::new(MockUserRepository::default_fixture()) } } }
Same UserRepo, no real database — swap at the edge:
#![allow(unused)] fn main() { // Production run_with([provide!(DatabaseLive), provide!(UserRepoLive)], app())?; // Test (fixed fixture) run_with([provide!(MockUserRepoLive)], get_user(1))?; }
Application code using caps!(UserRepo) and ~UserRepo stays identical.