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>;
119}
120
121impl<T, E: std::error::Error> ResultExt<T> for Result<T, E> {
122 fn with_context(self, context: &str) -> CommonResult<T> {
123 self.map_err(|e| CommonError::Custom(format!("{context}: {e}")))
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_error_creation() {
133 let err = CommonError::invalid_input("test");
134 assert!(err.is_input_error());
135 assert!(!err.is_recoverable());
136 }
137
138 #[test]
139 fn test_error_display() {
140 let err = CommonError::NotFound("file.txt".into());
141 assert_eq!(err.to_string(), "Not found: file.txt");
142 }
143
144 #[test]
145 fn test_result_ext() {
146 let result: Result<(), std::io::Error> =
147 Err(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
148 let common_result = result.with_context("Reading file");
149 assert!(common_result.is_err());
150 }
151}