async-nodes 0.1.0

An asynchronous, type-safe node engine implementing the Bulk Synchronous Parallel (BSP) model: control-flow loops, recovery, suspension, and serializable graphs, with dataflow-once pipelines as a profile.
Documentation
use thiserror::Error;

use super::graph::{EdgeId, NodeId, PortIndex};

/// Standardized error types for the BSP graph compiler and execution runtime.
#[derive(Debug, Clone, Error)]
pub enum BspError {
    #[error("Node {node_id} ('{node_name}') failed execution: {message}")]
    NodeExecutionFailed {
        node_id: NodeId,
        node_name: String,
        message: String,
    },

    #[error(
        "Type mismatch on node {node_id}, port {port_index}: Expected {expected}, found {found}"
    )]
    TypeMismatch {
        node_id: NodeId,
        port_index: PortIndex,
        expected: &'static str,
        found: &'static str,
    },

    #[error("Cycle detected in graph topology at or near node {node_id}")]
    CycleDetected { node_id: NodeId },

    #[error("Unconnected required input port {port_index} on node {node_id}")]
    UnconnectedPort {
        node_id: NodeId,
        port_index: PortIndex,
    },

    #[error("Node ID {node_id} was requested but does not exist in the graph registry")]
    NodeNotFound { node_id: NodeId },

    #[error(
        "Graph execution reached an unsolvable deadlocked state due to unresolved data dependencies"
    )]
    Deadlock,

    #[error("Graph compilation or validation failed: {0}")]
    ValidationError(String),

    /// A cycle-closing edge fired more than its configured `max_iterations` budget.
    ///
    /// This is the per-edge circuit breaker for control-flow loops: every edge that closes a
    /// cycle should carry a `max_iterations` cap, and exceeding it aborts the run loudly
    /// instead of looping forever.
    #[error(
        "Loop bound exceeded on edge {edge} ({from} -> {to}): fired more than the configured {max} iteration budget"
    )]
    LoopBoundExceeded {
        edge: EdgeId,
        from: NodeId,
        to: NodeId,
        max: u64,
    },

    /// A single edge accumulated more pending (unconsumed) activation tokens than allowed.
    ///
    /// Guards against unbounded memory growth when a misconfigured loop re-queues tokens
    /// faster than the target node consumes them.
    #[error(
        "Pending token limit exceeded on edge {edge} ({from} -> {to}): more than {cap} unconsumed tokens"
    )]
    TokenLimitExceeded {
        edge: EdgeId,
        from: NodeId,
        to: NodeId,
        cap: u64,
    },

    /// A node was executed more times than its configured `max_executions` budget.
    ///
    /// Useful as a circuit breaker for poll-until style nodes that must not run forever.
    #[error("Node {node_id} exceeded its configured maximum of {max} executions")]
    MaxExecutionsExceeded { node_id: NodeId, max: u64 },

    /// A graph definition (`GraphDef`) could not be parsed or instantiated.
    ///
    /// Unknown node `type_name`s, condition names, and merger names are reported through
    /// [`BspError::ValidationError`] instead (registry completeness is a validation concern);
    /// this variant covers structural definition faults such as an unsupported `version`.
    #[error("Graph definition error: {0}")]
    DefError(String),

    /// Serializing or deserializing a [`super::run::SuspendedExecution`] snapshot failed.
    #[error("Suspension snapshot (de)serialization failed: {0}")]
    SuspensionSerde(String),
}