use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum IcarusError {
#[error("MCP error: {0}")]
Mcp(String),
#[error("Canister error: {0}")]
Canister(String),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("State error: {0}")]
State(String),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Tool error: {0}")]
Tool(#[from] ToolError),
#[error("{0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, IcarusError>;
impl From<candid::Error> for IcarusError {
fn from(err: candid::Error) -> Self {
IcarusError::Canister(err.to_string())
}
}
#[derive(Error, Debug, Clone, Serialize, Deserialize)]
pub enum ToolError {
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("Operation failed: {0}")]
OperationFailed(String),
#[error("Internal error: {0}")]
InternalError(String),
}
impl ToolError {
pub fn invalid_input(msg: impl Into<String>) -> Self {
Self::InvalidInput(msg.into())
}
pub fn not_found(msg: impl Into<String>) -> Self {
Self::NotFound(msg.into())
}
pub fn permission_denied(msg: impl Into<String>) -> Self {
Self::PermissionDenied(msg.into())
}
pub fn operation_failed(msg: impl Into<String>) -> Self {
Self::OperationFailed(msg.into())
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::InternalError(msg.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_icarus_error_display() {
let err = IcarusError::Mcp("connection failed".to_string());
assert_eq!(err.to_string(), "MCP error: connection failed");
let err = IcarusError::Canister("out of cycles".to_string());
assert_eq!(err.to_string(), "Canister error: out of cycles");
let err = IcarusError::State("invalid state".to_string());
assert_eq!(err.to_string(), "State error: invalid state");
let err = IcarusError::Protocol("invalid message".to_string());
assert_eq!(err.to_string(), "Protocol error: invalid message");
let err = IcarusError::Other("something went wrong".to_string());
assert_eq!(err.to_string(), "something went wrong");
}
#[test]
fn test_tool_error_helpers() {
let err = ToolError::invalid_input("bad input");
assert_eq!(err.to_string(), "Invalid input: bad input");
let err = ToolError::not_found("resource missing");
assert_eq!(err.to_string(), "Not found: resource missing");
let err = ToolError::permission_denied("unauthorized");
assert_eq!(err.to_string(), "Permission denied: unauthorized");
let err = ToolError::operation_failed("failed to process");
assert_eq!(err.to_string(), "Operation failed: failed to process");
let err = ToolError::internal("internal error");
assert_eq!(err.to_string(), "Internal error: internal error");
}
#[test]
fn test_tool_error_conversion() {
let tool_err = ToolError::invalid_input("bad data");
let icarus_err: IcarusError = tool_err.into();
assert_eq!(
icarus_err.to_string(),
"Tool error: Invalid input: bad data"
);
}
#[test]
fn test_candid_error_conversion() {
fn _test_conversion_exists() {
let _: fn(candid::Error) -> IcarusError = IcarusError::from;
}
}
#[test]
fn test_tool_error_serialization() {
let err = ToolError::not_found("item");
let serialized = serde_json::to_string(&err).unwrap();
let deserialized: ToolError = serde_json::from_str(&serialized).unwrap();
assert_eq!(err.to_string(), deserialized.to_string());
}
#[test]
fn test_tool_error_clone() {
let err1 = ToolError::permission_denied("access denied");
let err2 = err1.clone();
assert_eq!(err1.to_string(), err2.to_string());
}
}