Composing Providers — CapabilityGraph and run_with

Individual providers build one capability. Applications pass a list to run_with; CapabilityGraph plans build order from each provider's requires() and provides().

Basic composition

#![allow(unused)]
fn main() {
use id_effect::{provide, run_with};

run_with(
    [
        provide!(ConfigLive),
        provide!(DatabaseLive),
        provide!(LoggerLive),
        provide!(UserRepoLive),
    ],
    my_application(),
)?;
}

The array order is irrelevant — the graph topologically sorts providers. Cycles or missing dependencies surface as CapabilityPlannerError with diagnostics from CapabilityGraph::diagnostics.

Building Env without running

#![allow(unused)]
fn main() {
let env = build_env([
    provide!(ConfigLive),
    provide!(DatabaseLive),
    provide!(UserRepoLive),
])?;

run_test(get_user(1), env)?;
}

Or inspect the plan explicitly:

#![allow(unused)]
fn main() {
let mut graph = CapabilityGraph::new();
graph = graph.add(provide!(ConfigLive).0);
graph = graph.add(provide!(DatabaseLive).0);
let order = graph.plan()?;
let env = graph.build()?;
}

Production vs test stacks

#![allow(unused)]
fn main() {
// Production
run_with(
    [provide!(ConfigLive), provide!(DatabaseLive), provide!(UserRepoLive)],
    my_app(),
)?;

// Test — swap implementations, same keys
run_with([provide!(MockUserRepoLive)], get_user(1))?;
}

Application code is unchanged. Only the provider list differs. Needs<K> bounds ensure each effect's requirements are met.

Subset wiring in tests

Tests need not mirror the full production graph — only the capabilities the effect under test requires:

#![allow(unused)]
fn main() {
#[test]
fn test_get_user() {
    let env = build_env([provide!(MockUserRepoLive)]).unwrap();
    let user = run_test(get_user(1), env).unwrap();
    assert_eq!(user.name, "Alice");
}
}

If get_user also needs EffectLogger, the test must include provide!(TestLoggerLive) or insert that key manually — incomplete wiring fails when the effect runs.