1use thiserror::Error;
20
21#[derive(Error, Debug)]
26pub enum CommonError {
27 #[error("Invalid input: {0}")]
29 InvalidInput(String),
30
31 #[error("Configuration error: {0}")]
33 Config(String),
34
35 #[error("IO error: {0}")]
37 Io(#[from] std::io::Error),
38
39 #[error("Parse error: {0}")]
41 Parse(String),
42
43 #[error("Not found: {0}")]
45 NotFound(String),
46
47 #[error("Permission denied: {0}")]
49 PermissionDenied(String),
50
51 #[error("Timeout: {0}")]
53 Timeout(String),
54
55 #[error("External error: {0}")]
57 External(String),
58
59 #[error("{0}")]
61 Custom(String),
62}
63
64pub type CommonResult<T> = Result<T, CommonError>;
66
67impl CommonError {
68 #[must_use]
70 pub fn invalid_input(msg: impl Into<String>) -> Self {
71 Self::InvalidInput(msg.into())
72 }
73
74 #[must_use]
76 pub fn config(msg: impl Into<String>) -> Self {
77 Self::Config(msg.into())
78 }
79
80 #[must_use]
82 pub fn parse(msg: impl Into<String>) -> Self {
83 Self::Parse(msg.into())
84 }
85
86 #[must_use]
88 pub fn not_found(msg: impl Into<String>) -> Self {
89 Self::NotFound(msg.into())
90 }
91
92 #[must_use]
94 pub fn custom(msg: impl Into<String>) -> Self {
95 Self::Custom(msg.into())
96 }
97
98 #[must_use]
100 pub const fn is_input_error(&self) -> bool {
101 matches!(self, Self::InvalidInput(_) | Self::Parse(_))
102 }
103
104 #[must_use]
106 pub const fn is_recoverable(&self) -> bool {
107 matches!(self, Self::Timeout(_) | Self::External(_))
108 }
109}
110
111pub trait ResultExt<T> {
113 fn with_context(self, context: &str) -> CommonResult<T>;
115}
116
117impl<T, E: std::error::Error> ResultExt<T> for Result<T, E> {
118 fn with_context(self, context: &str) -> CommonResult<T> {
119 self.map_err(|e| CommonError::Custom(format!("{context}: {e}")))
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_error_creation() {
129 let err = CommonError::invalid_input("test");
130 assert!(err.is_input_error());
131 assert!(!err.is_recoverable());
132 }
133
134 #[test]
135 fn test_error_display() {
136 let err = CommonError::NotFound("file.txt".into());
137 assert_eq!(err.to_string(), "Not found: file.txt");
138 }
139
140 #[test]
141 fn test_result_ext() {
142 let result: Result<(), std::io::Error> =
143 Err(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
144 let common_result = result.with_context("Reading file");
145 assert!(common_result.is_err());
146 }
147}