1use std::backtrace::{Backtrace, BacktraceStatus};
5use std::borrow::Cow;
6use std::error::Error as StdError;
7use std::fmt;
8
9use super::source::Source;
10use super::trace_info::TraceInfo;
11
12#[derive(Debug)]
14pub struct Inner {
15 pub(super) source: Source,
16 pub(super) backtrace: Backtrace,
17 pub(super) context: Vec<TraceInfo>,
18}
19
20pub struct OhnoCore {
46 pub(super) data: Box<Inner>,
47}
48
49impl OhnoCore {
50 #[must_use]
60 pub fn new() -> Self {
61 Self::from_source(Source::None)
62 }
63
64 pub fn without_backtrace(error: impl Into<Box<dyn StdError + Send + Sync + 'static>>) -> Self {
80 Self {
81 data: Box::new(Inner {
82 source: Source::Error(error.into()),
83 backtrace: Backtrace::disabled(),
84 context: Vec::new(),
85 }),
86 }
87 }
88
89 fn from_source(source: Source) -> Self {
90 Self {
91 data: Box::new(Inner {
92 source,
93 backtrace: Backtrace::capture(),
94 context: Vec::new(),
95 }),
96 }
97 }
98
99 #[must_use]
114 pub fn source(&self) -> Option<&(dyn StdError + 'static)> {
115 match &self.data.source {
116 Source::Error(source) => Some(source.as_ref()),
117 Source::Transparent(source) => source.source(),
118 Source::None => None,
119 }
120 }
121
122 #[must_use]
134 pub fn has_backtrace(&self) -> bool {
135 matches!(self.data.backtrace.status(), BacktraceStatus::Captured)
136 }
137
138 pub fn backtrace(&self) -> &Backtrace {
143 &self.data.backtrace
144 }
145
146 pub fn context_iter(&self) -> impl Iterator<Item = &TraceInfo> {
148 self.data.context.iter().rev()
149 }
150
151 pub fn context_messages(&self) -> impl Iterator<Item = &str> {
153 self.data.context.iter().rev().map(|ctx| ctx.message.as_ref())
154 }
155
156 #[must_use]
158 pub fn format_message(&self, default_message: &str, override_message: Option<Cow<'_, str>>) -> String {
159 MessageFormatter {
160 core: self,
161 default_message,
162 override_message,
163 }
164 .to_string()
165 }
166
167 pub fn format_error(&self, f: &mut fmt::Formatter<'_>, default_message: &str, override_message: Option<Cow<'_, str>>) -> fmt::Result {
176 let m = MessageFormatter {
177 core: self,
178 default_message,
179 override_message,
180 };
181
182 std::fmt::Display::fmt(&m, f)?;
183
184 for ctx in &self.data.context {
185 write!(f, "\n> {ctx}")?;
186 }
187
188 if matches!(self.data.backtrace.status(), BacktraceStatus::Captured) {
189 write!(f, "\n\nBacktrace:\n{}", self.data.backtrace)?;
190 }
191
192 Ok(())
193 }
194}
195
196impl std::fmt::Debug for OhnoCore {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 f.debug_struct("OhnoCore")
199 .field("source", &self.data.source)
200 .field("backtrace", &self.data.backtrace)
201 .field("context", &self.data.context)
202 .finish()
203 }
204}
205
206impl fmt::Display for OhnoCore {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 self.format_error(f, "", None)
209 }
210}
211
212impl Default for OhnoCore {
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218impl<T> From<T> for OhnoCore
219where
220 T: Into<Box<dyn StdError + Send + Sync>>,
221{
222 fn from(value: T) -> Self {
223 if is_string_error(&value) {
225 Self::from_source(Source::Transparent(value.into()))
226 } else {
227 Self::from_source(Source::Error(value.into()))
228 }
229 }
230}
231
232const STR_TYPE_IDS: [typeid::ConstTypeId; 3] = [
233 typeid::ConstTypeId::of::<&str>(),
234 typeid::ConstTypeId::of::<String>(),
235 typeid::ConstTypeId::of::<Cow<'_, str>>(),
236];
237
238fn is_string_error<T>(_: &T) -> bool {
239 let typeid_of_t = typeid::of::<T>();
240 STR_TYPE_IDS.iter().any(|&id| id == typeid_of_t)
241}
242
243struct MessageFormatter<'a> {
245 core: &'a OhnoCore,
246 default_message: &'a str,
247 override_message: Option<Cow<'a, str>>,
248}
249
250impl fmt::Display for MessageFormatter<'_> {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 const CAUSED_BY: &str = "caused by:";
253
254 let MessageFormatter {
255 core,
256 default_message,
257 override_message,
258 } = self;
259
260 match (override_message, &core.data.source) {
261 (Some(msg), Source::Transparent(source) | Source::Error(source)) => {
262 write!(f, "{msg}\n{CAUSED_BY} {source}")
263 }
264 (Some(msg), Source::None) => write!(f, "{msg}"),
265 (None, Source::Transparent(source) | Source::Error(source)) => write!(f, "{source}"),
266 (None, Source::None) => write!(f, "{default_message}"),
267 }
268 }
269}
270
271#[cfg_attr(coverage_nightly, coverage(off))]
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::error_trace::ErrorTrace;
276
277 #[test]
278 fn test_default() {
279 let error = OhnoCore::default();
280 assert!(matches!(error.data.source, Source::None));
281 }
282
283 #[test]
284 fn test_format_error() {
285 let error = OhnoCore::from("test error");
286 let result = error.to_string();
287 assert!(result.contains("test error"));
288 }
289
290 #[test]
291 fn test_new() {
292 let error = OhnoCore::new();
293 assert!(matches!(error.data.source, Source::None));
294 assert!(error.data.context.is_empty());
295 }
296
297 #[test]
298 fn test_from_string() {
299 let error = OhnoCore::from("msg");
300 assert!(error.source().is_none());
301 if let Source::Transparent(source) = &error.data.source {
302 assert_eq!(source.to_string(), "msg");
303 }
304 assert!(matches!(&error.data.source, Source::Transparent(_)), "expected transparent source");
305 }
306
307 #[test]
308 fn test_caused_by_without_backtrace() {
309 let io_error = std::io::Error::other("io error");
310 let error = OhnoCore::without_backtrace(io_error);
311 assert!(matches!(error.data.source, Source::Error(_)));
312 assert!(!error.has_backtrace());
313 assert!(error.source().unwrap().downcast_ref::<std::io::Error>().is_some());
314 }
315
316 #[test]
317 fn test_caused_by() {
318 let io_error = std::io::Error::other("io error");
319 let error = OhnoCore::from(io_error);
320 assert!(matches!(error.data.source, Source::Error(_)));
321 assert!(error.source().unwrap().downcast_ref::<std::io::Error>().is_some());
322 }
323
324 #[test]
325 fn test_from_boxed_error() {
326 let io_error = std::io::Error::other("io error");
327 let boxed: Box<dyn StdError + Send + Sync> = Box::new(io_error);
328 let error = OhnoCore::from(boxed);
329 assert!(matches!(error.data.source, Source::Error(_)));
330 assert!(error.source().unwrap().downcast_ref::<std::io::Error>().is_some());
331 }
332
333 #[test]
334 fn test_from_boxed_error_2() {
335 let io_error = std::io::Error::other("io error");
336 let boxed: Box<dyn StdError + Send + Sync> = Box::new(io_error);
337 let error: OhnoCore = boxed.into();
338 assert!(matches!(error.data.source, Source::Error(_)));
339 assert!(error.source().unwrap().downcast_ref::<std::io::Error>().is_some());
340 }
341
342 #[test]
343 fn test_context_iter_and_messages() {
344 let mut error = OhnoCore::from("msg");
345 error.add_error_trace(TraceInfo::new("ctx1"));
346 error.add_error_trace(TraceInfo::new("ctx2"));
347 let messages: Vec<_> = error.context_messages().collect();
348 assert_eq!(messages, vec!["ctx2", "ctx1"]);
349 }
350
351 #[test]
352 fn test_display_and_debug() {
353 let error = OhnoCore::from("msg");
354 let display = format!("{error}");
355 assert!(display.starts_with("msg"));
356 let debug = format!("{error:?}");
357 assert!(debug.contains("OhnoCore"));
358 }
359
360 #[test]
361 fn test_from_string_impls() {
362 let s = "abc";
363 let error1: OhnoCore = s.into();
364 assert!(error1.to_string().starts_with("abc"));
365 assert!(matches!(error1.data.source, Source::Transparent(_)));
366
367 let error2: OhnoCore = String::from("def").into();
368 assert!(error2.to_string().starts_with("def"));
369 assert!(matches!(error2.data.source, Source::Transparent(_)));
370
371 let error3: OhnoCore = Cow::Borrowed("ghi").into();
372 assert!(error3.to_string().starts_with("ghi"));
373 assert!(matches!(error3.data.source, Source::Transparent(_)));
374 }
375
376 #[test]
377 fn test_from_boxed_error_impl() {
378 let io_error = std::io::Error::other("io error");
379 let boxed: Box<dyn StdError + Send + Sync> = Box::new(io_error);
380 let error: OhnoCore = boxed.into();
381 assert!(matches!(error.data.source, Source::Error(_)));
382 assert!(error.source().unwrap().downcast_ref::<std::io::Error>().is_some());
383 }
384
385 #[test]
386 fn test_from_io_error_impl() {
387 let io_error = std::io::Error::other("io error");
388 let error: OhnoCore = io_error.into();
389 assert!(matches!(error.data.source, Source::Error(_)));
390 assert!(error.source().unwrap().downcast_ref::<std::io::Error>().is_some());
391 }
392
393 #[test]
394 #[cfg_attr(miri, ignore)] fn force_backtrace_capture() {
396 let mut error = OhnoCore::from("test error with backtrace");
397 error.data.backtrace = Backtrace::force_capture();
398
399 assert!(error.has_backtrace());
400 let backtrace = error.backtrace();
401 assert_eq!(backtrace.status(), BacktraceStatus::Captured);
402 let display = format!("{error}");
403 assert!(display.starts_with("test error with backtrace\n\nBacktrace:\n"));
404 }
405
406 #[test]
407 fn no_backtrace_capture() {
408 let mut error = OhnoCore::from("test error without backtrace");
409 error.data.backtrace = Backtrace::disabled();
410 assert!(!error.has_backtrace());
411 assert_eq!(error.backtrace().status(), BacktraceStatus::Disabled);
412 let display = format!("{error}");
413 assert_eq!(display, "test error without backtrace");
414 }
415
416 #[test]
417 fn is_string_error_test() {
418 assert!(is_string_error(&"a string slice"));
419 assert!(is_string_error(&String::from("a string")));
420 assert!(is_string_error(&Cow::Borrowed("a string slice")));
421 assert!(is_string_error(&Cow::<'static, str>::Owned(String::from("a string"))));
422 assert!(!is_string_error(&std::io::Error::other("an io error")));
423 }
424}