1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#![doc = include_str!("../README.md")]

use std::any::TypeId;
use std::fmt::{Debug, Display, Formatter};
use std::mem::ManuallyDrop;
use std::ptr;

pub use conerror_macro::conerror;

pub type Result<T> = std::result::Result<T, Error>;

/// Error with location information
pub struct Error(Box<Inner>);

impl Error {
    /// Create an [Error] with location information.
    pub fn new<T>(
        error: T,
        file: &'static str,
        line: u32,
        func: &'static str,
        module: &'static str,
    ) -> Self
    where
        T: Into<Box<dyn std::error::Error + Send + Sync>>,
    {
        Self(Box::new(Inner {
            source: error.into(),
            location: Some(vec![Location {
                file,
                line,
                func,
                module,
            }]),
        }))
    }

    /// Create an [Error] without location information
    pub fn plain<T>(error: T) -> Self
    where
        T: Into<Box<dyn std::error::Error + Send + Sync>>,
    {
        Self(Box::new(Inner {
            source: error.into(),
            location: None,
        }))
    }

    /// Same as [Error::new] if `error` is not of type [Error],
    /// otherwise add location information to `error` if not created by [Error::plain]
    pub fn chain<T>(
        error: T,
        file: &'static str,
        line: u32,
        func: &'static str,
        module: &'static str,
    ) -> Self
    where
        T: std::error::Error + Send + Sync + 'static,
    {
        if TypeId::of::<T>() == TypeId::of::<Self>() {
            let error = ManuallyDrop::new(error);
            // SAFETY: type checked
            let mut error = unsafe { ptr::read(&error as *const _ as *const Self) };
            if let Some(ref mut location) = error.0.location {
                location.push(Location {
                    file,
                    line,
                    func,
                    module,
                });
            }
            return error;
        }

        Self::new(error, file, line, func, module)
    }

    /// Return the location information
    pub fn location(&self) -> Option<&[Location]> {
        self.0.location.as_ref().map(|v| v.as_slice())
    }
}

impl Debug for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Error")
            .field("source", &self.0.source)
            .field("location", &self.0.location)
            .finish()
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.0.source, f)?;
        if let Some(ref location) = self.0.location {
            for (i, v) in location.iter().enumerate() {
                write!(f, "\n#{} {}", i, v)?;
            }
        }
        Ok(())
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&*self.0.source)
    }
}

struct Inner {
    source: Box<dyn std::error::Error + Send + Sync>,
    location: Option<Vec<Location>>,
}

#[derive(Debug)]
pub struct Location {
    pub file: &'static str,
    pub line: u32,
    pub func: &'static str,
    /// module path for function, struct name for method
    pub module: &'static str,
}

impl Display for Location {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}:{} {}::{}()",
            self.file, self.line, self.module, self.func
        )
    }
}