Skip to main content

agentcore/
error.rs

1use std::fmt;
2
3pub type Result<T> = std::result::Result<T, AgenticError>;
4
5#[derive(Debug)]
6pub enum AgenticError {
7    Api {
8        message: String,
9        status: Option<u16>,
10        retryable: bool,
11    },
12    Tool {
13        tool_name: String,
14        message: String,
15    },
16    Io(std::io::Error),
17    Json(serde_json::Error),
18    Aborted,
19    MaxTurnsExceeded(u32),
20    BudgetExceeded {
21        spent: f64,
22        limit: f64,
23    },
24    ContextOverflow {
25        token_count: u64,
26        limit: u64,
27    },
28    SchemaValidation {
29        path: String,
30        message: String,
31    },
32    SchemaRetryExhausted {
33        retries: u32,
34    },
35    Other(String),
36}
37
38impl fmt::Display for AgenticError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            AgenticError::Api {
42                message,
43                status,
44                retryable,
45            } => match status {
46                Some(code) => write!(
47                    f,
48                    "API error (status {code}): {message} (retryable: {retryable})"
49                ),
50                None => write!(f, "API error: {message} (retryable: {retryable})"),
51            },
52            AgenticError::Tool { tool_name, message } => {
53                write!(f, "Tool error ({tool_name}): {message}")
54            }
55            AgenticError::Io(err) => write!(f, "IO error: {err}"),
56            AgenticError::Json(err) => write!(f, "JSON error: {err}"),
57            AgenticError::Aborted => write!(f, "Operation aborted"),
58            AgenticError::MaxTurnsExceeded(n) => write!(f, "Maximum turns exceeded: {n}"),
59            AgenticError::BudgetExceeded { spent, limit } => {
60                write!(f, "Budget exceeded: spent ${spent:.4}, limit ${limit:.4}")
61            }
62            AgenticError::ContextOverflow { token_count, limit } => {
63                write!(
64                    f,
65                    "Context overflow: {token_count} tokens exceeds limit of {limit}"
66                )
67            }
68            AgenticError::SchemaValidation { path, message } => {
69                write!(f, "Schema validation error at {path}: {message}")
70            }
71            AgenticError::SchemaRetryExhausted { retries } => {
72                write!(f, "Schema retry exhausted after {retries} attempts")
73            }
74            AgenticError::Other(msg) => write!(f, "{msg}"),
75        }
76    }
77}
78
79impl std::error::Error for AgenticError {
80    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
81        match self {
82            AgenticError::Io(err) => Some(err),
83            AgenticError::Json(err) => Some(err),
84            _ => None,
85        }
86    }
87}
88
89impl From<std::io::Error> for AgenticError {
90    fn from(err: std::io::Error) -> Self {
91        AgenticError::Io(err)
92    }
93}
94
95impl From<serde_json::Error> for AgenticError {
96    fn from(err: serde_json::Error) -> Self {
97        AgenticError::Json(err)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn display_api_error() {
107        let err = AgenticError::Api {
108            message: "rate limited".into(),
109            status: Some(429),
110            retryable: true,
111        };
112        let display = format!("{err}");
113        assert!(display.contains("429"));
114        assert!(display.contains("rate limited"));
115    }
116
117    #[test]
118    fn from_io_error() {
119        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
120        let err: AgenticError = io_err.into();
121        assert!(matches!(err, AgenticError::Io(_)));
122        assert!(format!("{err}").contains("file not found"));
123    }
124
125    #[test]
126    fn from_json_error() {
127        let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
128        let err: AgenticError = json_err.into();
129        assert!(matches!(err, AgenticError::Json(_)));
130    }
131
132    #[test]
133    fn budget_exceeded_shows_amounts() {
134        let err = AgenticError::BudgetExceeded {
135            spent: 5.1234,
136            limit: 3.0,
137        };
138        let display = format!("{err}");
139        assert!(display.contains("5.1234"));
140        assert!(display.contains("3.0000"));
141    }
142
143    #[test]
144    fn all_variants_display_non_empty() {
145        let variants: Vec<AgenticError> = vec![
146            AgenticError::Api {
147                message: "msg".into(),
148                status: Some(500),
149                retryable: false,
150            },
151            AgenticError::Tool {
152                tool_name: "tool".into(),
153                message: "err".into(),
154            },
155            AgenticError::Io(std::io::Error::new(std::io::ErrorKind::Other, "io")),
156            AgenticError::Json(serde_json::from_str::<()>("bad").unwrap_err()),
157            AgenticError::Aborted,
158            AgenticError::MaxTurnsExceeded(10),
159            AgenticError::BudgetExceeded {
160                spent: 1.0,
161                limit: 0.5,
162            },
163            AgenticError::ContextOverflow {
164                token_count: 200_000,
165                limit: 100_000,
166            },
167            AgenticError::SchemaValidation {
168                path: "/a".into(),
169                message: "bad".into(),
170            },
171            AgenticError::SchemaRetryExhausted { retries: 3 },
172            AgenticError::Other("other".into()),
173        ];
174        for variant in &variants {
175            let display = format!("{variant}");
176            assert!(!display.is_empty(), "Empty display for: {variant:?}");
177        }
178    }
179}