Skip to main content

adk_graph/
error.rs

1//! Error types for adk-graph
2
3use std::time::Duration;
4
5use crate::interrupt::Interrupt;
6use thiserror::Error;
7
8/// Result type for graph operations
9pub type Result<T> = std::result::Result<T, GraphError>;
10
11/// Errors that can occur during graph operations
12#[derive(Error, Debug)]
13pub enum GraphError {
14    /// Graph structure is invalid
15    #[error("Invalid graph structure: {0}")]
16    InvalidGraph(String),
17
18    /// Node not found
19    #[error("Node not found: {0}")]
20    NodeNotFound(String),
21
22    /// Edge target not found
23    #[error("Edge target not found: {0}")]
24    EdgeTargetNotFound(String),
25
26    /// No entry point defined
27    #[error("No entry point defined (missing edge from START)")]
28    NoEntryPoint,
29
30    /// Recursion limit exceeded
31    #[error("Recursion limit exceeded: {0} steps")]
32    RecursionLimitExceeded(usize),
33
34    /// Execution was interrupted
35    #[error("Execution interrupted: {0:?}")]
36    Interrupted(Box<InterruptedExecution>),
37
38    /// Node execution failed
39    #[error("Node '{node}' execution failed: {message}")]
40    NodeExecutionFailed { node: String, message: String },
41
42    /// Node timed out
43    #[error("Node '{node}' timed out after {elapsed:?}")]
44    NodeTimedOut { node: String, elapsed: Duration },
45
46    /// Fan-in node timed out waiting for upstream paths
47    #[error("Fan-in node '{node}' timed out: received {received}/{expected} upstream outputs")]
48    FanInTimedOut { node: String, received: usize, expected: usize },
49
50    /// State serialization error
51    #[error("State serialization error: {0}")]
52    SerializationError(String),
53
54    /// Checkpoint error
55    #[error("Checkpoint error: {0}")]
56    CheckpointError(String),
57
58    /// A node wrote a channel the state schema does not declare.
59    ///
60    /// Only raised when channel enforcement is on. An undeclared channel
61    /// otherwise takes the overwrite reducer, which silently discards the
62    /// appends a list channel was meant to collect.
63    #[error(
64        "node '{node}' wrote undeclared channel '{channel}'. Declare it on the graph, or drop the write"
65    )]
66    UndeclaredChannel {
67        /// The node that produced the update.
68        node: String,
69        /// The channel name that is not declared.
70        channel: String,
71    },
72
73    /// A subgraph mapping names a channel the relevant side does not declare.
74    #[error("subgraph '{subgraph}' maps channel '{channel}', which the {side} does not declare")]
75    SubgraphChannelMismatch {
76        /// The subgraph node's name.
77        subgraph: String,
78        /// The channel that is not declared.
79        channel: String,
80        /// Which side is missing it, the parent or the subgraph.
81        side: String,
82    },
83
84    /// Router returned unknown target
85    #[error("Router returned unknown target: {0}")]
86    UnknownRouteTarget(String),
87
88    /// IO error
89    #[error("IO error: {0}")]
90    IoError(#[from] std::io::Error),
91
92    /// JSON error
93    #[error("JSON error: {0}")]
94    JsonError(#[from] serde_json::Error),
95
96    /// Database error (when sqlite feature enabled)
97    #[cfg(feature = "sqlite")]
98    #[error("Database error: {0}")]
99    DatabaseError(#[from] sqlx::Error),
100
101    /// Other error (used by extensions like the functional API)
102    #[error("{0}")]
103    Other(String),
104}
105
106/// Information about an interrupted execution
107#[derive(Debug, Clone)]
108pub struct InterruptedExecution {
109    /// Thread ID for resumption
110    pub thread_id: String,
111    /// Checkpoint ID for resumption
112    pub checkpoint_id: String,
113    /// The interrupt that occurred
114    pub interrupt: Interrupt,
115    /// Current state at interruption
116    pub state: crate::state::State,
117    /// Step number when interrupted
118    pub step: usize,
119}
120
121impl InterruptedExecution {
122    /// Create a new interrupted execution
123    pub fn new(
124        thread_id: String,
125        checkpoint_id: String,
126        interrupt: Interrupt,
127        state: crate::state::State,
128        step: usize,
129    ) -> Self {
130        Self { thread_id, checkpoint_id, interrupt, state, step }
131    }
132}
133
134impl From<GraphError> for adk_core::AdkError {
135    fn from(err: GraphError) -> Self {
136        use adk_core::{ErrorCategory, ErrorComponent};
137        let (category, code) = match &err {
138            GraphError::InvalidGraph(_) => (ErrorCategory::InvalidInput, "graph.invalid"),
139            GraphError::NodeNotFound(_) => (ErrorCategory::NotFound, "graph.node_not_found"),
140            GraphError::EdgeTargetNotFound(_) => {
141                (ErrorCategory::NotFound, "graph.edge_target_not_found")
142            }
143            GraphError::NoEntryPoint => (ErrorCategory::InvalidInput, "graph.no_entry_point"),
144            GraphError::RecursionLimitExceeded(_) => {
145                (ErrorCategory::Internal, "graph.recursion_limit")
146            }
147            GraphError::Interrupted(_) => (ErrorCategory::Cancelled, "graph.interrupted"),
148            GraphError::NodeExecutionFailed { .. } => {
149                (ErrorCategory::Internal, "graph.node_execution_failed")
150            }
151            GraphError::NodeTimedOut { .. } => (ErrorCategory::Timeout, "graph.node_timed_out"),
152            GraphError::FanInTimedOut { .. } => (ErrorCategory::Timeout, "graph.fan_in_timed_out"),
153            GraphError::SerializationError(_) => (ErrorCategory::Internal, "graph.serialization"),
154            GraphError::CheckpointError(_) => (ErrorCategory::Internal, "graph.checkpoint"),
155            GraphError::SubgraphChannelMismatch { .. } => {
156                (ErrorCategory::InvalidInput, "graph.subgraph_channel_mismatch")
157            }
158            GraphError::UndeclaredChannel { .. } => {
159                (ErrorCategory::InvalidInput, "graph.undeclared_channel")
160            }
161            GraphError::UnknownRouteTarget(_) => {
162                (ErrorCategory::NotFound, "graph.unknown_route_target")
163            }
164            GraphError::IoError(_) => (ErrorCategory::Internal, "graph.io"),
165            GraphError::JsonError(_) => (ErrorCategory::Internal, "graph.json"),
166            #[cfg(feature = "sqlite")]
167            GraphError::DatabaseError(_) => (ErrorCategory::Internal, "graph.database"),
168            GraphError::Other(_) => (ErrorCategory::Internal, "graph.other"),
169        };
170        adk_core::AdkError::new(ErrorComponent::Graph, category, code, err.to_string())
171            .with_source(err)
172    }
173}