Skip to main content

avila_error/
lib.rs

1//! Avila Error - AVL Platform error handling
2//! Replacement for anyhow/thiserror - 100% Rust std
3//!
4//! # Features
5//! - `derive`: Enable #[derive(Error)] macro
6//! - `context`: Enable Context trait for anyhow-style error handling
7//! - `full`: Enable all features
8
9#[cfg(feature = "derive")]
10pub use avila_error_derive::Error as ErrorDerive;
11
12use std::fmt;
13use std::error::Error as StdError;
14
15/// Generic error type for AVL Platform
16#[derive(Debug)]
17pub struct Error {
18    kind: ErrorKind,
19    message: String,
20    source: Option<Box<dyn StdError + Send + Sync>>,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ErrorKind {
25    Io,
26    Parse,
27    Network,
28    Database,
29    Auth,
30    NotFound,
31    InvalidInput,
32    Internal,
33    Tls,
34    Serialization,
35    Other,
36}
37
38impl Error {
39    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
40        Self {
41            kind,
42            message: message.into(),
43            source: None,
44        }
45    }
46
47    pub fn with_source<E>(mut self, source: E) -> Self
48    where
49        E: StdError + Send + Sync + 'static,
50    {
51        self.source = Some(Box::new(source));
52        self
53    }
54
55    pub fn kind(&self) -> ErrorKind {
56        self.kind
57    }
58
59    // Convenience constructors
60    pub fn io(message: impl Into<String>) -> Self {
61        Self::new(ErrorKind::Io, message)
62    }
63
64    pub fn parse(message: impl Into<String>) -> Self {
65        Self::new(ErrorKind::Parse, message)
66    }
67
68    pub fn network(message: impl Into<String>) -> Self {
69        Self::new(ErrorKind::Network, message)
70    }
71
72    pub fn database(message: impl Into<String>) -> Self {
73        Self::new(ErrorKind::Database, message)
74    }
75
76    pub fn auth(message: impl Into<String>) -> Self {
77        Self::new(ErrorKind::Auth, message)
78    }
79
80    pub fn not_found(message: impl Into<String>) -> Self {
81        Self::new(ErrorKind::NotFound, message)
82    }
83
84    pub fn invalid_input(message: impl Into<String>) -> Self {
85        Self::new(ErrorKind::InvalidInput, message)
86    }
87
88    pub fn internal(message: impl Into<String>) -> Self {
89        Self::new(ErrorKind::Internal, message)
90    }
91}
92
93impl fmt::Display for Error {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "{}", self.message)?;
96        if let Some(ref source) = self.source {
97            write!(f, ": {}", source)?;
98        }
99        Ok(())
100    }
101}
102
103impl StdError for Error {
104    fn source(&self) -> Option<&(dyn StdError + 'static)> {
105        self.source
106            .as_ref()
107            .map(|e| e.as_ref() as &(dyn StdError + 'static))
108    }
109}
110
111// Convert from std::io::Error
112impl From<std::io::Error> for Error {
113    fn from(err: std::io::Error) -> Self {
114        Error::io(err.to_string()).with_source(err)
115    }
116}
117
118// Convert from String
119impl From<String> for Error {
120    fn from(msg: String) -> Self {
121        Error::new(ErrorKind::Other, msg)
122    }
123}
124
125impl From<&str> for Error {
126    fn from(msg: &str) -> Self {
127        Error::new(ErrorKind::Other, msg)
128    }
129}
130
131/// Result type using our Error
132pub type Result<T> = std::result::Result<T, Error>;
133
134/// Macro para criar erros facilmente (similar ao anyhow!)
135#[macro_export]
136macro_rules! bail {
137    ($msg:expr) => {
138        return Err($crate::Error::new($crate::ErrorKind::Other, $msg))
139    };
140    ($kind:expr, $msg:expr) => {
141        return Err($crate::Error::new($kind, $msg))
142    };
143}
144
145/// Macro para garantir condições (similar ao ensure!)
146#[macro_export]
147macro_rules! ensure {
148    ($cond:expr, $msg:expr) => {
149        if !$cond {
150            $crate::bail!($msg);
151        }
152    };
153}
154
155// ============================================================================
156// Context trait (feature = "context") - anyhow-style error handling
157// ============================================================================
158
159#[cfg(feature = "context")]
160pub trait Context<T, E> {
161    /// Adiciona contexto ao erro
162    fn context<C>(self, context: C) -> Result<T>
163    where
164        C: fmt::Display + Send + Sync + 'static;
165
166    /// Adiciona contexto lazy ao erro
167    fn with_context<C, F>(self, f: F) -> Result<T>
168    where
169        C: fmt::Display + Send + Sync + 'static,
170        F: FnOnce() -> C;
171}
172
173#[cfg(feature = "context")]
174impl<T, E> Context<T, E> for std::result::Result<T, E>
175where
176    E: StdError + Send + Sync + 'static,
177{
178    fn context<C>(self, context: C) -> Result<T>
179    where
180        C: fmt::Display + Send + Sync + 'static,
181    {
182        self.map_err(|error| {
183            let mut err = Error::new(ErrorKind::Other, context.to_string());
184            err.source = Some(Box::new(error));
185            err
186        })
187    }
188
189    fn with_context<C, F>(self, f: F) -> Result<T>
190    where
191        C: fmt::Display + Send + Sync + 'static,
192        F: FnOnce() -> C,
193    {
194        self.map_err(|error| {
195            let context = f();
196            let mut err = Error::new(ErrorKind::Other, context.to_string());
197            err.source = Some(Box::new(error));
198            err
199        })
200    }
201}
202
203#[cfg(feature = "context")]
204impl<T> Context<T, Error> for Option<T> {
205    fn context<C>(self, context: C) -> Result<T>
206    where
207        C: fmt::Display + Send + Sync + 'static,
208    {
209        self.ok_or_else(|| Error::new(ErrorKind::NotFound, context.to_string()))
210    }
211
212    fn with_context<C, F>(self, f: F) -> Result<T>
213    where
214        C: fmt::Display + Send + Sync + 'static,
215        F: FnOnce() -> C,
216    {
217        self.ok_or_else(|| {
218            let context = f();
219            Error::new(ErrorKind::NotFound, context.to_string())
220        })
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn test_error_creation() {
230        let err = Error::not_found("Item not found");
231        assert_eq!(err.kind(), ErrorKind::NotFound);
232        assert_eq!(err.to_string(), "Item not found");
233    }
234
235    #[test]
236    fn test_error_with_source() {
237        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
238        let err = Error::io("Failed to read file").with_source(io_err);
239        assert!(err.source().is_some());
240    }
241}