Skip to main content

value_box/
error.rs

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}