1use std::time::Duration;
4
5use crate::interrupt::Interrupt;
6use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, GraphError>;
10
11#[derive(Error, Debug)]
13pub enum GraphError {
14 #[error("Invalid graph structure: {0}")]
16 InvalidGraph(String),
17
18 #[error("Node not found: {0}")]
20 NodeNotFound(String),
21
22 #[error("Edge target not found: {0}")]
24 EdgeTargetNotFound(String),
25
26 #[error("No entry point defined (missing edge from START)")]
28 NoEntryPoint,
29
30 #[error("Recursion limit exceeded: {0} steps")]
32 RecursionLimitExceeded(usize),
33
34 #[error("Execution interrupted: {0:?}")]
36 Interrupted(Box<InterruptedExecution>),
37
38 #[error("Node '{node}' execution failed: {message}")]
40 NodeExecutionFailed { node: String, message: String },
41
42 #[error("Node '{node}' timed out after {elapsed:?}")]
44 NodeTimedOut { node: String, elapsed: Duration },
45
46 #[error("Fan-in node '{node}' timed out: received {received}/{expected} upstream outputs")]
48 FanInTimedOut { node: String, received: usize, expected: usize },
49
50 #[error("State serialization error: {0}")]
52 SerializationError(String),
53
54 #[error("Checkpoint error: {0}")]
56 CheckpointError(String),
57
58 #[error(
64 "node '{node}' wrote undeclared channel '{channel}'. Declare it on the graph, or drop the write"
65 )]
66 UndeclaredChannel {
67 node: String,
69 channel: String,
71 },
72
73 #[error("subgraph '{subgraph}' maps channel '{channel}', which the {side} does not declare")]
75 SubgraphChannelMismatch {
76 subgraph: String,
78 channel: String,
80 side: String,
82 },
83
84 #[error("Router returned unknown target: {0}")]
86 UnknownRouteTarget(String),
87
88 #[error("IO error: {0}")]
90 IoError(#[from] std::io::Error),
91
92 #[error("JSON error: {0}")]
94 JsonError(#[from] serde_json::Error),
95
96 #[cfg(feature = "sqlite")]
98 #[error("Database error: {0}")]
99 DatabaseError(#[from] sqlx::Error),
100
101 #[error("{0}")]
103 Other(String),
104}
105
106#[derive(Debug, Clone)]
108pub struct InterruptedExecution {
109 pub thread_id: String,
111 pub checkpoint_id: String,
113 pub interrupt: Interrupt,
115 pub state: crate::state::State,
117 pub step: usize,
119}
120
121impl InterruptedExecution {
122 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}