1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! # Bare Workflow Example
//!
//! Demonstrates three ergonomic features for resource-free tasks:
//!
//! - **`Task::run_bare()`** — override instead of `run()` when the task needs no resources.
//! The default `run()` delegates to `run_bare()`, so bare tasks work in any workflow.
//! - **`Workflow::bare()`** — construct a workflow with no resources.
//! - **`Resources::empty()`** — named constructor alternative to `Workflow::bare()`.
//!
//! Use `run_bare()` for pure computation; use `run()` when the task accesses the store
//! or any other resource. Bare tasks can be mixed freely with resource tasks.
//!
//! Run with:
//! ```bash
//! cargo run --example workflow_bare
//! ```
use cano::prelude::*;
/// Workflow states for a simple data validation pipeline.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Stage {
Validate, // pure logic — no resources needed
Sanitize, // pure logic — no resources needed
Persist, // writes to a store — needs resources
Done,
}
// Bare tasks override `run_bare()` — no resources needed, no unused `_res` parameter.
/// Validates that a hardcoded input passes business rules (pure computation).
struct ValidateTask;
#[task(state = Stage)]
impl ValidateTask {
async fn run_bare(&self) -> CanoResult<TaskResult<Stage>> {
println!("ValidateTask: checking input constraints...");
// Pure logic — no resources accessed.
let value: i32 = 42;
if value < 0 {
return Err(CanoError::task_execution("value must be non-negative"));
}
println!(" value={value} -- OK");
Ok(TaskResult::Single(Stage::Sanitize))
}
}
/// Clamps the validated value to [0, 100] (pure computation).
struct SanitizeTask;
#[task(state = Stage)]
impl SanitizeTask {
async fn run_bare(&self) -> CanoResult<TaskResult<Stage>> {
println!("SanitizeTask: clamping value to [0, 100]...");
let raw: i32 = 42;
let clamped = raw.clamp(0, 100);
println!(" raw={raw} -> clamped={clamped}");
Ok(TaskResult::Single(Stage::Persist))
}
}
// Resource tasks override `run()` to access the store or other resources.
/// Persists the sanitized value to the shared store.
struct PersistTask;
#[task(state = Stage)]
impl PersistTask {
async fn run(&self, res: &Resources) -> CanoResult<TaskResult<Stage>> {
let store = res.get::<MemoryStore, _>("store")?;
println!("PersistTask: writing result to store...");
let result: i32 = 42_i32.clamp(0, 100);
store.put("sanitized_value", result)?;
println!(" stored sanitized_value={result}");
Ok(TaskResult::Single(Stage::Done))
}
}
#[tokio::main]
async fn main() -> CanoResult<()> {
println!("=== Bare workflow (no resources) ===\n");
// Workflow::bare() is equivalent to Workflow::new(Resources::empty()) —
// choose whichever reads more clearly at the call site.
let workflow = Workflow::bare()
.register(Stage::Validate, ValidateTask)
.register(Stage::Sanitize, SanitizeTask)
.add_exit_states(vec![Stage::Persist, Stage::Done]);
match workflow.orchestrate(Stage::Validate).await {
Ok(final_state) => println!("\nBare workflow reached: {final_state:?}\n"),
Err(e) => {
eprintln!("Workflow failed: {e}");
return Err(e);
}
}
println!("=== Mixed workflow (bare tasks + resource task) ===\n");
// Bare tasks work in any workflow — the default run() delegates to run_bare().
let store = MemoryStore::new();
let workflow = Workflow::new(Resources::new().insert("store", store.clone()))
.register(Stage::Validate, ValidateTask) // bare task
.register(Stage::Sanitize, SanitizeTask) // bare task
.register(Stage::Persist, PersistTask) // resource task
.add_exit_states(vec![Stage::Done]);
match workflow.orchestrate(Stage::Validate).await {
Ok(final_state) => {
println!("\nMixed workflow reached: {final_state:?}");
if let Ok(v) = store.get::<i32>("sanitized_value") {
println!("Persisted value: {v}");
}
}
Err(e) => {
eprintln!("Workflow failed: {e}");
return Err(e);
}
}
Ok(())
}