use async_trait::async_trait;
use cano::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum TaskState {
Start,
ProcessWithNode,
ProcessWithTask,
Complete,
Error,
}
#[derive(Clone)]
struct ProcessingNode {
name: String,
}
impl ProcessingNode {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
#[async_trait]
impl Node<TaskState> for ProcessingNode {
type PrepResult = String;
type ExecResult = String;
fn config(&self) -> TaskConfig {
TaskConfig::minimal() }
async fn prep(&self, _store: &MemoryStore) -> Result<Self::PrepResult, CanoError> {
println!("🔧 Node '{}' - Prep phase: Loading data", self.name);
Ok(format!("data_for_{}", self.name))
}
async fn exec(&self, prep_res: Self::PrepResult) -> Self::ExecResult {
println!(
"⚙️ Node '{}' - Exec phase: Processing {}",
self.name, prep_res
);
format!("processed_{}", prep_res)
}
async fn post(
&self,
store: &MemoryStore,
exec_res: Self::ExecResult,
) -> Result<TaskState, CanoError> {
println!("📝 Node '{}' - Post phase: Storing {}", self.name, exec_res);
store.put("node_result", exec_res)?;
Ok(TaskState::ProcessWithTask)
}
}
#[derive(Clone)]
struct ProcessingTask {
name: String,
}
impl ProcessingTask {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
#[async_trait]
impl Task<TaskState> for ProcessingTask {
async fn run(&self, store: &MemoryStore) -> Result<TaskResult<TaskState>, CanoError> {
println!(
"🚀 Task '{}' - Single run method: doing everything",
self.name
);
let node_result: String = store
.get("node_result")
.map_err(|e| CanoError::node_execution(format!("Failed to load node result: {e}")))?;
println!(" 📥 Loading previous result: {}", node_result);
let processed = format!("task_enhanced_{node_result}");
println!(" ⚙️ Processing: {}", processed);
store.put("final_result", processed.clone())?;
println!(" 📤 Stored final result: {}", processed);
Ok(TaskResult::Single(TaskState::Complete))
}
}
#[derive(Clone)]
struct InitializerTask;
#[async_trait]
impl Task<TaskState> for InitializerTask {
async fn run(&self, store: &MemoryStore) -> Result<TaskResult<TaskState>, CanoError> {
println!("🎯 Initializer Task - Setting up workflow data");
store.put("workflow_id", "demo_123".to_string())?;
store.put("start_time", std::time::SystemTime::now())?;
Ok(TaskResult::Single(TaskState::ProcessWithNode))
}
}
#[tokio::main]
async fn main() -> Result<(), CanoError> {
println!("🚀 Task Interface Demo");
println!("======================");
println!();
println!("This demo shows the difference between Node and Task interfaces:");
println!("• Node: Three-phase lifecycle (prep, exec, post) - more structured");
println!("• Task: Single run method - simpler and more flexible");
println!("• Both can be used in the same workflow!");
println!();
let store = MemoryStore::new();
let workflow = Workflow::new(store.clone())
.register(TaskState::Start, InitializerTask) .register(
TaskState::ProcessWithNode,
ProcessingNode::new("NodeProcessor"),
) .register(
TaskState::ProcessWithTask,
ProcessingTask::new("TaskProcessor"),
) .add_exit_states(vec![TaskState::Complete, TaskState::Error]);
println!("📋 Workflow Overview:");
println!(
" Start → InitializerTask (Task) → ProcessingNode (Node) → ProcessingTask (Task) → Complete"
);
println!();
println!("🏃 Executing workflow...");
println!();
match workflow.orchestrate(TaskState::Start).await {
Ok(final_state) => {
println!();
println!("✅ Workflow completed successfully!");
println!(" Final state: {final_state:?}");
if let Ok(workflow_id) = store.get::<String>("workflow_id") {
println!(" Workflow ID: {workflow_id}");
}
if let Ok(final_result) = store.get::<String>("final_result") {
println!(" Final result: {final_result}");
}
}
Err(e) => {
println!("❌ Workflow failed: {e}");
return Err(e);
}
}
println!();
println!("🎉 Demo completed!");
println!();
println!("Key takeaways:");
println!("• Tasks provide a simpler interface for straightforward operations");
println!("• Nodes provide structured lifecycle management for complex operations");
println!("• Both can be mixed in the same workflow seamlessly");
println!("• Existing Node implementations automatically work as Tasks");
Ok(())
}