Skip to main content

serdes_ai_tools/
errors.rs

1//! Tool-specific error types.
2//!
3//! This module provides comprehensive error types for tool execution,
4//! including retryable errors, approval flows, and validation failures.
5
6use serde::{Deserialize, Serialize};
7use std::time::Duration;
8use thiserror::Error;
9
10/// Structured validation error for tool arguments.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12pub struct ValidationError {
13    /// Field that failed validation, if applicable.
14    pub field: Option<String>,
15    /// Validation error message.
16    pub message: String,
17}
18
19impl ValidationError {
20    /// Create a new validation error.
21    #[must_use]
22    pub fn new(field: Option<String>, message: impl Into<String>) -> Self {
23        Self {
24            field,
25            message: message.into(),
26        }
27    }
28}
29
30/// Errors that can occur during tool execution.
31#[derive(Debug, Error)]
32pub enum ToolError {
33    /// Tool execution failed.
34    #[error("Tool execution failed: {message}")]
35    ExecutionFailed {
36        /// Error message.
37        message: String,
38        /// Whether this error is retryable.
39        retryable: bool,
40    },
41
42    /// Tool not found in registry.
43    #[error("Tool not found: {0}")]
44    NotFound(String),
45
46    /// Model should retry with different arguments.
47    #[error("Model retry requested: {0}")]
48    ModelRetry(String),
49
50    /// Tool requires approval before execution.
51    #[error("Approval required for tool '{tool_name}'")]
52    ApprovalRequired {
53        /// Name of the tool requiring approval.
54        tool_name: String,
55        /// Arguments that were provided.
56        args: serde_json::Value,
57    },
58
59    /// Tool call was deferred for later execution.
60    #[error("Tool call '{tool_name}' deferred")]
61    CallDeferred {
62        /// Name of the deferred tool.
63        tool_name: String,
64        /// Arguments for the deferred call.
65        args: serde_json::Value,
66    },
67
68    /// Tool execution timed out.
69    #[error("Tool execution timed out after {0:?}")]
70    Timeout(Duration),
71
72    /// Tool argument validation failed.
73    #[error("Tool argument validation failed for '{tool_name}'")]
74    ValidationFailed {
75        /// Name of the tool.
76        tool_name: String,
77        /// Validation errors.
78        errors: Vec<ValidationError>,
79    },
80
81    /// Tool was cancelled.
82    #[error("Tool execution cancelled")]
83    Cancelled,
84
85    /// Tool returned an error result.
86    #[error("Tool returned error: {0}")]
87    ToolReturnedError(String),
88
89    /// JSON serialization/deserialization error.
90    #[error("JSON error: {0}")]
91    Json(#[from] serde_json::Error),
92
93    /// Other errors.
94    #[error(transparent)]
95    Other(#[from] anyhow::Error),
96}
97
98impl ToolError {
99    /// Check if this error is retryable.
100    #[must_use]
101    pub fn is_retryable(&self) -> bool {
102        match self {
103            Self::ExecutionFailed { retryable, .. } => *retryable,
104            Self::ModelRetry(_) => true,
105            Self::Timeout(_) => true,
106            Self::ValidationFailed { .. } => false,
107            Self::NotFound(_) => false,
108            Self::ApprovalRequired { .. } => false,
109            Self::CallDeferred { .. } => false,
110            Self::Cancelled => false,
111            Self::ToolReturnedError(_) => false,
112            Self::Json(_) => false,
113            Self::Other(_) => false,
114        }
115    }
116
117    /// Create a non-retryable execution failure.
118    #[must_use]
119    pub fn execution_failed(msg: impl Into<String>) -> Self {
120        Self::ExecutionFailed {
121            message: msg.into(),
122            retryable: false,
123        }
124    }
125
126    /// Create a retryable execution failure.
127    #[must_use]
128    pub fn retryable(msg: impl Into<String>) -> Self {
129        Self::ExecutionFailed {
130            message: msg.into(),
131            retryable: true,
132        }
133    }
134
135    /// Create a validation failed error with multiple issues.
136    #[must_use]
137    pub fn validation_failed(tool_name: impl Into<String>, errors: Vec<ValidationError>) -> Self {
138        Self::ValidationFailed {
139            tool_name: tool_name.into(),
140            errors,
141        }
142    }
143
144    /// Create a validation failed error with a single issue.
145    #[must_use]
146    pub fn validation_error(
147        tool_name: impl Into<String>,
148        field: Option<String>,
149        message: impl Into<String>,
150    ) -> Self {
151        Self::validation_failed(tool_name, vec![ValidationError::new(field, message)])
152    }
153
154    /// Create an invalid arguments error for argument parsing.
155    #[must_use]
156    pub fn invalid_arguments(tool_name: impl Into<String>, message: impl Into<String>) -> Self {
157        Self::validation_failed(tool_name, vec![ValidationError::new(None, message)])
158    }
159
160    /// Create a not found error.
161    #[must_use]
162    pub fn not_found(name: impl Into<String>) -> Self {
163        Self::NotFound(name.into())
164    }
165
166    /// Create a model retry error.
167    #[must_use]
168    pub fn model_retry(msg: impl Into<String>) -> Self {
169        Self::ModelRetry(msg.into())
170    }
171
172    /// Create an approval required error.
173    #[must_use]
174    pub fn approval_required(tool_name: impl Into<String>, args: serde_json::Value) -> Self {
175        Self::ApprovalRequired {
176            tool_name: tool_name.into(),
177            args,
178        }
179    }
180
181    /// Create a call deferred error.
182    #[must_use]
183    pub fn call_deferred(tool_name: impl Into<String>, args: serde_json::Value) -> Self {
184        Self::CallDeferred {
185            tool_name: tool_name.into(),
186            args,
187        }
188    }
189
190    /// Create a timeout error.
191    #[must_use]
192    pub fn timeout(duration: Duration) -> Self {
193        Self::Timeout(duration)
194    }
195
196    /// Get the error message.
197    #[must_use]
198    pub fn message(&self) -> String {
199        self.to_string()
200    }
201
202    /// Check if this is an approval required error.
203    #[must_use]
204    pub fn is_approval_required(&self) -> bool {
205        matches!(self, Self::ApprovalRequired { .. })
206    }
207
208    /// Check if this is a call deferred error.
209    #[must_use]
210    pub fn is_call_deferred(&self) -> bool {
211        matches!(self, Self::CallDeferred { .. })
212    }
213
214    /// Check if this is a model retry error.
215    #[must_use]
216    pub fn is_model_retry(&self) -> bool {
217        matches!(self, Self::ModelRetry(_))
218    }
219}
220
221impl From<String> for ToolError {
222    fn from(s: String) -> Self {
223        Self::execution_failed(s)
224    }
225}
226
227impl From<&str> for ToolError {
228    fn from(s: &str) -> Self {
229        Self::execution_failed(s)
230    }
231}
232
233/// Serializable error information for tool return.
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct ToolErrorInfo {
236    /// Error type/code.
237    pub error_type: String,
238    /// Human-readable message.
239    pub message: String,
240    /// Whether the error is retryable.
241    pub retryable: bool,
242    /// Additional details.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub details: Option<serde_json::Value>,
245}
246
247impl ToolErrorInfo {
248    /// Create a new error info.
249    #[must_use]
250    pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
251        Self {
252            error_type: error_type.into(),
253            message: message.into(),
254            retryable: false,
255            details: None,
256        }
257    }
258
259    /// Set retryable flag.
260    #[must_use]
261    pub fn retryable(mut self, retryable: bool) -> Self {
262        self.retryable = retryable;
263        self
264    }
265
266    /// Add details.
267    #[must_use]
268    pub fn with_details(mut self, details: serde_json::Value) -> Self {
269        self.details = Some(details);
270        self
271    }
272}
273
274impl From<&ToolError> for ToolErrorInfo {
275    fn from(err: &ToolError) -> Self {
276        let error_type = match err {
277            ToolError::ExecutionFailed { .. } => "execution_failed",
278            ToolError::NotFound(_) => "not_found",
279            ToolError::ModelRetry(_) => "model_retry",
280            ToolError::ApprovalRequired { .. } => "approval_required",
281            ToolError::CallDeferred { .. } => "call_deferred",
282            ToolError::Timeout(_) => "timeout",
283            ToolError::ValidationFailed { .. } => "validation_failed",
284            ToolError::Cancelled => "cancelled",
285            ToolError::ToolReturnedError(_) => "tool_error",
286            ToolError::Json(_) => "json_error",
287            ToolError::Other(_) => "other",
288        };
289
290        let details = match err {
291            ToolError::ValidationFailed { errors, .. } => serde_json::to_value(errors).ok(),
292            _ => None,
293        };
294
295        Self {
296            error_type: error_type.to_string(),
297            message: err.message(),
298            retryable: err.is_retryable(),
299            details,
300        }
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_execution_failed() {
310        let err = ToolError::execution_failed("Something went wrong");
311        assert!(!err.is_retryable());
312        assert!(err.message().contains("Something went wrong"));
313    }
314
315    #[test]
316    fn test_retryable_error() {
317        let err = ToolError::retryable("Temporary failure");
318        assert!(err.is_retryable());
319    }
320
321    #[test]
322    fn test_not_found() {
323        let err = ToolError::not_found("unknown_tool");
324        assert!(!err.is_retryable());
325        assert!(err.message().contains("unknown_tool"));
326    }
327
328    #[test]
329    fn test_approval_required() {
330        let err = ToolError::approval_required("dangerous_tool", serde_json::json!({"x": 1}));
331        assert!(err.is_approval_required());
332        assert!(!err.is_retryable());
333    }
334
335    #[test]
336    fn test_call_deferred() {
337        let err = ToolError::call_deferred("slow_tool", serde_json::json!({"a": "b"}));
338        assert!(err.is_call_deferred());
339    }
340
341    #[test]
342    fn test_timeout() {
343        let err = ToolError::timeout(Duration::from_secs(30));
344        assert!(err.is_retryable());
345        assert!(err.message().contains("30"));
346    }
347
348    #[test]
349    fn test_model_retry() {
350        let err = ToolError::model_retry("Invalid format");
351        assert!(err.is_model_retry());
352        assert!(err.is_retryable());
353    }
354
355    #[test]
356    fn test_validation_failed() {
357        let err =
358            ToolError::validation_error("test_tool", Some("field".to_string()), "Invalid value");
359        assert!(!err.is_retryable());
360        let info = ToolErrorInfo::from(&err);
361        assert_eq!(info.error_type, "validation_failed");
362        assert!(info.details.is_some());
363    }
364
365    #[test]
366    fn test_error_info_from_error() {
367        let err = ToolError::execution_failed("Test error");
368        let info = ToolErrorInfo::from(&err);
369        assert_eq!(info.error_type, "execution_failed");
370        assert!(!info.retryable);
371    }
372
373    #[test]
374    fn test_from_string() {
375        let err: ToolError = "error message".into();
376        assert!(matches!(err, ToolError::ExecutionFailed { .. }));
377    }
378}