flowgentra_ai/core/error/
mod.rs1use thiserror::Error;
21
22pub type Result<T> = std::result::Result<T, FlowgentraError>;
24
25#[non_exhaustive]
30#[derive(Error, Debug)]
31pub enum FlowgentraError {
32 #[error("Configuration error: {0}")]
35 ConfigError(String),
36
37 #[error("Graph error: {0}")]
40 GraphError(String),
41
42 #[error("Node not found: {0}")]
44 NodeNotFound(String),
45
46 #[error("Invalid edge: {0}")]
48 InvalidEdge(String),
49
50 #[error("Routing error: {0}")]
52 RoutingError(String),
53
54 #[error("Cycle detected in graph")]
56 CycleDetected,
57
58 #[error("Recursion limit of {limit} steps exceeded. Ensure your graph has a termination condition that routes to END. You can raise the limit via `recursion_limit` in the graph config.")]
60 RecursionLimitExceeded { limit: usize },
61
62 #[error("Graph validation warning: node(s) [{nodes}] have cycles but no path to END. Add a conditional edge to END or the graph will loop forever.")]
64 NoTerminationPath { nodes: String },
65
66 #[error("Node execution error: {0}")]
69 NodeExecutionError(String),
70
71 #[error("Runtime error: {0}")]
73 RuntimeError(String),
74
75 #[error("Invalid state transition: {0}")]
77 InvalidStateTransition(String),
78
79 #[error("Execution error: {0}")]
81 ExecutionError(String),
82
83 #[error("Execution aborted: {0}")]
85 ExecutionAborted(String),
86
87 #[error("Timeout error")]
89 TimeoutError,
90
91 #[error("Execution timeout: {0}")]
93 ExecutionTimeout(String),
94
95 #[error("Parallel execution error: {0}")]
98 ParallelExecutionError(String),
99
100 #[error("State error: {0}")]
103 StateError(String),
104
105 #[error("{0}")]
107 Context(String, #[source] Box<FlowgentraError>),
108
109 #[error("LLM error: {0}")]
112 LLMError(String),
113
114 #[error("MCP error: {0}")]
116 MCPError(String),
117
118 #[error("MCP transport error: {0}")]
121 MCPTransportError(String),
122
123 #[error("MCP server error: {0}")]
126 MCPServerError(String),
127
128 #[error("Tool error: {0}")]
130 ToolError(String),
131
132 #[error("Validation error: {0}")]
134 ValidationError(String),
135
136 #[error("Serialization error: {0}")]
139 SerializationError(#[from] serde_json::Error),
140
141 #[error("YAML parse error: {0}")]
143 YamlError(String),
144
145 #[error("IO error: {0}")]
148 IoError(#[from] std::io::Error),
149}
150
151impl FlowgentraError {
152 pub fn is_retryable(&self) -> bool {
154 matches!(
155 self,
156 FlowgentraError::MCPTransportError(_) | FlowgentraError::TimeoutError
157 )
158 }
159}
160
161impl FlowgentraError {
171 pub fn context(self, msg: &str) -> Self {
183 match self {
184 FlowgentraError::Context(existing_msg, inner) => {
185 FlowgentraError::Context(format!("{}\nContext: {}", existing_msg, msg), inner)
186 }
187 _ => FlowgentraError::Context(msg.to_string(), Box::new(self)),
188 }
189 }
190
191 pub fn is_timeout(&self) -> bool {
193 match self {
194 FlowgentraError::TimeoutError | FlowgentraError::ExecutionTimeout(_) => true,
195 FlowgentraError::Context(_, inner) => inner.is_timeout(),
196 _ => false,
197 }
198 }
199
200 pub fn is_validation_error(&self) -> bool {
202 match self {
203 FlowgentraError::ValidationError(_) => true,
204 FlowgentraError::Context(_, inner) => inner.is_validation_error(),
205 _ => false,
206 }
207 }
208
209 pub fn is_llm_error(&self) -> bool {
211 match self {
212 FlowgentraError::LLMError(_) => true,
213 FlowgentraError::Context(_, inner) => inner.is_llm_error(),
214 _ => false,
215 }
216 }
217
218 pub fn is_state_error(&self) -> bool {
220 match self {
221 FlowgentraError::StateError(_) => true,
222 FlowgentraError::Context(_, inner) => inner.is_state_error(),
223 _ => false,
224 }
225 }
226
227 pub fn hint(&self) -> Option<&'static str> {
229 match self {
230 FlowgentraError::NodeNotFound(_) => {
231 Some("Make sure the handler name in config.yaml matches a #[register_handler] function name")
232 }
233 FlowgentraError::StateError(msg) if msg.contains("not found") => {
234 Some("Check that previous nodes set this state field, or set it in main before agent.run()")
235 }
236 FlowgentraError::LLMError(msg) if msg.contains("401") || msg.contains("Unauthorized") => {
237 Some("Check your API key is valid and set correctly (e.g., $MISTRAL_API_KEY environment variable)")
238 }
239 FlowgentraError::TimeoutError | FlowgentraError::ExecutionTimeout(_) => {
240 Some("Increase the timeout value in config.yaml for this node, or optimize handler performance")
241 }
242 FlowgentraError::ConfigError(_) => {
243 Some("Ensure config.yaml is valid YAML and all required fields are present")
244 }
245 FlowgentraError::Context(_, inner) => inner.hint(),
246 _ => None,
247 }
248 }
249}