use cano::prelude::*;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
enum Key {
Store,
Cache,
Config,
}
#[derive(Resource)]
struct AppConfig {
max_connections: u32,
}
#[derive(Resource)]
struct Cache {
label: String,
}
#[derive(Resource)]
struct Counter {
#[allow(dead_code)]
value: u32,
}
struct TrackedResource {
name: String,
log: Arc<Mutex<Vec<String>>>,
fail_on_setup: bool,
}
impl TrackedResource {
fn new(name: &str, log: Arc<Mutex<Vec<String>>>, fail_on_setup: bool) -> Self {
Self {
name: name.to_string(),
log,
fail_on_setup,
}
}
}
#[resource]
impl Resource for TrackedResource {
async fn setup(&self) -> Result<(), CanoError> {
self.log
.lock()
.unwrap()
.push(format!("setup:{}", self.name));
if self.fail_on_setup {
return Err(CanoError::configuration(format!(
"TrackedResource '{}' failed during setup",
self.name
)));
}
println!(" setup: {}", self.name);
Ok(())
}
async fn teardown(&self) -> Result<(), CanoError> {
self.log
.lock()
.unwrap()
.push(format!("teardown:{}", self.name));
println!(" teardown (rollback): {}", self.name);
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Resources Advanced Demo ===\n");
println!("-- (a) Enum resource keys --");
{
let resources = Resources::<Key>::new()
.insert(Key::Store, MemoryStore::new())
.insert(Key::Cache, Cache { label: "L1".into() })
.insert(
Key::Config,
AppConfig {
max_connections: 32,
},
);
let store = resources.get::<MemoryStore, _>(&Key::Store)?;
let cache = resources.get::<Cache, _>(&Key::Cache)?;
let config = resources.get::<AppConfig, _>(&Key::Config)?;
store.put("ping", "pong".to_string())?;
let pong: String = store.get("ping")?;
println!(" Key::Store -> MemoryStore, ping='{pong}'");
println!(" Key::Cache -> Cache(label='{}')", cache.label);
println!(
" Key::Config -> AppConfig(max_connections={})\n",
config.max_connections
);
}
println!("-- (b) try_insert duplicate detection --");
{
let resources: Resources = Resources::new().try_insert("counter", Counter { value: 1 })?;
println!(" try_insert 'counter' (first) -> Ok");
match resources.try_insert("counter", Counter { value: 2 }) {
Ok(_) => println!(" try_insert 'counter' (second) -> Ok (unexpected!)"),
Err(CanoError::ResourceDuplicateKey(msg)) => {
println!(" try_insert 'counter' (second) -> Err(ResourceDuplicateKey): {msg}");
}
Err(other) => {
println!(" try_insert 'counter' (second) -> Err (unexpected variant): {other}")
}
}
println!();
}
println!("-- (c) setup_all partial LIFO rollback --");
{
let log: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let resources: Resources = Resources::new()
.insert(
"alpha",
TrackedResource::new("alpha", Arc::clone(&log), false),
)
.insert(
"beta",
TrackedResource::new("beta", Arc::clone(&log), false),
)
.insert(
"gamma",
TrackedResource::new("gamma", Arc::clone(&log), true),
);
println!(" calling setup_all() ...");
match resources.setup_all().await {
Ok(()) => println!(" setup_all -> Ok (unexpected!)"),
Err(e) => println!(" setup_all -> Err: {e}"),
}
let events = log.lock().unwrap().clone();
println!("\n lifecycle event log (in order):");
for (i, ev) in events.iter().enumerate() {
println!(" [{i}] {ev}");
}
assert_eq!(
events,
vec![
"setup:alpha",
"setup:beta",
"setup:gamma", "teardown:beta", "teardown:alpha",
],
"LIFO rollback order is wrong: {events:?}"
);
println!("\n LIFO rollback confirmed:");
println!(" - 'gamma' failed during setup -> no teardown for gamma");
println!(" - 'beta' torn down first (LIFO: last ok)");
println!(" - 'alpha' torn down second (LIFO: first inserted)");
}
println!("\n=== Done ===");
Ok(())
}