1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use crate::app::DynError;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};

/// [AeonError] is an implementation of [Error] which is
/// intended as a general "runtime error" in the AEON application.
pub struct AeonError {
    description: String,
    source: Option<DynError>,
}

impl AeonError {
    /// Create a new instance of [AeonError] with the provided `description` and
    /// an optional `source` [DynError].
    ///
    /// Refer to [Error] regarding recommended error description format.
    ///
    /// ```rust
    /// # use biodivine_sketchbook::app::AeonError;
    /// # use std::error::Error;
    /// let error = AeonError::new("something failed", None);
    /// let other_error = AeonError::new("something else failed", Some(Box::new(error)));
    /// assert_eq!("something else failed", format!("{}", other_error));
    /// assert_eq!("something failed", format!("{}", other_error.source().unwrap()));
    /// ```
    pub fn new(description: impl Into<String>, source: Option<DynError>) -> AeonError {
        AeonError {
            description: description.into(),
            source,
        }
    }

    /// The same as [Self::new], but returns [DynError] instead.
    pub fn dyn_new(description: impl Into<String>) -> DynError {
        Box::new(Self::new(description, None))
    }

    /// Create a new instance of [AeonError], convert it to [DynError] and return it as
    /// the specified [Result] type.
    ///
    /// This function is useful when you want to return an error from a function which
    /// returns some `Result<R, DynError>`, because you don't need to convert the error
    /// into the expected result type.
    ///
    /// See also [AeonError::throw_with_source].
    ///
    /// ```rust
    /// # use biodivine_sketchbook::app::{AeonError, DynError};
    /// fn division(numerator: i32, denominator: i32) -> Result<i32, DynError> {
    ///     if denominator == 0 {
    ///         AeonError::throw("division by zero")
    ///     } else {
    ///         Ok(numerator / denominator)
    ///     }
    /// }
    ///
    /// assert_eq!(5, division(10, 2).unwrap());
    /// assert_eq!("division by zero", format!("{}", division(10, 0).unwrap_err()));
    /// ```
    pub fn throw<R>(description: impl Into<String>) -> Result<R, DynError> {
        Err(Box::new(AeonError::new(description, None)))
    }

    /// The same as [AeonError::throw], but also includes a generic error `source`.
    ///
    /// Note that compared to [AeonError::new], `source` can be any `Into<DynError>` type,
    /// which means you can avoid conversions when they can be performed automatically
    /// (see the example below).
    ///
    /// ```rust
    /// # use biodivine_sketchbook::app::{AeonError, DynError};
    /// fn read_number(num: &str) -> Result<i32, DynError> {
    ///     match num.parse::<i32>() {
    ///         Ok(num) => Ok(num),
    ///         Err(e) => AeonError::throw_with_source("invalid number", e),
    ///     }
    /// }
    ///
    /// assert_eq!(5, read_number("5").unwrap());
    /// assert_eq!("invalid number", format!("{}", read_number("abc").unwrap_err()));
    /// ```
    pub fn throw_with_source<R>(
        description: impl Into<String>,
        source: impl Into<DynError>,
    ) -> Result<R, DynError> {
        Err(Box::new(AeonError::new(description, Some(source.into()))))
    }
}

impl Debug for AeonError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if let Some(source) = self.source.as_ref() {
            write!(
                f,
                "AeonError[description=\"{}\",source=\"{}\"]",
                self.description, source
            )
        } else {
            write!(f, "AeonError[description=\"{}\"]", self.description)
        }
    }
}

impl Display for AeonError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.description)
    }
}

impl Error for AeonError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_ref().map(|it| it.as_ref())
    }
}