1use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum IcarusError {
9 #[error("MCP error: {0}")]
11 Mcp(String),
12
13 #[error("Canister error: {0}")]
15 Canister(String),
16
17 #[error("Serialization error: {0}")]
19 Serialization(#[from] serde_json::Error),
20
21 #[error("State error: {0}")]
23 State(String),
24
25 #[error("Protocol error: {0}")]
27 Protocol(String),
28
29 #[error("Tool error: {0}")]
31 Tool(#[from] ToolError),
32
33 #[error("{0}")]
35 Other(String),
36}
37
38pub type Result<T> = std::result::Result<T, IcarusError>;
40
41impl From<candid::Error> for IcarusError {
42 fn from(err: candid::Error) -> Self {
43 IcarusError::Canister(err.to_string())
44 }
45}
46
47#[derive(Error, Debug, Clone, Serialize, Deserialize)]
49pub enum ToolError {
50 #[error("Invalid input: {0}")]
52 InvalidInput(String),
53
54 #[error("Not found: {0}")]
56 NotFound(String),
57
58 #[error("Permission denied: {0}")]
60 PermissionDenied(String),
61
62 #[error("Operation failed: {0}")]
64 OperationFailed(String),
65
66 #[error("Internal error: {0}")]
68 InternalError(String),
69}
70
71impl ToolError {
72 pub fn invalid_input(msg: impl Into<String>) -> Self {
74 Self::InvalidInput(msg.into())
75 }
76
77 pub fn not_found(msg: impl Into<String>) -> Self {
79 Self::NotFound(msg.into())
80 }
81
82 pub fn permission_denied(msg: impl Into<String>) -> Self {
84 Self::PermissionDenied(msg.into())
85 }
86
87 pub fn operation_failed(msg: impl Into<String>) -> Self {
89 Self::OperationFailed(msg.into())
90 }
91
92 pub fn internal(msg: impl Into<String>) -> Self {
94 Self::InternalError(msg.into())
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_icarus_error_display() {
104 let err = IcarusError::Mcp("connection failed".to_string());
105 assert_eq!(err.to_string(), "MCP error: connection failed");
106
107 let err = IcarusError::Canister("out of cycles".to_string());
108 assert_eq!(err.to_string(), "Canister error: out of cycles");
109
110 let err = IcarusError::State("invalid state".to_string());
111 assert_eq!(err.to_string(), "State error: invalid state");
112
113 let err = IcarusError::Protocol("invalid message".to_string());
114 assert_eq!(err.to_string(), "Protocol error: invalid message");
115
116 let err = IcarusError::Other("something went wrong".to_string());
117 assert_eq!(err.to_string(), "something went wrong");
118 }
119
120 #[test]
121 fn test_tool_error_helpers() {
122 let err = ToolError::invalid_input("bad input");
123 assert_eq!(err.to_string(), "Invalid input: bad input");
124
125 let err = ToolError::not_found("resource missing");
126 assert_eq!(err.to_string(), "Not found: resource missing");
127
128 let err = ToolError::permission_denied("unauthorized");
129 assert_eq!(err.to_string(), "Permission denied: unauthorized");
130
131 let err = ToolError::operation_failed("failed to process");
132 assert_eq!(err.to_string(), "Operation failed: failed to process");
133
134 let err = ToolError::internal("internal error");
135 assert_eq!(err.to_string(), "Internal error: internal error");
136 }
137
138 #[test]
139 fn test_tool_error_conversion() {
140 let tool_err = ToolError::invalid_input("bad data");
141 let icarus_err: IcarusError = tool_err.into();
142 assert_eq!(
143 icarus_err.to_string(),
144 "Tool error: Invalid input: bad data"
145 );
146 }
147
148 #[test]
149 fn test_candid_error_conversion() {
150 fn _test_conversion_exists() {
153 let _: fn(candid::Error) -> IcarusError = IcarusError::from;
154 }
155 }
156
157 #[test]
158 fn test_tool_error_serialization() {
159 let err = ToolError::not_found("item");
160 let serialized = serde_json::to_string(&err).unwrap();
161 let deserialized: ToolError = serde_json::from_str(&serialized).unwrap();
162 assert_eq!(err.to_string(), deserialized.to_string());
163 }
164
165 #[test]
166 fn test_tool_error_clone() {
167 let err1 = ToolError::permission_denied("access denied");
168 let err2 = err1.clone();
169 assert_eq!(err1.to_string(), err2.to_string());
170 }
171}