agentic_core/executor/
error.rs1use http::StatusCode;
2use thiserror::Error;
3
4use crate::StorageError;
5use crate::tool::ToolError;
6use crate::utils::common::serialize_to_vec_or_default;
7
8#[non_exhaustive]
9#[derive(Debug, Error)]
10pub enum ExecutorError {
11 #[error("storage error: {0}")]
13 Storage(#[from] StorageError),
14
15 #[error("LLM request failed ({status}): {body}")]
17 LLMRequest { status: StatusCode, body: String },
18
19 #[error("network error: {0}")]
24 NetworkError(
25 #[from]
26 #[source]
27 reqwest::Error,
28 ),
29
30 #[error("json error: {0}")]
35 JsonError(
36 #[from]
37 #[source]
38 serde_json::Error,
39 ),
40
41 #[error("stream error: {0}")]
45 StreamError(String),
46
47 #[error("parse error: {0}")]
51 ParseError(String),
52
53 #[error("{entity} not found: {id}")]
54 NotFound { entity: String, id: String },
55
56 #[error("invalid request: {0}")]
57 InvalidRequest(String),
58
59 #[error("tool error: {0}")]
60 Tool(#[from] ToolError),
61}
62
63impl ExecutorError {
64 #[must_use]
66 pub fn http_status(&self) -> StatusCode {
67 match self {
68 Self::Storage(e) if e.is_not_found() => StatusCode::NOT_FOUND,
69 Self::LLMRequest { status, .. } => *status,
70 Self::Tool(ToolError::Config(_)) | Self::InvalidRequest(_) | Self::JsonError(_) => StatusCode::BAD_REQUEST,
71 Self::Tool(ToolError::Execution(_)) => StatusCode::BAD_GATEWAY,
72 Self::ParseError(_) => StatusCode::UNPROCESSABLE_ENTITY,
73 _ => StatusCode::INTERNAL_SERVER_ERROR,
74 }
75 }
76
77 #[must_use]
79 pub fn error_code(&self) -> &'static str {
80 match self {
81 Self::Storage(e) if e.is_not_found() => "not_found",
82 Self::LLMRequest { .. } => "upstream_error",
83 Self::Tool(ToolError::Config(_)) | Self::InvalidRequest(_) | Self::ParseError(_) | Self::JsonError(_) => {
84 "invalid_request_error"
85 }
86 Self::Tool(ToolError::Execution(_)) => "tool_error",
87 _ => "server_error",
88 }
89 }
90
91 #[must_use]
96 pub fn into_response_body(self) -> Vec<u8> {
97 match self {
98 Self::LLMRequest { body, .. } => body.into_bytes(),
99 other => {
100 let code = other.error_code();
101 serialize_to_vec_or_default(&serde_json::json!({
102 "error": { "message": other.to_string(), "type": code, "code": code }
103 }))
104 }
105 }
106 }
107}
108
109pub type ExecutorResult<T> = Result<T, ExecutorError>;
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_executor_error_display() {
117 let err = ExecutorError::InvalidRequest("test message".into());
118 assert!(err.to_string().contains("invalid request"));
119 assert!(err.to_string().contains("test message"));
120 }
121
122 #[test]
123 fn test_executor_error_stream() {
124 let err = ExecutorError::StreamError("connection lost".into());
125 assert!(err.to_string().contains("stream error"));
126 }
127
128 #[test]
129 fn test_executor_error_not_found() {
130 let err = ExecutorError::NotFound {
131 entity: "Conversation".into(),
132 id: "conv_123".into(),
133 };
134 assert!(err.to_string().contains("Conversation"));
135 assert!(err.to_string().contains("conv_123"));
136 }
137
138 #[test]
139 fn test_executor_error_from_storage() {
140 let storage_err = StorageError::NotConfigured;
141 let exec_err = ExecutorError::from(storage_err);
142 assert!(exec_err.to_string().contains("storage error"));
143 }
144
145 #[test]
146 fn test_executor_error_json_preserves_source() {
147 use std::error::Error;
148 let json_err: serde_json::Error = serde_json::from_str::<serde_json::Value>("{bad}").unwrap_err();
149 let exec_err = ExecutorError::from(json_err);
150 assert!(exec_err.source().is_some(), "source should be chained");
151 assert!(exec_err.to_string().contains("json error"));
152 }
153}