Skip to main content

universal_tool_core/
error.rs

1use schemars::JsonSchema;
2use serde::Deserialize;
3use serde::Serialize;
4use std::collections::HashMap;
5use thiserror::Error;
6
7/// The primary error type that all tool functions must return in their Result.
8#[derive(Debug, Error, Serialize, Deserialize, JsonSchema)]
9pub struct ToolError {
10    /// A machine-readable code for the error category.
11    pub code: ErrorCode,
12    /// A human-readable, context-specific error message.
13    pub message: String,
14    /// Additional error context as key-value pairs.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub details: Option<HashMap<String, serde_json::Value>>,
17}
18
19/// A controlled vocabulary for error types.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
21pub enum ErrorCode {
22    BadRequest,
23    InvalidArgument,
24    NotFound,
25    PermissionDenied,
26    Internal,
27    Timeout,
28    Conflict,
29    NetworkError,
30    ExternalServiceError,
31    ExecutionFailed,
32    SerializationError,
33    IoError,
34}
35
36// Implementation of Display for ToolError to satisfy the Error trait
37impl std::fmt::Display for ToolError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        write!(f, "[{:?}] {}", self.code, self.message)
40    }
41}
42
43// Convenience constructors
44impl ToolError {
45    /// Create a new ToolError with the given code and message.
46    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
47        Self {
48            code,
49            message: message.into(),
50            details: None,
51        }
52    }
53
54    /// Add details to this error.
55    pub fn with_details(mut self, details: HashMap<String, serde_json::Value>) -> Self {
56        self.details = Some(details);
57        self
58    }
59
60    /// Add a single detail string to this error.
61    pub fn with_detail(mut self, key: &str, value: impl Into<serde_json::Value>) -> Self {
62        let mut details = self.details.unwrap_or_default();
63        details.insert(key.to_string(), value.into());
64        self.details = Some(details);
65        self
66    }
67
68    // Convenience constructors for common error types
69    pub fn internal(message: impl Into<String>) -> Self {
70        Self::new(ErrorCode::Internal, message)
71    }
72
73    pub fn not_found(message: impl Into<String>) -> Self {
74        Self::new(ErrorCode::NotFound, message)
75    }
76
77    pub fn invalid_input(message: impl Into<String>) -> Self {
78        Self::new(ErrorCode::InvalidArgument, message)
79    }
80
81    pub fn conflict(message: impl Into<String>) -> Self {
82        Self::new(ErrorCode::Conflict, message)
83    }
84}
85
86// From implementations for common error types
87impl From<std::io::Error> for ToolError {
88    fn from(err: std::io::Error) -> Self {
89        Self::new(ErrorCode::ExecutionFailed, err.to_string())
90    }
91}
92
93impl From<serde_json::Error> for ToolError {
94    fn from(err: serde_json::Error) -> Self {
95        Self::new(ErrorCode::InvalidArgument, err.to_string())
96    }
97}
98
99impl From<String> for ToolError {
100    fn from(err: String) -> Self {
101        Self::new(ErrorCode::ExecutionFailed, err)
102    }
103}
104
105impl From<&str> for ToolError {
106    fn from(err: &str) -> Self {
107        Self::new(ErrorCode::ExecutionFailed, err)
108    }
109}