twilight_model/util/datetime/
error.rs

1//! Error detail implementation for [`Timestamp`] parsing.
2//!
3//! This type wraps parsing errors generated by the `time` crate.
4//!
5//! [`Timestamp`]: super::Timestamp
6
7use std::{
8    error::Error,
9    fmt::{Display, Formatter, Result as FmtResult},
10};
11use time::error::{ComponentRange as ComponentRangeError, Parse as ParseError};
12
13/// Reason that an ISO 8601 format couldn't be parsed.
14#[derive(Debug)]
15pub struct TimestampParseError {
16    /// Type of error that occurred.
17    kind: TimestampParseErrorType,
18    /// Source of the error, if there is any.
19    source: Option<Box<dyn Error + Send + Sync>>,
20}
21
22impl TimestampParseError {
23    /// Error that was caused by the datetime being of an improper format.
24    pub(super) const FORMAT: TimestampParseError = TimestampParseError {
25        kind: TimestampParseErrorType::Format,
26        source: None,
27    };
28
29    /// Immutable reference to the type of error that occurred.
30    #[must_use = "retrieving the type has no effect if left unused"]
31    pub const fn kind(&self) -> &TimestampParseErrorType {
32        &self.kind
33    }
34
35    /// Consume the error, returning the source error if there is any.
36    #[allow(clippy::unused_self)]
37    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
38    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
39        self.source
40    }
41
42    /// Consume the error, returning the owned error type and the source error.
43    #[must_use = "consuming the error into its parts has no effect if left unused"]
44    pub fn into_parts(
45        self,
46    ) -> (
47        TimestampParseErrorType,
48        Option<Box<dyn Error + Send + Sync>>,
49    ) {
50        (self.kind, self.source)
51    }
52
53    /// Create a new error from a [`ComponentRangeError`].
54    pub(super) fn from_component_range(source: ComponentRangeError) -> Self {
55        Self {
56            kind: TimestampParseErrorType::Range,
57            source: Some(Box::new(source)),
58        }
59    }
60
61    /// Create a new error from a [`ParseError`].
62    pub(super) fn from_parse(source: ParseError) -> Self {
63        Self {
64            kind: TimestampParseErrorType::Parsing,
65            source: Some(Box::new(source)),
66        }
67    }
68}
69
70impl Display for TimestampParseError {
71    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
72        match &self.kind {
73            TimestampParseErrorType::Format => {
74                f.write_str("provided value is not in an iso 8601 format")
75            }
76            TimestampParseErrorType::Parsing => f.write_str("timestamp parsing failed"),
77            TimestampParseErrorType::Range => {
78                f.write_str("value of a field is not in an acceptable range")
79            }
80        }
81    }
82}
83
84impl Error for TimestampParseError {}
85
86/// Type of [`TimestampParseError`] that occurred.
87#[derive(Debug)]
88pub enum TimestampParseErrorType {
89    /// Format of the input datetime is invalid.
90    ///
91    /// A datetime can take two forms: with microseconds and without
92    /// microseconds.
93    Format,
94    /// Timestamp parsing failed.
95    Parsing,
96    /// Value of a field is not in an acceptable range.
97    Range,
98}
99
100#[cfg(test)]
101mod tests {
102    use super::{TimestampParseError, TimestampParseErrorType};
103    use static_assertions::assert_impl_all;
104    use std::{error::Error, fmt::Debug};
105
106    assert_impl_all!(TimestampParseErrorType: Debug, Send, Sync);
107    assert_impl_all!(TimestampParseError: Error, Send, Sync);
108}