Node

Trait Node 

Source
pub trait Node<TState, TStore = MemoryStore, TParams = DefaultParams>: Send + Sync
where 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 the prep phase, passed to exec.
  • ExecResult: The result type from the exec phase, passed to post.

§Node Lifecycle

Each node follows a three-phase execution lifecycle:

  1. prep: Preparation phase - setup and data loading
  2. exec: Execution phase - main processing logic
  3. post: 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§

Source

type PrepResult: Send + Sync

Result type from the prep phase

Source

type ExecResult: Send + Sync

Result type from the exec phase

Required Methods§

Source

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.

Source

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
Source

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§

Source

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.

Source

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
Source

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.

Source

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.

Implementors§