1use std::any::Any;
2use thiserror::Error;
3use user_error::{UFE, UserFacingError};
4
5const SUMMARY_PREFIX: &str = "\u{001b}[97;41;22mError:\u{001b}[91;49;1m ";
6const RESET: &str = "\u{001b}[0m";
7const REASON_PREFIX: &str = "\u{001b}[93;49;1m - \u{001b}[97;49;1m";
8
9#[derive(Error, Debug)]
10pub enum BoxerError {
11 #[error("The pointer to the box of type {0} is null")]
12 NullPointer(String),
13 #[error("There is no value of type {0} in the box")]
14 NoValue(String),
15 #[error("There was an error")]
16 #[cfg(feature = "anyhow")]
17 AnyhowError(#[from] anyhow::Error),
18 #[error("There was an IO error")]
19 IOError(#[from] std::io::Error),
20 #[error("There was an error")]
21 AnyError(#[from] Box<dyn std::error::Error>),
22}
23
24impl<T> From<BoxerError> for core::result::Result<T, BoxerError> {
25 fn from(error: BoxerError) -> Self {
26 Err(error)
27 }
28}
29
30impl From<String> for BoxerError {
31 fn from(value: String) -> Self {
32 Self::AnyError(value.into())
33 }
34}
35
36impl From<&str> for BoxerError {
37 fn from(value: &str) -> Self {
38 Self::AnyError(value.into())
39 }
40}
41
42pub type Result<T> = core::result::Result<T, BoxerError>;
43
44pub trait ReturnBoxerResult<Return: Any> {
45 fn log(self);
46 fn or_log(self, value: Return) -> Return;
47 fn or_print(self, value: Return) -> Return;
48}
49
50impl<Return: Any> ReturnBoxerResult<Return> for Result<Return> {
51 fn log(self) {
52 if let Err(error) = self {
53 log_boxer_error(error);
54 }
55 }
56
57 fn or_log(self, value: Return) -> Return {
58 self.unwrap_or_else(|error| {
59 log_boxer_error(error);
60 value
61 })
62 }
63
64 fn or_print(self, value: Return) -> Return {
65 self.map_err(|error| {
66 let error: Box<dyn std::error::Error> = Box::new(error);
67 let user_facing_error: UserFacingError = error.into();
68 user_facing_error
69 })
70 .unwrap_or_else(|error| {
71 println!("{}", pretty_summary(error.summary().as_str()));
72 if let Some(reasons) = pretty_reasons(error.reasons()) {
73 println!("{}", reasons);
74 }
75 value
76 })
77 }
78}
79
80fn log_boxer_error(error: BoxerError) {
81 match &error {
82 BoxerError::NullPointer(_) => warn_user_facing_error(to_user_facing_error(error)),
83 BoxerError::NoValue(_) => warn_user_facing_error(to_user_facing_error(error)),
84 _ => error_user_facing_error(to_user_facing_error(error)),
85 };
86}
87
88fn warn_user_facing_error(error: UserFacingError) {
89 warn!("{}", pretty_summary(error.summary().as_str()));
90 if let Some(reasons) = pretty_reasons(error.reasons()) {
91 warn!("{}", reasons);
92 }
93}
94
95fn error_user_facing_error(error: UserFacingError) {
96 error!("{}", pretty_summary(error.summary().as_str()));
97 if let Some(reasons) = pretty_reasons(error.reasons()) {
98 error!("{}", reasons);
99 }
100}
101
102fn to_user_facing_error(error: BoxerError) -> UserFacingError {
103 let error: Box<dyn std::error::Error> = Box::new(error);
104 let user_facing_error: UserFacingError = error.into();
105 user_facing_error
106}
107
108fn pretty_summary(summary: &str) -> String {
109 [SUMMARY_PREFIX, summary, RESET].concat()
110}
111
112fn pretty_reasons(reasons: Option<Vec<String>>) -> Option<String> {
113 reasons.map(|reasons| {
114 let mut reason_strings = Vec::with_capacity(reasons.len());
115 for reason in reasons {
116 let bullet_point = [REASON_PREFIX, &reason].concat();
117 reason_strings.push(bullet_point);
118 }
119 [&reason_strings.join("\n"), RESET].concat()
120 })
121}