Capability Graphs — Automatic Dependency Resolution

For small applications, passing a short provider list to run_with is enough. For larger apps, CapabilityGraph plans build order from each provider's requires() / provides() metadata and surfaces diagnostics via CapabilityGraph::diagnostics.

Declaring a provider graph

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

let graph = CapabilityGraph::new()
    .add(provide!(ConfigLive).0)
    .add(provide!(DatabaseLive).0)
    .add(provide!(CacheLive).0);
}

Each ProviderSpec declares dependencies via requires(); CapabilityGraph topologically sorts providers before calling build / build_from.

Planning and building

#![allow(unused)]
fn main() {
let order = graph.plan()?;
let env = graph.build()?;
}

The planner returns node indices in dependency order. Independent branches may appear in any stable topological order.

Cycle detection

graph.plan() returns an error if there are circular dependencies:

#![allow(unused)]
fn main() {
let diags = bad_graph.diagnostics();
assert!(!diags.is_empty()); // e.g. cycle-detected
}

Cycles and missing providers are detected at plan time via CapabilityPlannerError::to_diagnostic. Use cargo run -p id_effect_cli --bin id-effect-diagnose -- example cycle to print a sample report.

Conditional providers

Layers can be added conditionally:

#![allow(unused)]
fn main() {
let mut providers = vec![provide!(ConfigLive)];
if cfg!(feature = "metrics") {
    providers.push(provide!(MetricsLive));
}
let env = build_env(providers)?;
}

Feature flags and environment-based configuration compose naturally with the graph API.

When to use graphs vs hand-built Env

SituationPrefer
< 5 capabilities, testsbuild_env([...])
Complex requires() graphsCapabilityGraph
Need diagnostics / cyclesCapabilityGraph::diagnostics
Request-local overridesEnv::scoped
CLI troubleshootingid-effect-diagnose