Skip to main content

commons/
error.rs

1//! Common error types and handling utilities.
2//!
3//! This module provides a unified error type for use across ecosystem projects,
4//! along with convenient Result type aliases.
5//!
6//! # Example
7//!
8//! ```rust
9//! use commons::error::{CommonError, CommonResult};
10//!
11//! fn process_data(input: &str) -> CommonResult<String> {
12//!     if input.is_empty() {
13//!         return Err(CommonError::InvalidInput("Input cannot be empty".into()));
14//!     }
15//!     Ok(input.to_uppercase())
16//! }
17//! ```
18
19use thiserror::Error;
20
21/// Common error type for ecosystem projects.
22///
23/// This enum covers the most common error cases encountered across projects.
24/// For project-specific errors, consider wrapping this or creating derived types.
25#[derive(Error, Debug)]
26pub enum CommonError {
27    /// Invalid input provided to a function.
28    #[error("Invalid input: {0}")]
29    InvalidInput(String),
30
31    /// Configuration error.
32    #[error("Configuration error: {0}")]
33    Config(String),
34
35    /// IO operation failed.
36    #[error("IO error: {0}")]
37    Io(#[from] std::io::Error),
38
39    /// Parse error for various formats.
40    #[error("Parse error: {0}")]
41    Parse(String),
42
43    /// Resource not found.
44    #[error("Not found: {0}")]
45    NotFound(String),
46
47    /// Operation not permitted.
48    #[error("Permission denied: {0}")]
49    PermissionDenied(String),
50
51    /// Operation timed out.
52    #[error("Timeout: {0}")]
53    Timeout(String),
54
55    /// External service error.
56    #[error("External error: {0}")]
57    External(String),
58
59    /// Generic error with custom message.
60    #[error("{0}")]
61    Custom(String),
62}
63
64/// Result type alias using [`CommonError`].
65pub type CommonResult<T> = Result<T, CommonError>;
66
67impl CommonError {
68    /// Create a new invalid input error.
69    #[must_use]
70    pub fn invalid_input(msg: impl Into<String>) -> Self {
71        Self::InvalidInput(msg.into())
72    }
73
74    /// Create a new configuration error.
75    #[must_use]
76    pub fn config(msg: impl Into<String>) -> Self {
77        Self::Config(msg.into())
78    }
79
80    /// Create a new parse error.
81    #[must_use]
82    pub fn parse(msg: impl Into<String>) -> Self {
83        Self::Parse(msg.into())
84    }
85
86    /// Create a new not found error.
87    #[must_use]
88    pub fn not_found(msg: impl Into<String>) -> Self {
89        Self::NotFound(msg.into())
90    }
91
92    /// Create a new custom error.
93    #[must_use]
94    pub fn custom(msg: impl Into<String>) -> Self {
95        Self::Custom(msg.into())
96    }
97
98    /// Check if this is an input validation error.
99    #[must_use]
100    pub const fn is_input_error(&self) -> bool {
101        matches!(self, Self::InvalidInput(_) | Self::Parse(_))
102    }
103
104    /// Check if this is a recoverable error.
105    #[must_use]
106    pub const fn is_recoverable(&self) -> bool {
107        matches!(self, Self::Timeout(_) | Self::External(_))
108    }
109}
110
111/// Extension trait for Result types.
112pub trait ResultExt<T> {
113    /// Convert any error to a `CommonError` with context.
114    ///
115    /// # Errors
116    ///
117    /// Returns an error if the underlying result is an error.
118    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}