1use std::borrow::Cow;
2use std::fmt;
3
4use anyhow::{Error, Result};
5
6pub const ERR_READ_FILE: &str = "failed to read file";
8pub const ERR_WRITE_FILE: &str = "failed to write file";
9pub const ERR_READ_DIR: &str = "failed to read directory";
10pub const ERR_CREATE_DIR: &str = "failed to create directory";
11pub const ERR_REMOVE_FILE: &str = "failed to remove file";
12pub const ERR_REMOVE_DIR: &str = "failed to remove directory";
13pub const ERR_READ_DIR_ENTRY: &str = "failed to read directory entry";
14pub const ERR_GET_FILE_TYPE: &str = "failed to read file type";
15pub const ERR_GET_METADATA: &str = "failed to read file metadata";
16pub const ERR_CANONICALIZE_PATH: &str = "failed to canonicalize path";
17pub const ERR_READ_SYMLINK: &str = "failed to read symlink";
18
19pub const ERR_CREATE_SKILLS_DIR: &str = "failed to create skills directory";
21pub const ERR_CREATE_SKILL_DIR: &str = "failed to create skill directory";
22pub const ERR_READ_SKILL_CODE: &str = "failed to read skill code";
23pub const ERR_WRITE_SKILL_CODE: &str = "failed to write skill code";
24pub const ERR_READ_SKILL_METADATA: &str = "failed to read skill metadata";
25pub const ERR_WRITE_SKILL_METADATA: &str = "failed to write skill metadata";
26pub const ERR_PARSE_SKILL_METADATA: &str = "failed to parse skill metadata";
27pub const ERR_WRITE_SKILL_DOCS: &str = "failed to write skill documentation";
28pub const ERR_DELETE_SKILL: &str = "failed to delete skill";
29pub const ERR_READ_SKILLS_DIR: &str = "failed to read skills directory";
30pub const ERR_TOOL_DENIED: &str = "tool denied or unavailable by policy";
31
32pub const ERR_CREATE_AUDIT_DIR: &str = "Failed to create audit directory";
34pub const ERR_WRITE_AUDIT_LOG: &str = "failed to write audit log";
35
36pub const ERR_CREATE_CHECKPOINT_DIR: &str = "failed to create checkpoint directory";
38pub const ERR_WRITE_CHECKPOINT: &str = "failed to write checkpoint";
39pub const ERR_READ_CHECKPOINT: &str = "failed to read checkpoint";
40
41pub const ERR_CREATE_POLICY_DIR: &str = "Failed to create directory for tool policy config";
43pub const ERR_CREATE_WORKSPACE_POLICY_DIR: &str = "Failed to create workspace policy directory";
44
45pub const ERR_SERIALIZE_METADATA: &str = "failed to serialize skill metadata";
47pub const ERR_SERIALIZE_STATE: &str = "failed to serialize state";
48pub const ERR_DESERIALIZE: &str = "failed to deserialize data";
49
50pub const ERR_CREATE_IPC_DIR: &str = "failed to create IPC directory";
52pub const ERR_READ_REQUEST_FILE: &str = "failed to read request file";
53pub const ERR_READ_REQUEST_JSON: &str = "failed to read request JSON";
54pub const ERR_PARSE_REQUEST_JSON: &str = "failed to parse request JSON";
55pub const ERR_PARSE_ARGS: &str = "failed to parse tokenized args";
56pub const ERR_PARSE_RESULT: &str = "failed to parse de-tokenized result";
57
58#[macro_export]
61macro_rules! file_err {
62 ($path:expr, read) => {
63 format!("failed to read {}", $path)
64 };
65 ($path:expr, write) => {
66 format!("failed to write {}", $path)
67 };
68 ($path:expr, delete) => {
69 format!("failed to delete {}", $path)
70 };
71 ($path:expr, create) => {
72 format!("failed to create {}", $path)
73 };
74}
75
76#[macro_export]
79macro_rules! ctx_err {
80 ($op:expr, $ctx:expr) => {
81 format!("{}: {}", $op, $ctx)
82 };
83}
84
85pub trait ErrorFormatter: Send + Sync {
89 fn format_error(&self, error: &Error) -> Cow<'_, str>;
91}
92
93pub trait ErrorReporter: Send + Sync {
95 fn capture(&self, error: &Error) -> Result<()>;
97
98 fn capture_message(&self, message: impl Into<Cow<'static, str>>) -> Result<()> {
100 let message: Cow<'static, str> = message.into();
101 self.capture(&Error::msg(message))
102 }
103}
104
105#[derive(Debug, Default, Clone, Copy)]
108pub struct NoopErrorReporter;
109
110impl ErrorReporter for NoopErrorReporter {
111 fn capture(&self, _error: &Error) -> Result<()> {
112 Ok(())
113 }
114}
115
116#[derive(Debug, Default, Clone, Copy)]
118pub struct DisplayErrorFormatter;
119
120impl ErrorFormatter for DisplayErrorFormatter {
121 fn format_error(&self, error: &Error) -> Cow<'_, str> {
122 Cow::Owned(format!("{error}"))
123 }
124}
125
126#[derive(Debug, Clone)]
150pub struct MultiErrors<E = Error> {
151 errors: Vec<E>,
152}
153
154impl<E> MultiErrors<E> {
155 pub fn new() -> Self {
157 Self { errors: Vec::new() }
158 }
159
160 pub fn push(&mut self, error: E) {
162 self.errors.push(error);
163 }
164
165 pub fn extend(&mut self, iter: impl IntoIterator<Item = E>) {
167 self.errors.extend(iter);
168 }
169
170 #[must_use]
172 pub fn is_empty(&self) -> bool {
173 self.errors.is_empty()
174 }
175
176 #[must_use]
178 pub fn len(&self) -> usize {
179 self.errors.len()
180 }
181
182 #[must_use]
184 pub fn into_inner(self) -> Vec<E> {
185 self.errors
186 }
187
188 #[must_use]
190 pub fn as_slice(&self) -> &[E] {
191 &self.errors
192 }
193
194 pub fn iter(&self) -> std::slice::Iter<'_, E> {
196 self.errors.iter()
197 }
198
199 pub fn ok(self) -> std::result::Result<(), Self> {
201 if self.errors.is_empty() {
202 Ok(())
203 } else {
204 Err(self)
205 }
206 }
207
208 pub fn clear(&mut self) {
210 self.errors.clear();
211 }
212
213 pub fn collect_result<T, F>(&mut self, result: std::result::Result<T, F>) -> Option<T>
218 where
219 F: Into<E>,
220 {
221 match result {
222 Ok(val) => Some(val),
223 Err(e) => {
224 self.errors.push(e.into());
225 None
226 }
227 }
228 }
229
230 #[must_use]
232 pub fn to_anyhow(&self) -> Error
233 where
234 E: fmt::Display,
235 {
236 Error::msg(self.to_string())
237 }
238}
239
240impl<E> Default for MultiErrors<E> {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246impl<E> From<Vec<E>> for MultiErrors<E> {
247 fn from(errors: Vec<E>) -> Self {
248 Self { errors }
249 }
250}
251
252impl<E> IntoIterator for MultiErrors<E> {
253 type Item = E;
254 type IntoIter = std::vec::IntoIter<E>;
255
256 fn into_iter(self) -> Self::IntoIter {
257 self.errors.into_iter()
258 }
259}
260
261impl<E: serde::Serialize> serde::Serialize for MultiErrors<E> {
262 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
263 self.errors.serialize(serializer)
264 }
265}
266
267impl<'de, E: serde::Deserialize<'de>> serde::Deserialize<'de> for MultiErrors<E> {
268 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
269 Vec::<E>::deserialize(deserializer).map(|errors| Self { errors })
270 }
271}
272
273impl<E: fmt::Display> fmt::Display for MultiErrors<E> {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 match self.errors.len() {
276 0 => write!(f, "no errors"),
277 1 => write!(f, "{}", self.errors[0]),
278 _ => {
279 for (i, error) in self.errors.iter().enumerate() {
280 if i > 0 {
281 writeln!(f)?;
282 }
283 write!(f, " {}. {error}", i + 1)?;
284 }
285 Ok(())
286 }
287 }
288 }
289}
290
291impl<E: std::error::Error + 'static> std::error::Error for MultiErrors<E> {
292 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
293 self.errors
294 .first()
295 .map(|e| e as &(dyn std::error::Error + 'static))
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn formatter_uses_display() {
305 let formatter = DisplayErrorFormatter;
306 let error = Error::msg("test error");
307 assert_eq!(formatter.format_error(&error), "test error");
308 }
309
310 #[test]
311 fn noop_reporter_drops_errors() {
312 let reporter = NoopErrorReporter;
313 let error = Error::msg("test");
314 assert!(reporter.capture(&error).is_ok());
315 assert!(reporter.capture_message("message").is_ok());
316 }
317
318 #[test]
319 fn multi_errors_new_is_empty() {
320 let errors: MultiErrors<String> = MultiErrors::new();
321 assert!(errors.is_empty());
322 assert_eq!(errors.len(), 0);
323 }
324
325 #[test]
326 fn multi_errors_push_and_len() {
327 let mut errors = MultiErrors::new();
328 errors.push("error 1".to_string());
329 errors.push("error 2".to_string());
330 assert!(!errors.is_empty());
331 assert_eq!(errors.len(), 2);
332 }
333
334 #[test]
335 fn multi_errors_collect_result_ok() {
336 let mut errors: MultiErrors<String> = MultiErrors::new();
337 let value: i32 = errors.collect_result(Ok::<_, String>(42)).unwrap_or(0);
338 assert_eq!(value, 42);
339 assert!(errors.is_empty());
340 }
341
342 #[test]
343 fn multi_errors_collect_result_err() {
344 let mut errors: MultiErrors<String> = MultiErrors::new();
345 let value: i32 = errors
346 .collect_result(Err::<i32, String>("bad".to_string()))
347 .unwrap_or(0);
348 assert_eq!(value, 0);
349 assert_eq!(errors.len(), 1);
350 }
351
352 #[test]
353 fn multi_errors_ok_succeeds_when_empty() {
354 let errors: MultiErrors<String> = MultiErrors::new();
355 assert!(errors.ok().is_ok());
356 }
357
358 #[test]
359 fn multi_errors_ok_fails_when_not_empty() {
360 let mut errors = MultiErrors::new();
361 errors.push("error".to_string());
362 assert!(errors.ok().is_err());
363 }
364
365 #[test]
366 fn multi_errors_display_empty() {
367 let errors: MultiErrors<String> = MultiErrors::new();
368 assert_eq!(errors.to_string(), "no errors");
369 }
370
371 #[test]
372 fn multi_errors_display_single() {
373 let mut errors = MultiErrors::new();
374 errors.push("something failed".to_string());
375 assert_eq!(errors.to_string(), "something failed");
376 }
377
378 #[test]
379 fn multi_errors_display_multiple() {
380 let mut errors = MultiErrors::new();
381 errors.push("first issue".to_string());
382 errors.push("second issue".to_string());
383 let display = errors.to_string();
384 assert!(display.contains("1. first issue"));
385 assert!(display.contains("2. second issue"));
386 }
387
388 #[test]
389 fn multi_errors_extend() {
390 let mut errors = MultiErrors::new();
391 errors.extend(vec!["a".to_string(), "b".to_string()]);
392 assert_eq!(errors.len(), 2);
393 }
394
395 #[test]
396 fn multi_errors_into_inner() {
397 let mut errors = MultiErrors::new();
398 errors.push("test".to_string());
399 let inner: Vec<String> = errors.into_inner();
400 assert_eq!(inner.len(), 1);
401 }
402
403 #[test]
404 fn multi_errors_from_vec() {
405 let errors: MultiErrors<String> = MultiErrors::from(vec!["a".to_string()]);
406 assert_eq!(errors.len(), 1);
407 }
408
409 #[test]
410 fn multi_errors_into_iterator() {
411 let mut errors = MultiErrors::new();
412 errors.push("a".to_string());
413 errors.push("b".to_string());
414 let collected: Vec<String> = errors.into_iter().collect();
415 assert_eq!(collected, vec!["a", "b"]);
416 }
417
418 #[test]
419 fn multi_errors_slice_access() {
420 let mut errors = MultiErrors::new();
421 errors.push("err".to_string());
422 assert_eq!(errors.as_slice(), &["err".to_string()]);
423 }
424
425 #[test]
426 fn multi_errors_to_anyhow() {
427 let mut errors = MultiErrors::new();
428 errors.push("something broke".to_string());
429 let err = errors.to_anyhow();
430 assert!(err.to_string().contains("something broke"));
431 }
432}