use thiserror::Error;
#[derive(Debug, Error)]
pub enum ToolError {
#[error("Tool '{name}' not found in registry")]
NotFound {
name: String,
},
#[error("Invalid arguments for '{tool}': {reason}")]
InvalidArguments {
tool: String,
reason: String,
},
#[error("Execution of '{tool}' failed: {message}")]
ExecutionFailed {
tool: String,
message: String,
},
#[error("Tool '{tool}' timed out after {timeout_ms}ms")]
Timeout {
tool: String,
timeout_ms: u64,
},
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Audit logging failed: {0}")]
AuditFailed(String),
#[error("Tool '{name}' is currently unavailable: {reason}")]
Unavailable {
name: String,
reason: String,
},
}
impl ToolError {
pub fn not_found(name: impl Into<String>) -> Self {
Self::NotFound { name: name.into() }
}
pub fn invalid_args(tool: impl Into<String>, reason: impl Into<String>) -> Self {
Self::InvalidArguments {
tool: tool.into(),
reason: reason.into(),
}
}
pub fn execution_failed(tool: impl Into<String>, message: impl Into<String>) -> Self {
Self::ExecutionFailed {
tool: tool.into(),
message: message.into(),
}
}
pub fn timeout(tool: impl Into<String>, timeout_ms: u64) -> Self {
Self::Timeout {
tool: tool.into(),
timeout_ms,
}
}
pub fn unavailable(name: impl Into<String>, reason: impl Into<String>) -> Self {
Self::Unavailable {
name: name.into(),
reason: reason.into(),
}
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Timeout { .. } | Self::Unavailable { .. })
}
pub fn should_audit(&self) -> bool {
!matches!(self, Self::AuditFailed(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = ToolError::not_found("calculator");
assert!(err.to_string().contains("calculator"));
assert!(err.to_string().contains("not found"));
}
#[test]
fn test_invalid_args() {
let err = ToolError::invalid_args("datetime", "Missing timezone field");
assert!(err.to_string().contains("datetime"));
assert!(err.to_string().contains("Missing timezone"));
}
#[test]
fn test_retryable() {
assert!(ToolError::timeout("test", 1000).is_retryable());
assert!(ToolError::unavailable("test", "maintenance").is_retryable());
assert!(!ToolError::not_found("test").is_retryable());
}
#[test]
fn test_should_audit() {
assert!(ToolError::not_found("test").should_audit());
assert!(!ToolError::AuditFailed("db error".into()).should_audit());
}
}