value_box/
error.rs

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