1#[cfg(feature = "derive")]
10pub use avila_error_derive::Error as ErrorDerive;
11
12use std::fmt;
13use std::error::Error as StdError;
14
15#[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 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
111impl 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
118impl 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
131pub type Result<T> = std::result::Result<T, Error>;
133
134#[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_export]
147macro_rules! ensure {
148 ($cond:expr, $msg:expr) => {
149 if !$cond {
150 $crate::bail!($msg);
151 }
152 };
153}
154
155#[cfg(feature = "context")]
160pub trait Context<T, E> {
161 fn context<C>(self, context: C) -> Result<T>
163 where
164 C: fmt::Display + Send + Sync + 'static;
165
166 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}