pub trait Node<TState, TStore = MemoryStore, TParams = DefaultParams>: Send + Syncwhere
TState: Clone + Debug + Send + Sync + 'static,
TParams: Send + Sync + Clone,
TStore: Send + Sync + 'static,{
type PrepResult: Send + Sync;
type ExecResult: Send + Sync;
// Required methods
fn prep<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
) -> Pin<Box<dyn Future<Output = Result<Self::PrepResult, CanoError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn exec<'life0, 'async_trait>(
&'life0 self,
prep_res: Self::PrepResult,
) -> Pin<Box<dyn Future<Output = Self::ExecResult> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
fn post<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
exec_res: Self::ExecResult,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
// Provided methods
fn set_params(&mut self, _params: TParams) { ... }
fn config(&self) -> TaskConfig { ... }
fn run<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn run_with_retries<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
config: &'life2 TaskConfig,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait { ... }
}
Expand description
Node trait for workflow processing
This trait defines the core interface that all workflow nodes must implement. It provides type flexibility while maintaining performance and type safety.
§Generic Types
TState
: The return type from the post method (typically an enum for workflow control)TParams
: The parameter type for this node (e.g.,HashMap<String, String>
)TStore
: The store backend type (e.g.,MemoryStore
)PrepResult
: The result type from theprep
phase, passed toexec
.ExecResult
: The result type from theexec
phase, passed topost
.
§Node Lifecycle
Each node follows a three-phase execution lifecycle:
prep
: Preparation phase - setup and data loadingexec
: Execution phase - main processing logicpost
: Post-processing phase - cleanup and result handling
The run
method orchestrates these phases automatically.
§Benefits over String-based Approaches
- Type Safety: Return enum values instead of strings
- Performance: No string conversion overhead
- IDE Support: Autocomplete for enum variants
- Compile-Time Safety: Impossible to have invalid state transitions
§Example
use cano::prelude::*;
struct MyNode;
#[async_trait]
impl Node<String> for MyNode {
type PrepResult = String;
type ExecResult = bool;
fn config(&self) -> TaskConfig {
TaskConfig::minimal() // Use minimal retries for fast execution
}
async fn prep(&self, _store: &MemoryStore) -> Result<Self::PrepResult, CanoError> {
Ok("prepared_data".to_string())
}
async fn exec(&self, _prep_res: Self::PrepResult) -> Self::ExecResult {
true // Success
}
async fn post(&self, _store: &MemoryStore, exec_res: Self::ExecResult)
-> Result<String, CanoError> {
if exec_res {
Ok("next".to_string())
} else {
Ok("terminate".to_string())
}
}
}
Required Associated Types§
Sourcetype PrepResult: Send + Sync
type PrepResult: Send + Sync
Result type from the prep phase
Sourcetype ExecResult: Send + Sync
type ExecResult: Send + Sync
Result type from the exec phase
Required Methods§
Sourcefn prep<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
) -> Pin<Box<dyn Future<Output = Result<Self::PrepResult, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn prep<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
) -> Pin<Box<dyn Future<Output = Result<Self::PrepResult, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Preparation phase - load data and setup resources
This is the first phase of node execution. Use it to:
- Load data from store that was left by previous nodes
- Validate inputs and parameters
- Setup resources needed for execution
- Prepare any data structures
The result of this phase is passed to the exec
method.
Sourcefn exec<'life0, 'async_trait>(
&'life0 self,
prep_res: Self::PrepResult,
) -> Pin<Box<dyn Future<Output = Self::ExecResult> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn exec<'life0, 'async_trait>(
&'life0 self,
prep_res: Self::PrepResult,
) -> Pin<Box<dyn Future<Output = Self::ExecResult> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Execution phase - main processing logic
This is the core processing phase where the main business logic runs.
This phase doesn’t have access to store - it only receives the result
from the prep
phase and produces a result for the post
phase.
Benefits of this design:
- Clear separation of concerns
- Easier testing (pure function)
- Better performance (no store access during processing)
- Retry logic can wrap just this phase
Sourcefn post<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
exec_res: Self::ExecResult,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn post<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
exec_res: Self::ExecResult,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Post-processing phase - cleanup and result handling
This is the final phase of node execution. Use it to:
- Store results for the next node to use
- Clean up resources
- Determine the next action/node to run
- Handle errors from the exec phase
This method returns a typed value that determines what happens next in the workflow.
Provided Methods§
Sourcefn set_params(&mut self, _params: TParams)
fn set_params(&mut self, _params: TParams)
Set parameters for the node
Default implementation that does nothing. Override this method if your node needs to store or process parameters when they are set.
Sourcefn config(&self) -> TaskConfig
fn config(&self) -> TaskConfig
Get the node configuration that controls execution behavior
Returns the TaskConfig that determines how this node should be executed.
The default implementation returns TaskConfig::default()
which configures
the node with standard retry logic.
Override this method to customize execution behavior:
- Use
TaskConfig::minimal()
for fast-failing nodes with minimal retries - Use
TaskConfig::new().with_fixed_retry(n, duration)
for custom retry behavior - Return a custom configuration with specific retry/parameter settings
Sourcefn run<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn run<'life0, 'life1, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Run the complete node lifecycle with configuration-driven execution
This method provides a default implementation that runs the three lifecycle phases with execution behavior controlled by the node’s configuration. Nodes execute with minimal overhead for maximum throughput.
You can override this method for completely custom orchestration.
Sourcefn run_with_retries<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
config: &'life2 TaskConfig,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn run_with_retries<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
store: &'life1 TStore,
config: &'life2 TaskConfig,
) -> Pin<Box<dyn Future<Output = Result<TState, CanoError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Internal method to run the node lifecycle with retry logic
This method handles the actual execution of the three phases (prep, exec, post) with retry logic based on the node configuration.