hehe_core/
error.rs

1use thiserror::Error;
2
3pub mod codes {
4    pub const CONFIG_INVALID: &str = "E1001";
5    pub const CONFIG_MISSING: &str = "E1002";
6    pub const VALIDATION_FAILED: &str = "E2001";
7    pub const INVALID_INPUT: &str = "E2002";
8    pub const NOT_FOUND: &str = "E3001";
9    pub const ALREADY_EXISTS: &str = "E3002";
10    pub const CANCELLED: &str = "E4001";
11    pub const TIMEOUT: &str = "E4002";
12    pub const NOT_PERMITTED: &str = "E4003";
13    pub const RATE_LIMITED: &str = "E4004";
14    pub const LLM_REQUEST_FAILED: &str = "E5001";
15    pub const LLM_RATE_LIMITED: &str = "E5002";
16    pub const LLM_CONTEXT_LENGTH: &str = "E5003";
17    pub const LLM_INVALID_RESPONSE: &str = "E5004";
18    pub const TOOL_NOT_FOUND: &str = "E6001";
19    pub const TOOL_EXECUTION_FAILED: &str = "E6002";
20    pub const TOOL_INVALID_INPUT: &str = "E6003";
21    pub const STORAGE_CONNECTION: &str = "E7001";
22    pub const STORAGE_QUERY: &str = "E7002";
23    pub const STORAGE_WRITE: &str = "E7003";
24    pub const INTERNAL: &str = "E9001";
25    pub const NOT_IMPLEMENTED: &str = "E9002";
26}
27
28#[derive(Error, Debug)]
29pub enum Error {
30    #[error("Configuration error: {0}")]
31    Config(String),
32
33    #[error("Missing required config: {0}")]
34    MissingConfig(String),
35
36    #[error("JSON error: {0}")]
37    Json(#[from] serde_json::Error),
38
39    #[error("IO error: {0}")]
40    Io(#[from] std::io::Error),
41
42    #[error("Validation error: {0}")]
43    Validation(String),
44
45    #[error("Invalid input: {field} - {message}")]
46    InvalidInput { field: String, message: String },
47
48    #[error("Not found: {resource_type} with id {id}")]
49    NotFound { resource_type: String, id: String },
50
51    #[error("Already exists: {resource_type} with id {id}")]
52    AlreadyExists { resource_type: String, id: String },
53
54    #[error("Operation cancelled")]
55    Cancelled,
56
57    #[error("Operation timeout after {0}ms")]
58    Timeout(u64),
59
60    #[error("Rate limited: {0}")]
61    RateLimited(String),
62
63    #[error("Operation not permitted: {0}")]
64    NotPermitted(String),
65
66    #[error("LLM error: {provider} - {message}")]
67    Llm { provider: String, message: String },
68
69    #[error("Tool error: {tool} - {message}")]
70    Tool { tool: String, message: String },
71
72    #[error("Storage error: {backend} - {message}")]
73    Storage { backend: String, message: String },
74
75    #[error("Not implemented: {0}")]
76    NotImplemented(String),
77
78    #[error("Internal error: {0}")]
79    Internal(String),
80
81    #[error(transparent)]
82    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
83}
84
85impl Error {
86    pub fn code(&self) -> &'static str {
87        match self {
88            Error::Config(_) => codes::CONFIG_INVALID,
89            Error::MissingConfig(_) => codes::CONFIG_MISSING,
90            Error::Json(_) => codes::VALIDATION_FAILED,
91            Error::Io(_) => codes::INTERNAL,
92            Error::Validation(_) => codes::VALIDATION_FAILED,
93            Error::InvalidInput { .. } => codes::INVALID_INPUT,
94            Error::NotFound { .. } => codes::NOT_FOUND,
95            Error::AlreadyExists { .. } => codes::ALREADY_EXISTS,
96            Error::Cancelled => codes::CANCELLED,
97            Error::Timeout(_) => codes::TIMEOUT,
98            Error::RateLimited(_) => codes::RATE_LIMITED,
99            Error::NotPermitted(_) => codes::NOT_PERMITTED,
100            Error::Llm { .. } => codes::LLM_REQUEST_FAILED,
101            Error::Tool { .. } => codes::TOOL_EXECUTION_FAILED,
102            Error::Storage { .. } => codes::STORAGE_CONNECTION,
103            Error::NotImplemented(_) => codes::NOT_IMPLEMENTED,
104            Error::Internal(_) => codes::INTERNAL,
105            Error::Other(_) => codes::INTERNAL,
106        }
107    }
108
109    pub fn not_found(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
110        Self::NotFound {
111            resource_type: resource_type.into(),
112            id: id.into(),
113        }
114    }
115
116    pub fn already_exists(resource_type: impl Into<String>, id: impl Into<String>) -> Self {
117        Self::AlreadyExists {
118            resource_type: resource_type.into(),
119            id: id.into(),
120        }
121    }
122
123    pub fn invalid_input(field: impl Into<String>, message: impl Into<String>) -> Self {
124        Self::InvalidInput {
125            field: field.into(),
126            message: message.into(),
127        }
128    }
129
130    pub fn llm(provider: impl Into<String>, message: impl Into<String>) -> Self {
131        Self::Llm {
132            provider: provider.into(),
133            message: message.into(),
134        }
135    }
136
137    pub fn tool(tool: impl Into<String>, message: impl Into<String>) -> Self {
138        Self::Tool {
139            tool: tool.into(),
140            message: message.into(),
141        }
142    }
143
144    pub fn storage(backend: impl Into<String>, message: impl Into<String>) -> Self {
145        Self::Storage {
146            backend: backend.into(),
147            message: message.into(),
148        }
149    }
150}
151
152pub type Result<T> = std::result::Result<T, Error>;
153
154pub trait ResultExt<T> {
155    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T>;
156}
157
158impl<T, E: Into<Error>> ResultExt<T> for std::result::Result<T, E> {
159    fn with_context<F: FnOnce() -> String>(self, f: F) -> Result<T> {
160        self.map_err(|e| {
161            let inner = e.into();
162            Error::Internal(format!("{}: {}", f(), inner))
163        })
164    }
165}