use dependency_injector::{Container, ScopedContainer};
#[derive(Clone)]
struct AppConfig {
name: String,
}
#[derive(Clone)]
struct Database {
url: String,
}
#[derive(Clone)]
struct RequestContext {
request_id: String,
}
#[derive(Clone)]
struct SessionData {
user: String,
}
fn main() {
println!("=== Dependency Injector Scopes Demo ===\n");
let root = Container::new();
root.singleton(AppConfig {
name: "MyApp".into(),
});
root.singleton(Database {
url: "postgres://prod-db:5432/app".into(),
});
let config = root.get::<AppConfig>().expect("AppConfig is registered");
println!("Root container ready: app = {}\n", config.name);
println!("Creating a request scope with root.scope()...");
let request_scope = root.scope();
request_scope.singleton(RequestContext {
request_id: "req-123".into(),
});
let ctx = request_scope
.get::<RequestContext>()
.expect("registered in this scope");
let db = request_scope
.get::<Database>()
.expect("inherited from root");
println!(" {} handled with {}", ctx.request_id, db.url);
assert!(!root.contains::<RequestContext>());
println!(
" root sees RequestContext: {}\n",
root.contains::<RequestContext>()
);
println!("Shadowing Database in a test scope...");
let test_scope = root.scope();
test_scope.singleton(Database {
url: "sqlite::memory:".into(),
});
let test_db = test_scope.get::<Database>().expect("local override");
let root_db = root.get::<Database>().expect("root untouched");
println!(" test scope resolves: {}", test_db.url);
println!(" root still resolves: {}\n", root_db.url);
println!("Removing the override from the test scope...");
assert!(test_scope.remove::<Database>());
let fallback = test_scope.get::<Database>().expect("falls back to root");
println!(" test scope now resolves: {}", fallback.url);
assert!(root.contains::<Database>());
assert!(!test_scope.remove::<Database>());
println!();
println!("Clearing a scratch scope...");
let scratch = root.scope();
scratch.singleton(RequestContext {
request_id: "req-999".into(),
});
scratch.singleton(SessionData {
user: "alice".into(),
});
println!(" scratch scope has {} local services", scratch.len());
scratch.clear();
println!(" after clear: {} local services", scratch.len());
assert!(!scratch.contains::<SessionData>());
assert!(scratch.contains::<AppConfig>());
println!(
" AppConfig still reachable from scratch scope: {}\n",
scratch.contains::<AppConfig>()
);
println!("Creating a ScopedContainer with an explicit Scope id...");
let session = ScopedContainer::from_parent(&root);
session.singleton(SessionData { user: "bob".into() });
let scope_id = session.scope();
println!(
" session scope: {scope_id} (raw id {}, depth {})",
scope_id.id(),
session.depth()
);
let user = session.get::<SessionData>().expect("registered in session");
let cfg = session.get::<AppConfig>().expect("inherited from root");
println!(" session for {} in app {}", user.user, cfg.name);
let nested = ScopedContainer::from_scope(&session);
println!(
" nested scope: {} (depth {})",
nested.scope(),
nested.depth()
);
assert!(nested.contains::<SessionData>());
assert!(!session.contains::<RequestContext>());
println!();
println!("=== Demo Complete ===");
println!("\nScopes provide:");
println!(" - Inheritance: children resolve the full parent chain");
println!(" - Isolation: parents and siblings never see child registrations");
println!(" - Local mutation: remove()/clear() only affect the scope itself");
}