1#![cfg_attr(doc, doc = include_str!("../README.md"))]
2#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5#[cfg(feature = "alloc")]
6extern crate alloc;
7
8#[cfg(all(feature = "alloc", not(feature = "std")))]
9use alloc::boxed::Box;
10use core::fmt;
11
12pub trait Source: core::error::Error + Send + Sync + 'static {}
13
14impl<T> Source for T where T: core::error::Error + Send + Sync + 'static {}
15
16#[cfg(feature = "alloc")]
23struct ErrorMeta {
24 context: &'static str,
25 location: &'static core::panic::Location<'static>,
26 source: Option<Box<dyn Source>>,
27}
28
29pub struct Error<Kind> {
39 kind: Kind,
40 #[cfg(feature = "alloc")]
42 meta: Box<ErrorMeta>,
43 #[cfg(not(feature = "alloc"))]
45 context: &'static str,
46 #[cfg(not(feature = "alloc"))]
47 location: &'static core::panic::Location<'static>,
48}
49
50impl<Kind: fmt::Debug> fmt::Debug for Error<Kind> {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 let mut dbg = f.debug_struct("Error");
58 #[cfg(feature = "alloc")]
59 dbg.field("context", &self.meta.context)
60 .field("kind", &self.kind)
61 .field("source", &self.meta.source);
62 #[cfg(not(feature = "alloc"))]
63 dbg.field("context", &self.context).field("kind", &self.kind);
64 dbg.finish()
65 }
66}
67
68impl<Kind> Error<Kind> {
69 #[cold]
70 #[must_use]
71 #[track_caller]
72 pub fn new(context: &'static str, kind: Kind) -> Self {
73 Self {
74 kind,
75 #[cfg(feature = "alloc")]
76 meta: Box::new(ErrorMeta {
77 context,
78 location: core::panic::Location::caller(),
79 source: None,
80 }),
81 #[cfg(not(feature = "alloc"))]
82 context,
83 #[cfg(not(feature = "alloc"))]
84 location: core::panic::Location::caller(),
85 }
86 }
87
88 #[cold]
89 #[must_use]
90 pub fn with_source<E>(self, source: E) -> Self
91 where
92 E: Source,
93 {
94 #[cfg(feature = "alloc")]
95 {
96 let mut this = self;
97 this.meta.source = Some(Box::new(source));
98 this
99 }
100
101 #[cfg(not(feature = "alloc"))]
103 {
104 let _ = source;
105 self
106 }
107 }
108
109 pub fn kind(&self) -> &Kind {
110 &self.kind
111 }
112
113 pub fn location(&self) -> &'static core::panic::Location<'static> {
119 #[cfg(feature = "alloc")]
120 {
121 self.meta.location
122 }
123 #[cfg(not(feature = "alloc"))]
124 {
125 self.location
126 }
127 }
128
129 pub fn set_context(&mut self, context: &'static str) {
130 #[cfg(feature = "alloc")]
131 {
132 self.meta.context = context;
133 }
134 #[cfg(not(feature = "alloc"))]
135 {
136 self.context = context;
137 }
138 }
139
140 pub fn report(&self) -> ErrorReport<'_, Kind> {
141 ErrorReport(self)
142 }
143}
144
145impl<Kind> fmt::Display for Error<Kind>
146where
147 Kind: fmt::Display,
148{
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 #[cfg(feature = "alloc")]
151 {
152 write!(
153 f,
154 "[{} @ {}:{}] {}",
155 self.meta.context,
156 self.meta.location.file(),
157 self.meta.location.line(),
158 self.kind
159 )
160 }
161 #[cfg(not(feature = "alloc"))]
162 {
163 write!(
164 f,
165 "[{} @ {}:{}] {}",
166 self.context,
167 self.location.file(),
168 self.location.line(),
169 self.kind
170 )
171 }
172 }
173}
174
175#[cfg(feature = "std")]
176impl<Kind> core::error::Error for Error<Kind>
177where
178 Kind: core::error::Error,
179{
180 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
181 if let Some(source) = self.kind.source() {
182 Some(source)
183 } else {
184 if let Some(e) = &self.meta.source {
186 Some(e.as_ref())
187 } else {
188 None
189 }
190 }
191 }
192}
193
194#[cfg(feature = "std")]
195impl<Kind> From<Error<Kind>> for std::io::Error
196where
197 Kind: core::error::Error + Send + Sync + 'static,
198{
199 fn from(error: Error<Kind>) -> Self {
200 Self::other(error)
201 }
202}
203
204pub struct ErrorReport<'a, Kind>(&'a Error<Kind>);
205
206#[cfg(feature = "std")]
207impl<Kind> fmt::Display for ErrorReport<'_, Kind>
208where
209 Kind: core::error::Error,
210{
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 use core::error::Error as _;
213
214 write!(f, "{}", self.0)?;
215
216 let mut next_source = self.0.source();
217
218 while let Some(e) = next_source {
219 write!(f, ", caused by: {e}")?;
220 next_source = e.source();
221 }
222
223 Ok(())
224 }
225}
226
227#[cfg(not(feature = "std"))]
228impl<E> fmt::Display for ErrorReport<'_, E>
229where
230 E: fmt::Display,
231{
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 write!(f, "{}", self.0)?;
234
235 #[cfg(feature = "alloc")]
236 if let Some(source) = &self.0.meta.source {
237 write!(f, ", caused by: {source}")?;
238 }
239
240 Ok(())
241 }
242}
243
244#[macro_export]
260macro_rules! bail {
261 ($kind:expr $(,)?) => {
262 return ::core::result::Result::Err($crate::Error::new("", $kind))
263 };
264 ($context:expr, $kind:expr $(,)?) => {
265 return ::core::result::Result::Err($crate::Error::new($context, $kind))
266 };
267 ($context:expr, $kind:expr, source: $source:expr $(,)?) => {
268 return ::core::result::Result::Err($crate::Error::new($context, $kind).with_source($source))
269 };
270}
271
272#[macro_export]
287macro_rules! ensure {
288 ($condition:expr, $kind:expr $(,)?) => {
289 if !($condition) {
290 return ::core::result::Result::Err($crate::Error::new("", $kind));
291 }
292 };
293 ($condition:expr, $context:expr, $kind:expr $(,)?) => {
294 if !($condition) {
295 return ::core::result::Result::Err($crate::Error::new($context, $kind));
296 }
297 };
298}