Building Providers — From Simple to Complex

Leaf providers — no dependencies

#![allow(unused)]
fn main() {
struct ConfigLive;

impl ProviderSpec for ConfigLive {
    type Key = Config;
    type Output = Config;

    fn provider_id() -> &'static str { "config-from-env" }

    fn provide(_deps: &Env) -> Result<Config, ProviderError> {
        Config::from_env().map_err(|e| ProviderError {
            provider: Self::provider_id(),
            message: e.to_string(),
        })
    }
}
}

Dependent providers

Read already-built capabilities from deps:

#![allow(unused)]
fn main() {
struct UserRepoLive;

impl ProviderSpec for UserRepoLive {
    type Key = UserRepo;
    type Output = Arc<dyn UserRepository>;

    fn provider_id() -> &'static str { "user-repo-postgres" }

    fn provide(deps: &Env) -> Result<Arc<dyn UserRepository>, ProviderError> {
        let pool = deps.get::<Cap<Database>>().clone();
        Ok(Arc::new(PostgresUserRepository { pool }))
    }
}
}

Override requires() to return [Database::id(), …] so the graph builds the database before the repository.

Test doubles

Same key, different provider type:

#![allow(unused)]
fn main() {
struct MockUserRepoLive;

impl ProviderSpec for MockUserRepoLive {
    type Key = UserRepo;
    type Output = Arc<dyn UserRepository>;

    fn provider_id() -> '&'static str { "user-repo-mock" }

    fn provide(_deps: &Env) -> Result<Arc<dyn UserRepository>, ProviderError> {
        Ok(Arc::new(MockUserRepository::default_fixture()))
    }
}
}

When a test needs custom data, use Env::insert instead of provide!. Business logic still uses Needs<UserRepo> — only the wiring at the edge changes.

Custom values

Workspace crates expose helpers that return ProviderBox directly — e.g. id_effect_config::provide_config_provider, id_effect_platform::http::reqwest::provide_reqwest_client. Use these when you already hold a concrete handle and don't need a zero-sized ProviderSpec type.

The pattern in practice

ConfigLive          (no deps)
  → DatabaseLive    (needs Config)
  → CacheLive       (needs Config)
  → UserRepoLive    (needs Database)

The next section shows how CapabilityGraph wires the list together.