1use std::fmt;
6
7#[derive(Debug, Clone)]
11pub struct ToolError {
12 pub code: String,
14 pub message: String,
16 pub details: Option<String>,
18 pub suggestion: Option<String>,
20}
21
22impl ToolError {
23 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
25 Self {
26 code: code.into(),
27 message: message.into(),
28 details: None,
29 suggestion: None,
30 }
31 }
32
33 pub fn with_details(mut self, details: impl Into<String>) -> Self {
35 self.details = Some(details.into());
36 self
37 }
38
39 pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
41 self.suggestion = Some(suggestion.into());
42 self
43 }
44}
45
46impl fmt::Display for ToolError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "[{}] {}", self.code, self.message)?;
49 if let Some(details) = &self.details {
50 write!(f, " ({})", details)?;
51 }
52 if let Some(suggestion) = &self.suggestion {
53 write!(f, " - Suggestion: {}", suggestion)?;
54 }
55 Ok(())
56 }
57}
58
59impl std::error::Error for ToolError {}
60
61impl From<reqwest::Error> for ToolError {
63 fn from(err: reqwest::Error) -> Self {
64 let (code, message) = if err.is_timeout() {
65 ("TIMEOUT", "Request timeout")
66 } else if err.is_connect() {
67 ("NETWORK_ERROR", "Connection failed")
68 } else if err.is_request() {
69 ("REQUEST_ERROR", "Invalid request")
70 } else if err.is_status() {
71 ("HTTP_ERROR", "HTTP error response")
72 } else {
73 ("NETWORK_ERROR", "Network error")
74 };
75
76 ToolError::new(code, message)
77 .with_details(err.to_string())
78 .with_suggestion("Check your network connection and try again")
79 }
80}
81
82impl From<std::io::Error> for ToolError {
84 fn from(err: std::io::Error) -> Self {
85 let (code, message) = match err.kind() {
86 std::io::ErrorKind::NotFound => ("FILE_NOT_FOUND", "File not found"),
87 std::io::ErrorKind::PermissionDenied => ("PERMISSION_DENIED", "Permission denied"),
88 std::io::ErrorKind::InvalidInput => ("INVALID_INPUT", "Invalid input"),
89 std::io::ErrorKind::TimedOut => ("TIMEOUT", "Operation timeout"),
90 _ => ("IO_ERROR", "IO error"),
91 };
92
93 ToolError::new(code, message)
94 .with_details(err.to_string())
95 }
96}
97
98impl From<serde_json::Error> for ToolError {
100 fn from(err: serde_json::Error) -> Self {
101 ToolError::new("JSON_ERROR", "JSON parsing error")
102 .with_details(err.to_string())
103 .with_suggestion("Check the JSON format and try again")
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_tool_error_creation() {
113 let err = ToolError::new("TEST_ERROR", "Test message");
114 assert_eq!(err.code, "TEST_ERROR");
115 assert_eq!(err.message, "Test message");
116 assert!(err.details.is_none());
117 assert!(err.suggestion.is_none());
118 }
119
120 #[test]
121 fn test_tool_error_with_details() {
122 let err = ToolError::new("TEST_ERROR", "Test message")
123 .with_details("Additional context");
124 assert_eq!(err.details, Some("Additional context".to_string()));
125 }
126
127 #[test]
128 fn test_tool_error_with_suggestion() {
129 let err = ToolError::new("TEST_ERROR", "Test message")
130 .with_suggestion("Try this instead");
131 assert_eq!(err.suggestion, Some("Try this instead".to_string()));
132 }
133
134 #[test]
135 fn test_tool_error_display() {
136 let err = ToolError::new("TEST_ERROR", "Test message")
137 .with_details("Details")
138 .with_suggestion("Suggestion");
139 let display = format!("{}", err);
140 assert!(display.contains("TEST_ERROR"));
141 assert!(display.contains("Test message"));
142 assert!(display.contains("Details"));
143 assert!(display.contains("Suggestion"));
144 }
145}