1use thiserror::Error;
7
8#[derive(Debug, Error)]
10pub enum KernelError {
11 #[error("Agent {id} not found")]
13 AgentNotFound {
14 id: crate::types::AgentId,
16 },
17
18 #[error("Permission denied: {reason}")]
20 PermissionDenied {
21 reason: String,
23 },
24
25 #[error("Program '{name}' not found")]
27 ProgramNotFound {
28 name: String,
30 },
31
32 #[error("Program '{name}' already installed")]
34 ProgramAlreadyExists {
35 name: String,
37 },
38
39 #[error("Invalid configuration: {detail}")]
41 InvalidConfig {
42 detail: String,
44 },
45
46 #[error("Seed '{id}' not found")]
48 SeedNotFound {
49 id: String,
51 },
52
53 #[error("Session '{id}' not found")]
55 SessionNotFound {
56 id: String,
58 },
59
60 #[error("State store error: {0}")]
62 StateStore(#[from] std::io::Error),
63
64 #[error("{0}")]
66 Internal(#[from] anyhow::Error),
67
68 #[error("Memory error: {reason}")]
70 Memory {
71 reason: String,
73 },
74
75 #[error("Operation timed out: {context}")]
77 Timeout {
78 context: String,
80 },
81
82 #[error("Rate limit exceeded: {context}")]
84 RateLimited {
85 context: String,
87 },
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum HttpStatus {
93 Ok = 200,
95 BadRequest = 400,
97 Forbidden = 403,
99 NotFound = 404,
101 Conflict = 409,
103 TooManyRequests = 429,
105 InternalServerError = 500,
107 ServiceUnavailable = 503,
109}
110
111impl From<HttpStatus> for u16 {
112 fn from(status: HttpStatus) -> u16 {
113 status as u16
114 }
115}
116
117impl KernelError {
118 pub fn http_status(&self) -> HttpStatus {
123 match self {
124 Self::AgentNotFound { .. } => HttpStatus::NotFound,
125 Self::PermissionDenied { .. } => HttpStatus::Forbidden,
126
127 Self::ProgramNotFound { .. } => HttpStatus::NotFound,
128 Self::ProgramAlreadyExists { .. } => HttpStatus::Conflict,
129 Self::InvalidConfig { .. } => HttpStatus::BadRequest,
130 Self::SeedNotFound { .. } => HttpStatus::NotFound,
131 Self::SessionNotFound { .. } => HttpStatus::NotFound,
132 Self::StateStore(_) => HttpStatus::InternalServerError,
133 Self::Memory { .. } => HttpStatus::InternalServerError,
134 Self::Timeout { .. } => HttpStatus::ServiceUnavailable,
135 Self::RateLimited { .. } => HttpStatus::TooManyRequests,
136 Self::Internal(_) => HttpStatus::InternalServerError,
137 }
138 }
139}
140
141pub type KernelResult<T> = Result<T, KernelError>;
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_error_display() {
150 let id = crate::types::AgentId::new_v4();
151 let err = KernelError::AgentNotFound { id };
152 let msg = err.to_string();
153 assert!(msg.contains("not found"));
154 }
155
156 #[test]
157 fn test_all_http_status_mappings() {
158 let id = crate::types::AgentId::new_v4();
159 assert_eq!(
160 u16::from(KernelError::AgentNotFound { id }.http_status()),
161 404
162 );
163 assert_eq!(
164 u16::from(
165 KernelError::PermissionDenied {
166 reason: "test".into()
167 }
168 .http_status()
169 ),
170 403
171 );
172 assert_eq!(
173 u16::from(KernelError::ProgramNotFound { name: "p".into() }.http_status()),
174 404
175 );
176 assert_eq!(
177 u16::from(KernelError::ProgramAlreadyExists { name: "p".into() }.http_status()),
178 409
179 );
180 assert_eq!(
181 u16::from(
182 KernelError::InvalidConfig {
183 detail: "bad".into()
184 }
185 .http_status()
186 ),
187 400
188 );
189 assert_eq!(
190 u16::from(KernelError::SeedNotFound { id: "s".into() }.http_status()),
191 404
192 );
193 assert_eq!(
194 u16::from(KernelError::SessionNotFound { id: "s".into() }.http_status()),
195 404
196 );
197 }
198
199 #[test]
200 fn test_internal_error_wrapping() {
201 let err = KernelError::Internal(anyhow::anyhow!("something broke"));
202 assert!(err.to_string().contains("something broke"));
203 assert_eq!(u16::from(err.http_status()), 500);
204 }
205
206 #[test]
207 fn test_io_error_conversion() {
208 let err =
209 KernelError::StateStore(std::io::Error::new(std::io::ErrorKind::NotFound, "gone"));
210 assert!(err.to_string().contains("gone"));
211 assert_eq!(u16::from(err.http_status()), 500);
212 }
213
214 #[test]
215 fn test_timeout_error_status() {
216 let err = KernelError::Timeout {
217 context: "agent execution exceeded 300s".into(),
218 };
219 assert!(err.to_string().contains("timed out"));
220 assert!(err.to_string().contains("300s"));
221 assert_eq!(u16::from(err.http_status()), 503);
222 }
223
224 #[test]
225 fn test_rate_limited_error_status() {
226 let err = KernelError::RateLimited {
227 context: "API calls exceeded 60/min".into(),
228 };
229 assert!(err.to_string().contains("Rate limit exceeded"));
230 assert!(err.to_string().contains("60/min"));
231 assert_eq!(u16::from(err.http_status()), 429);
232 }
233}