use std::fmt;
pub type Result<T> = std::result::Result<T, AgenticError>;
#[derive(Debug)]
pub enum AgenticError {
Api {
message: String,
status: Option<u16>,
retryable: bool,
retry_after_ms: Option<u64>,
},
Tool {
tool_name: String,
message: String,
},
Io(std::io::Error),
Json(serde_json::Error),
Aborted,
MaxTurnsExceeded(u32),
ContextOverflow {
token_count: u64,
limit: u64,
},
SchemaValidation {
path: String,
message: String,
},
SchemaRetryExhausted {
retries: u32,
},
Other(String),
}
impl AgenticError {
pub fn is_retryable(&self) -> bool {
matches!(self, AgenticError::Api { retryable: true, .. })
}
pub fn retry_after_ms(&self) -> Option<u64> {
match self {
AgenticError::Api { retry_after_ms, .. } => *retry_after_ms,
_ => None,
}
}
}
impl fmt::Display for AgenticError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AgenticError::Api {
message,
status,
retryable,
..
} => match status {
Some(code) => write!(
f,
"API error (status {code}): {message} (retryable: {retryable})"
),
None => write!(f, "API error: {message} (retryable: {retryable})"),
},
AgenticError::Tool { tool_name, message } => {
write!(f, "Tool error ({tool_name}): {message}")
}
AgenticError::Io(err) => write!(f, "IO error: {err}"),
AgenticError::Json(err) => write!(f, "JSON error: {err}"),
AgenticError::Aborted => write!(f, "Operation aborted"),
AgenticError::MaxTurnsExceeded(n) => write!(f, "Maximum turns exceeded: {n}"),
AgenticError::ContextOverflow { token_count, limit } => {
write!(
f,
"Context overflow: {token_count} tokens exceeds limit of {limit}"
)
}
AgenticError::SchemaValidation { path, message } => {
write!(f, "Schema validation error at {path}: {message}")
}
AgenticError::SchemaRetryExhausted { retries } => {
write!(f, "Schema retry exhausted after {retries} attempts")
}
AgenticError::Other(msg) => write!(f, "{msg}"),
}
}
}
impl std::error::Error for AgenticError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
AgenticError::Io(err) => Some(err),
AgenticError::Json(err) => Some(err),
_ => None,
}
}
}
impl From<std::io::Error> for AgenticError {
fn from(err: std::io::Error) -> Self {
AgenticError::Io(err)
}
}
impl From<serde_json::Error> for AgenticError {
fn from(err: serde_json::Error) -> Self {
AgenticError::Json(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_api_error() {
let err = AgenticError::Api {
message: "rate limited".into(),
status: Some(429),
retryable: true,
retry_after_ms: None,
};
let display = format!("{err}");
assert!(display.contains("429"));
assert!(display.contains("rate limited"));
}
#[test]
fn from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err: AgenticError = io_err.into();
assert!(matches!(err, AgenticError::Io(_)));
assert!(format!("{err}").contains("file not found"));
}
#[test]
fn from_json_error() {
let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
let err: AgenticError = json_err.into();
assert!(matches!(err, AgenticError::Json(_)));
}
#[test]
fn all_variants_display_non_empty() {
let variants: Vec<AgenticError> = vec![
AgenticError::Api {
message: "msg".into(),
status: Some(500),
retryable: false,
retry_after_ms: None,
},
AgenticError::Tool {
tool_name: "tool".into(),
message: "err".into(),
},
AgenticError::Io(std::io::Error::new(std::io::ErrorKind::Other, "io")),
AgenticError::Json(serde_json::from_str::<()>("bad").unwrap_err()),
AgenticError::Aborted,
AgenticError::MaxTurnsExceeded(10),
AgenticError::ContextOverflow {
token_count: 200_000,
limit: 100_000,
},
AgenticError::SchemaValidation {
path: "/a".into(),
message: "bad".into(),
},
AgenticError::SchemaRetryExhausted { retries: 3 },
AgenticError::Other("other".into()),
];
for variant in &variants {
let display = format!("{variant}");
assert!(!display.is_empty(), "Empty display for: {variant:?}");
}
}
}