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
//! Error detail implementation for [`Timestamp`] parsing.
//!
//! This type wraps parsing errors generated by the `time` crate.
//!
//! [`Timestamp`]: super::Timestamp

use std::{
    error::Error,
    fmt::{Display, Formatter, Result as FmtResult},
};
use time::error::{ComponentRange as ComponentRangeError, Parse as ParseError};

/// Reason that an ISO 8601 format couldn't be parsed.
#[derive(Debug)]
pub struct TimestampParseError {
    /// Type of error that occurred.
    kind: TimestampParseErrorType,
    /// Source of the error, if there is any.
    source: Option<Box<dyn Error + Send + Sync>>,
}

impl TimestampParseError {
    /// Error that was caused by the datetime being of an improper format.
    pub(super) const FORMAT: TimestampParseError = TimestampParseError {
        kind: TimestampParseErrorType::Format,
        source: None,
    };

    /// Immutable reference to the type of error that occurred.
    #[must_use = "retrieving the type has no effect if left unused"]
    pub const fn kind(&self) -> &TimestampParseErrorType {
        &self.kind
    }

    /// Consume the error, returning the source error if there is any.
    #[allow(clippy::unused_self)]
    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
        self.source
    }

    /// Consume the error, returning the owned error type and the source error.
    #[must_use = "consuming the error into its parts has no effect if left unused"]
    pub fn into_parts(
        self,
    ) -> (
        TimestampParseErrorType,
        Option<Box<dyn Error + Send + Sync>>,
    ) {
        (self.kind, self.source)
    }

    /// Create a new error from a [`ComponentRangeError`].
    pub(super) fn from_component_range(source: ComponentRangeError) -> Self {
        Self {
            kind: TimestampParseErrorType::Range,
            source: Some(Box::new(source)),
        }
    }

    /// Create a new error from a [`ParseError`].
    pub(super) fn from_parse(source: ParseError) -> Self {
        Self {
            kind: TimestampParseErrorType::Parsing,
            source: Some(Box::new(source)),
        }
    }
}

impl Display for TimestampParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match &self.kind {
            TimestampParseErrorType::Format => {
                f.write_str("provided value is not in an iso 8601 format")
            }
            TimestampParseErrorType::Parsing => f.write_str("timestamp parsing failed"),
            TimestampParseErrorType::Range => {
                f.write_str("value of a field is not in an acceptable range")
            }
        }
    }
}

impl Error for TimestampParseError {}

/// Type of [`TimestampParseError`] that occurred.
#[derive(Debug)]
pub enum TimestampParseErrorType {
    /// Format of the input datetime is invalid.
    ///
    /// A datetime can take two forms: with microseconds and without
    /// microseconds.
    Format,
    /// Timestamp parsing failed.
    Parsing,
    /// Value of a field is not in an acceptable range.
    Range,
}

#[cfg(test)]
mod tests {
    use super::{TimestampParseError, TimestampParseErrorType};
    use static_assertions::assert_impl_all;
    use std::{error::Error, fmt::Debug};

    assert_impl_all!(TimestampParseErrorType: Debug, Send, Sync);
    assert_impl_all!(TimestampParseError: Error, Send, Sync);
}