1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum CommonError {
8 #[error("Failed to parse shell command: {0}")]
10 ShellParse(String),
11
12 #[error("Command execution failed: {0}")]
14 CommandExecution(String),
15
16 #[error("File system error: {0}")]
18 FileSystem(#[from] std::io::Error),
19
20 #[error("JSON error: {0}")]
22 Json(#[from] serde_json::Error),
23
24 #[error("{context}: {source}")]
26 WithContext {
27 context: String,
28 #[source]
29 source: Box<dyn std::error::Error + Send + Sync>,
30 },
31
32 #[error(transparent)]
34 Other(#[from] anyhow::Error),
35}
36
37pub type Result<T> = std::result::Result<T, CommonError>;
39
40impl CommonError {
41 pub fn with_context<E>(context: impl Into<String>, error: E) -> Self
43 where
44 E: std::error::Error + Send + Sync + 'static,
45 {
46 Self::WithContext {
47 context: context.into(),
48 source: Box::new(error),
49 }
50 }
51
52 pub fn shell_parse(msg: impl Into<String>) -> Self {
54 Self::ShellParse(msg.into())
55 }
56
57 pub fn command_execution(msg: impl Into<String>) -> Self {
59 Self::CommandExecution(msg.into())
60 }
61}
62
63pub trait ErrorContext<T> {
65 fn context(self, context: impl Into<String>) -> Result<T>;
67
68 fn with_context<F>(self, f: F) -> Result<T>
70 where
71 F: FnOnce() -> String;
72}
73
74impl<T, E> ErrorContext<T> for std::result::Result<T, E>
75where
76 E: std::error::Error + Send + Sync + 'static,
77{
78 fn context(self, context: impl Into<String>) -> Result<T> {
79 self.map_err(|e| CommonError::with_context(context, e))
80 }
81
82 fn with_context<F>(self, f: F) -> Result<T>
83 where
84 F: FnOnce() -> String,
85 {
86 self.map_err(|e| CommonError::with_context(f(), e))
87 }
88}
89
90pub trait OptionContext<T> {
92 fn context(self, context: impl Into<String>) -> Result<T>;
94}
95
96impl<T> OptionContext<T> for Option<T> {
97 fn context(self, context: impl Into<String>) -> Result<T> {
98 self.ok_or_else(|| CommonError::Other(anyhow::anyhow!(context.into())))
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_error_context() {
108 let result: std::result::Result<i32, std::io::Error> = Err(std::io::Error::new(
109 std::io::ErrorKind::NotFound,
110 "file not found",
111 ));
112
113 let with_context = result.context("Failed to read config file");
114 assert!(with_context.is_err());
115
116 let err = with_context.unwrap_err();
117 match err {
118 CommonError::WithContext { context, .. } => {
119 assert_eq!(context, "Failed to read config file");
120 }
121 _ => panic!("Expected WithContext error"),
122 }
123 }
124
125 #[test]
126 fn test_option_context() {
127 let value: Option<i32> = None;
128 let result = value.context("Value not found");
129
130 assert!(result.is_err());
131 let err = result.unwrap_err();
132 assert!(matches!(err, CommonError::Other(_)));
133 }
134}