conerror/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::any::TypeId;
4use std::fmt::{Debug, Display, Formatter};
5use std::mem::ManuallyDrop;
6use std::ptr;
7
8pub use conerror_macro::conerror;
9
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Represents an error with additional location information.
13pub struct Error(Box<Inner>);
14
15impl Error {
16    /// Creates a new [Error] with location information.
17    ///
18    /// # Parameters
19    ///
20    /// - `error`: The error to wrap.
21    /// - `file`: The file where the error occurred.
22    /// - `line`: The line number where the error occurred.
23    /// - `func`: The function where the error occurred.
24    /// - `module`: The module where the error occurred.
25    pub fn new<T>(
26        error: T,
27        file: &'static str,
28        line: u32,
29        func: &'static str,
30        module: &'static str,
31    ) -> Self
32    where
33        T: Into<Box<dyn std::error::Error + Send + Sync>>,
34    {
35        Self(Box::new(Inner {
36            source: error.into(),
37            location: Some(vec![Location {
38                file,
39                line,
40                func,
41                module,
42            }]),
43            context: Vec::new(),
44        }))
45    }
46
47    /// Creates a new [Error] without location information.
48    pub fn plain<T>(error: T) -> Self
49    where
50        T: Into<Box<dyn std::error::Error + Send + Sync>>,
51    {
52        Self(Box::new(Inner {
53            source: error.into(),
54            location: None,
55            context: Vec::new(),
56        }))
57    }
58
59    /// Chains an error with additional location information.
60    ///
61    /// If the provided error is not of type [Error], it creates a new [Error] with location information.
62    /// If the provided error is of type [Error], it adds the location information if it was not created by [Error::plain].
63    ///
64    /// # Parameters
65    ///
66    /// - `error`: The error to wrap.
67    /// - `file`: The file where the error occurred.
68    /// - `line`: The line number where the error occurred.
69    /// - `func`: The function where the error occurred.
70    /// - `module`: The module where the error occurred.
71    pub fn chain<T>(
72        error: T,
73        file: &'static str,
74        line: u32,
75        func: &'static str,
76        module: &'static str,
77    ) -> Self
78    where
79        T: std::error::Error + Send + Sync + 'static,
80    {
81        if TypeId::of::<T>() == TypeId::of::<Self>() {
82            let error = ManuallyDrop::new(error);
83            // SAFETY: type checked
84            let mut error = unsafe { ptr::read(&error as *const _ as *const Self) };
85            if let Some(ref mut location) = error.0.location {
86                location.push(Location {
87                    file,
88                    line,
89                    func,
90                    module,
91                });
92            }
93            return error;
94        }
95
96        Self::new(error, file, line, func, module)
97    }
98
99    pub fn context(mut self, context: impl ToString) -> Self {
100        self.0.context.push(context.to_string());
101        self
102    }
103
104    /// Returns the location information.
105    pub fn location(&self) -> Option<&[Location]> {
106        self.0.location.as_ref().map(|v| v.as_slice())
107    }
108}
109
110impl Debug for Error {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        f.debug_struct("Error")
113            .field("source", &self.0.source)
114            .field("location", &self.0.location)
115            .field("context", &self.0.context)
116            .finish()
117    }
118}
119
120impl Display for Error {
121    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
122        for c in self.0.context.iter().rev() {
123            write!(f, "{}: ", c)?;
124        }
125        Display::fmt(&self.0.source, f)?;
126        if let Some(ref location) = self.0.location {
127            for (i, v) in location.iter().enumerate() {
128                write!(f, "\n#{} {}", i, v)?;
129            }
130        }
131        Ok(())
132    }
133}
134
135impl std::error::Error for Error {
136    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
137        Some(&*self.0.source)
138    }
139}
140
141struct Inner {
142    source: Box<dyn std::error::Error + Send + Sync>,
143    location: Option<Vec<Location>>,
144    context: Vec<String>,
145}
146
147/// Represents the location where an error occurred.
148#[derive(Debug)]
149pub struct Location {
150    pub file: &'static str,
151    pub line: u32,
152    pub func: &'static str,
153    /// Module path for function, struct/trait name for method.
154    pub module: &'static str,
155}
156
157impl Display for Location {
158    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
159        write!(
160            f,
161            "{}:{} {}::{}()",
162            self.file, self.line, self.module, self.func
163        )
164    }
165}