fundu_core/
error.rs

1// Copyright (c) 2023 Joining7943 <joining@posteo.de>
2//
3// This software is released under the MIT License.
4// https://opensource.org/licenses/MIT
5
6//! Provide the errors used in fundu like [`ParseError`] and [`TryFromDurationError`]
7
8use std::error::Error;
9use std::fmt::Display;
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14/// Error type emitted during the parsing
15#[derive(Debug, PartialEq, Eq, Clone, Hash)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[non_exhaustive]
18pub enum ParseError {
19    /// Returned, if the input was empty.
20    Empty,
21    /// A syntax error. Syntax errors report the position (column) where it was encountered and a
22    /// reason.
23    Syntax(usize, String),
24    /// Currently only used internally for overflows of the maximum Duration.
25    /// TODO: Rename to positive overflow
26    /// TODO: Add `NegativeOverflow`
27    Overflow,
28    /// An error concerning time units. Like [`ParseError::Syntax`]  the position where the error
29    /// occurred is included.
30    TimeUnit(usize, String),
31    /// The exponent exceeded the minimum negative exponent (`-32768`)
32    NegativeExponentOverflow,
33    /// The exponent exceeded the maximum positive exponent (`+32767`)
34    PositiveExponentOverflow,
35    /// The input number was negative. Note that numbers close to `0` (`< 1e-18`) are not negative
36    /// but resolve to `0`
37    NegativeNumber,
38    /// A generic error if no other error type fits
39    InvalidInput(String),
40}
41
42impl Error for ParseError {}
43
44impl Display for ParseError {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        let msg = match self {
47            Self::Syntax(column, reason) => {
48                format!("Syntax error: {reason} at column {column}")
49            }
50            Self::Overflow => "Number overflow".to_owned(),
51            Self::TimeUnit(pos, reason) => {
52                format!("Time unit error: {reason} at column {pos}")
53            }
54            Self::NegativeExponentOverflow => {
55                "Negative exponent overflow: Minimum is -32768".to_owned()
56            }
57            Self::PositiveExponentOverflow => {
58                "Positive exponent overflow: Maximum is +32767".to_owned()
59            }
60            Self::NegativeNumber => "Number was negative".to_owned(),
61            Self::InvalidInput(reason) => format!("Invalid input: {reason}"),
62            Self::Empty => "Empty input".to_owned(),
63        };
64        f.write_str(&msg)
65    }
66}
67
68/// This error may occur when converting a [`crate::time::Duration`] to a different duration like
69/// [`std::time::Duration`]
70#[derive(Debug, PartialEq, Eq, Clone, Hash)]
71#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
72pub enum TryFromDurationError {
73    /// The duration was negative and the destination duration doesn't support negative durations
74    NegativeDuration,
75    /// The duration was higher than the maximum of the destination duration
76    PositiveOverflow,
77    /// The duration was lower than the minimum of the destination duration
78    NegativeOverflow,
79}
80
81impl From<TryFromDurationError> for ParseError {
82    fn from(error: TryFromDurationError) -> Self {
83        match error {
84            TryFromDurationError::NegativeDuration => Self::NegativeNumber,
85            TryFromDurationError::PositiveOverflow | TryFromDurationError::NegativeOverflow => {
86                Self::Overflow
87            }
88        }
89    }
90}
91
92impl Error for TryFromDurationError {}
93
94impl Display for TryFromDurationError {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        let description = match self {
97            Self::NegativeDuration => "Error converting duration: value is negative",
98            Self::PositiveOverflow => {
99                "Error converting duration: value overflows the positive value range"
100            }
101            Self::NegativeOverflow => {
102                "Error converting duration: value overflows the negative value range"
103            }
104        };
105        description.fmt(f)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use rstest::rstest;
112    #[cfg(feature = "serde")]
113    use serde_test::{assert_tokens, Token};
114
115    use super::*;
116
117    #[rstest]
118    #[case::syntax_error(
119        ParseError::Syntax(10, "Invalid character".to_owned()),
120        "Syntax error: Invalid character at column 10"
121    )]
122    #[case::overflow(ParseError::Overflow, "Number overflow")]
123    #[case::time_unit_error(
124        ParseError::TimeUnit(10, "Found invalid 'y'".to_owned()),
125        "Time unit error: Found invalid 'y' at column 10"
126    )]
127    #[case::negative_exponent_overflow_error(
128        ParseError::NegativeExponentOverflow,
129        "Negative exponent overflow: Minimum is -32768"
130    )]
131    #[case::positive_exponent_overflow_error(
132        ParseError::PositiveExponentOverflow,
133        "Positive exponent overflow: Maximum is +32767"
134    )]
135    #[case::negative_number_error(ParseError::NegativeNumber, "Number was negative")]
136    #[case::invalid_input(
137        ParseError::InvalidInput("Unexpected".to_owned()),
138        "Invalid input: Unexpected"
139    )]
140    #[case::empty(ParseError::Empty, "Empty input")]
141    fn test_error_messages_parse_error(#[case] error: ParseError, #[case] expected: &str) {
142        assert_eq!(error.to_string(), expected);
143    }
144
145    #[rstest]
146    #[case::negative_overflow(TryFromDurationError::NegativeOverflow, ParseError::Overflow)]
147    #[case::positive_overflow(TryFromDurationError::PositiveOverflow, ParseError::Overflow)]
148    #[case::negative_number(TryFromDurationError::NegativeDuration, ParseError::NegativeNumber)]
149    fn test_from_for_parse_error(#[case] from: TryFromDurationError, #[case] expected: ParseError) {
150        assert_eq!(ParseError::from(from), expected);
151    }
152
153    #[rstest]
154    #[case::negative_number(
155        TryFromDurationError::NegativeDuration,
156        "Error converting duration: value is negative"
157    )]
158    #[case::positive_overflow(
159        TryFromDurationError::PositiveOverflow,
160        "Error converting duration: value overflows the positive value range"
161    )]
162    #[case::positive_overflow(
163        TryFromDurationError::NegativeOverflow,
164        "Error converting duration: value overflows the negative value range"
165    )]
166    fn test_error_messages_try_from_duration_error(
167        #[case] error: TryFromDurationError,
168        #[case] expected: &str,
169    ) {
170        assert_eq!(error.to_string(), expected);
171    }
172
173    #[cfg(feature = "serde")]
174    #[test]
175    fn test_serde_try_from_duration_error() {
176        let error = TryFromDurationError::NegativeDuration;
177
178        assert_tokens(
179            &error,
180            &[
181                Token::Enum {
182                    name: "TryFromDurationError",
183                },
184                Token::Str("NegativeDuration"),
185                Token::Unit,
186            ],
187        );
188    }
189
190    #[cfg(feature = "serde")]
191    #[test]
192    fn test_serde_parse_error() {
193        let error = ParseError::Empty;
194
195        assert_tokens(
196            &error,
197            &[
198                Token::Enum { name: "ParseError" },
199                Token::Str("Empty"),
200                Token::Unit,
201            ],
202        );
203    }
204}