Skip to main content

error_repr/
error.rs

1use crate::{
2    RawOsError,
3    kind::{ErrorKind, FromRawOsError},
4};
5
6#[cfg(feature = "alloc")]
7use alloc::boxed::Box;
8
9#[derive(Debug)]
10enum ErrorPayload {
11    Simple,
12    RawOsError(RawOsError),
13    Message(&'static str),
14    #[cfg(feature = "alloc")]
15    Error(Box<dyn core::error::Error + Send + Sync + 'static>),
16}
17
18/// Primary Error type from this crate.
19/// [`Error`] contains a kind and an optional payload, which may be an OS Error Code, a Custom Message, or (with an allocator available) a custom Error object.
20#[derive(Debug)]
21pub struct Error<K> {
22    kind: K,
23    payload: ErrorPayload,
24    #[cfg(feature = "error-track_caller")]
25    #[expect(dead_code, reason = "Intentionally Unused Field for debugging only")]
26    caller_location: &'static core::panic::Location<'static>, // intentionally unused field - used by the `Debug` impl
27}
28
29impl<K: ErrorKind> core::fmt::Display for Error<K> {
30    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31        core::fmt::Display::fmt(&self.kind, f)?;
32        match &self.payload {
33            ErrorPayload::Simple => Ok(()),
34            ErrorPayload::RawOsError(os) => f.write_fmt(format_args!(" (raw os error {os})")),
35            ErrorPayload::Message(m) => f.write_fmt(format_args!(": {m}")),
36            #[cfg(feature = "alloc")]
37            ErrorPayload::Error(error) => f.write_fmt(format_args!(": {error}")),
38        }
39    }
40}
41
42impl<K> Error<K> {
43    #[cfg_attr(feature = "error-track_caller", track_caller)]
44    const fn internal_new(kind: K, payload: ErrorPayload) -> Self {
45        Self {
46            kind,
47            payload,
48            #[cfg(feature = "error-track_caller")]
49            caller_location: core::panic::Location::caller(),
50        }
51    }
52
53    /// Constructs a new error with a kind, but not payload
54    #[cfg_attr(feature = "error-track_caller", track_caller)]
55    pub const fn new_simple(kind: K) -> Self {
56        Self::internal_new(kind, ErrorPayload::Simple)
57    }
58
59    /// Constructs a new error with a kind and a custom message
60    #[cfg_attr(feature = "error-track_caller", track_caller)]
61    pub const fn new_with_message(kind: K, msg: &'static str) -> Self {
62        Self::internal_new(kind, ErrorPayload::Message(msg))
63    }
64
65    /// Constructs a new error with a kind and a custom payload
66    #[cfg_attr(feature = "error-track_caller", track_caller)]
67    #[cfg(feature = "alloc")]
68    pub fn new<E: Into<Box<dyn core::error::Error + Send + Sync + 'static>>>(
69        kind: K,
70        error: E,
71    ) -> Self {
72        Self::internal_new(kind, ErrorPayload::Error(error.into()))
73    }
74
75    /// Returns the error Kind of the Error
76    pub const fn kind(&self) -> K
77    where
78        K: Copy,
79    {
80        self.kind
81    }
82
83    /// Returns the raw os error if the error was constructed with one (via. [`Error::from_raw_os_error`])
84    pub fn raw_os_error(&self) -> Option<RawOsError> {
85        match self.payload {
86            ErrorPayload::RawOsError(e) => Some(e),
87            _ => None,
88        }
89    }
90
91    /// Converts `self` into an boxed error if a custom payload is present.
92    /// [`Error::into_error`] will return [`Some`] if constructed with a custom payload ([`Error::new`], [`Error::other`], or [`Error::uncategorized`]),
93    /// or if constructed with a custom message ([`Error::new_with_message`], [`Error::other_with_message`], or [`Error::uncategorized_with_message`]), and [`None`] otherwise.
94    ///
95    /// Note that the latter case cannot be downcast.
96    #[cfg(feature = "alloc")]
97    pub fn into_error(self) -> Option<Box<dyn core::error::Error + Send + Sync + 'static>> {
98        match self.payload {
99            ErrorPayload::Error(payload) => Some(payload),
100            ErrorPayload::Message(m) => Some(m.into()),
101            _ => None,
102        }
103    }
104
105    /// Converts `self` into an boxed error if a custom payload is present.
106    /// [`Error::into_inner`] will return [`Some`] if constructed with a custom payload ([`Error::new`], [`Error::other`], or [`Error::uncategorized`]), and [`None`] otherwise.
107    #[cfg(feature = "alloc")]
108    pub fn into_inner(self) -> Option<Box<dyn core::error::Error + Send + Sync + 'static>> {
109        match self.payload {
110            ErrorPayload::Error(payload) => Some(payload),
111            _ => None,
112        }
113    }
114}
115
116impl<K: ErrorKind> Error<K> {
117
118    #[cfg_attr(feature = "error-track_caller", track_caller)]
119    const fn internal_other(payload: ErrorPayload) -> Self {
120        Self::internal_new(K::OTHER, payload)
121    }
122
123    /// Constructs a new [`Error`] with no payload that indicates an other error.
124    #[cfg_attr(feature = "error-track_caller", track_caller)]
125    pub const fn other_simple() -> Self {
126        Self::internal_other(ErrorPayload::Simple)
127    }
128
129    /// Constructs a new [`Error`] with a custom message that indicates an other error.
130    #[cfg_attr(feature = "error-track_caller", track_caller)]
131    pub const fn other_with_message(msg: &'static str) -> Self {
132        Self::internal_other(ErrorPayload::Message(msg))
133    }
134
135    /// Constructs a new [`Error`] with a custom payload that indicates an other error.
136    #[cfg_attr(feature = "error-track_caller", track_caller)]
137    #[cfg(feature = "alloc")]
138    pub fn other<E: Into<Box<dyn core::error::Error + Send + Sync + 'static>>>(error: E) -> Self {
139        Self::internal_other(ErrorPayload::Error(error.into()))
140    }
141
142    #[cfg_attr(feature = "error-track_caller", track_caller)]
143    fn internal_uncategorized(payload: ErrorPayload) -> Self {
144        Self::internal_new(K::uncategorized(), payload)
145    }
146
147    /// Constructs a new [`Error`] with no payload that indicates an uncategorized error.
148    #[cfg_attr(feature = "error-track_caller", track_caller)]
149    pub fn uncategorized_simple() -> Self {
150        Self::internal_uncategorized(ErrorPayload::Simple)
151    }
152
153    /// Constructs a new [`Error`] with a custom message that indicates an uncategorized error.
154    #[cfg_attr(feature = "error-track_caller", track_caller)]
155    pub fn uncategorized_with_message(msg: &'static str) -> Self {
156        Self::internal_uncategorized(ErrorPayload::Message(msg))
157    }
158
159    /// Constructs a new [`Error`] with a custom payload that indicates an uncategorized error.
160    #[cfg_attr(feature = "error-track_caller", track_caller)]
161    #[cfg(feature = "alloc")]
162    pub fn uncategorized<E: Into<Box<dyn core::error::Error + Send + Sync + 'static>>>(
163        error: E,
164    ) -> Self {
165        Self::internal_uncategorized(ErrorPayload::Error(error.into()))
166    }
167}
168
169impl<K: FromRawOsError> Error<K> {
170    /// Constructs a new [`Error`] that contains an Error created from the OS
171    #[cfg_attr(feature = "error-track_caller", track_caller)]
172    pub fn from_raw_os_error(error: RawOsError) -> Self {
173        Self::internal_new(K::from_raw_os_error(error), ErrorPayload::RawOsError(error))
174    }
175}
176
177impl<K: ErrorKind> core::error::Error for Error<K> {}
178
179#[cfg(feature = "std")]
180impl<K: crate::kind::FromIoKind> From<std::io::ErrorKind> for Error<K> {
181    #[cfg_attr(feature = "error-track_caller", track_caller)]
182    fn from(kind: std::io::ErrorKind) -> Self {
183        Error::new_simple(K::from_io_error_kind(kind))
184    }
185}
186
187#[cfg(feature = "std")]
188impl<K: crate::kind::FromIoKind> From<std::io::Error> for Error<K> {
189    #[cfg_attr(feature = "error-track_caller", track_caller)]
190    fn from(value: std::io::Error) -> Self {
191        let kind = K::from_io_error_kind(value.kind());
192        if let Some(code) = value.raw_os_error() {
193            Self::internal_new(kind, ErrorPayload::RawOsError(code))
194        } else if let Some(data) = value.into_inner() {
195            Self::internal_new(kind, ErrorPayload::Error(data))
196        } else {
197            Self::internal_new(kind, ErrorPayload::Simple)
198        }
199    }
200}
201
202#[cfg(feature = "std")]
203impl<K: crate::kind::IntoIoKind> From<Error<K>> for std::io::Error {
204    fn from(value: Error<K>) -> Self {
205        if let Some(code) = value.raw_os_error() {
206            Self::from_raw_os_error(code)
207        } else {
208            let kind = K::into_io_error_kind(value.kind());
209
210            if let Some(payload) = value.into_error() {
211                std::io::Error::new(kind, payload)
212            } else {
213                std::io::Error::new(kind, "(no context)")
214            }
215        }
216    }
217}