leap_seconds/
errors.rs

1//! Contains all error-related functionality of the crate.
2
3use {
4    crate::{DateTime, Sha1Hash},
5    core::fmt::{self, Display},
6    std::io,
7    thiserror::Error,
8};
9
10/// Data that is required to exist exactly once in a `leap-seconds.list` file.
11///
12/// If that is not the case, an attempt to parse the file will result in a [`ParseFileError`].
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum DataComponent {
15    /// A line containing a timestamp when the file was updated last.
16    LastUpdate,
17
18    /// A timestamp at which the data in the file expires.
19    ExpirationDate,
20
21    /// A hash of the data contained in the file.
22    Hash,
23}
24
25impl Display for DataComponent {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        let result = match self {
28            Self::LastUpdate => "last update",
29            Self::ExpirationDate => "expiration date",
30            Self::Hash => "hash",
31        };
32
33        write!(f, "{result}")
34    }
35}
36
37/// Indicates that a `leap-seconds.list` file could not be parsed successfully.
38#[derive(Debug, Error)]
39pub enum ParseFileError {
40    /// An IO error occurred in the underlying stream.
41    #[error(transparent)]
42    IoError(#[from] io::Error),
43
44    /// A line in the file could not be parsed successfully.
45    ///
46    /// See [`ParseLineError`] for further information.
47    #[error(transparent)]
48    ParseLineError(#[from] ParseLineError),
49
50    /// The hash that was calculated did not match the one that was found in the file.
51    #[error("incorrect hash: calculated = {calculated}, found = {found}")]
52    InvalidHash {
53        /// The hash that was calculated from the contents of the file.
54        calculated: Sha1Hash,
55        /// The hash that was found in the file.
56        found: Sha1Hash,
57    },
58
59    /// The given file is incomplete. Required data could not be found in the file.
60    #[error("missing data: {0}")]
61    MissingData(DataComponent),
62
63    /// The file contains duplicate data.
64    #[error("duplicate data on lines {line1} and {line2}: {data_component}")]
65    DuplicateData {
66        /// The type of data that was found twice.
67        data_component: DataComponent,
68        /// The first line the data was found on.
69        line1: usize,
70        /// The second line the data was found on.
71        line2: usize,
72    },
73}
74
75/// Indicates an attempt to create an invalid [`Date`](crate::Date).
76///
77/// ```
78/// use leap_seconds::{Date, InvalidDate};
79///
80/// // November only has 30 days
81/// let error = Date::new(2000, 11, 31);
82///
83/// assert_eq!(error, Err(InvalidDate::DayOutOfRange(31)));
84/// ```
85#[derive(Clone, Debug, Error, Eq, PartialEq)]
86pub enum InvalidDate {
87    /// Indicates that the day was out of range.
88    #[error("day out of range: {0}")]
89    MonthOutOfRange(u8),
90    /// Indicates that the month was out of range.
91    #[error("month out of range: {0}")]
92    DayOutOfRange(u8),
93}
94
95/// Indicates and attempt to create an invalid [`Time`](crate::Time).
96///
97/// ```
98/// use leap_seconds::{Time, InvalidTime};
99///
100/// // A time with 60 seconds is invalid
101/// let error = Time::new(18, 42, 60);
102///
103/// assert_eq!(error, Err(InvalidTime::SecondsOutOfRange(60)));
104/// ```
105#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
106pub enum InvalidTime {
107    /// Indicates that the hours were out of range.
108    #[error("hours out of range: {0}")]
109    HoursOutOfRange(u8),
110    /// Indicates that the minutes were out of range.
111    #[error("minutes out of range: {0}")]
112    MinutesOutOfRange(u8),
113    /// Indicates that the seconds were out of range.
114    #[error("seconds out of range: {0}")]
115    SecondsOutOfRange(u8),
116}
117
118/// Occurs if a [`DateTime`] is not representable as a [`Timestamp`].
119///
120/// See [`Timestamp::MAX_REPRESENTABLE_DATE_TIME`] and [`Timestamp::MIN_REPRESENTABLE_DATE_TIME`]
121/// for the latest and the earliest [`DateTime`] respectively that can be represented as a
122/// [`Timestamp`].
123///
124/// ```
125/// # use std::error::Error;
126/// use leap_seconds::{
127///     Date, DateTime, Time, Timestamp,
128///     errors::DateTimeNotRepresentable,
129/// };
130///
131/// let date_time = DateTime {
132///     date: Date::new(1234, 5, 6)?,
133///     time: Time::new(7, 8, 9)?,
134/// };
135///
136/// let error: DateTimeNotRepresentable = Timestamp::from_date_time(date_time).unwrap_err();
137/// assert_eq!(error.date_time(), date_time);
138/// #
139/// # Ok::<(), Box<dyn Error>>(())
140/// ```
141///
142/// [`Timestamp`]: crate::Timestamp
143/// [`Timestamp::MAX_REPRESENTABLE_DATE_TIME`]: crate::Timestamp::MAX_REPRESENTABLE_DATE_TIME
144/// [`Timestamp::MIN_REPRESENTABLE_DATE_TIME`]: crate::Timestamp::MIN_REPRESENTABLE_DATE_TIME
145#[derive(Clone, Debug, Error, Eq, PartialEq)]
146pub struct DateTimeNotRepresentable {
147    pub(crate) date_time: DateTime,
148}
149
150impl DateTimeNotRepresentable {
151    /// Gets the [`DateTime`] that could not be represented as a [`Timestamp`].
152    ///
153    /// [`Timestamp`]: crate::Timestamp
154    #[must_use]
155    pub const fn date_time(self) -> DateTime {
156        self.date_time
157    }
158}
159
160impl Display for DateTimeNotRepresentable {
161    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
162        write!(f, "DateTime is not representable as 64-bit timestamp")
163    }
164}
165
166/// The error that occurs when a line of a `leap-seconds.list` file could not be parsed.
167#[derive(Debug, Error)]
168pub struct ParseLineError {
169    pub(crate) cause: ParseLineErrorKind,
170    pub(crate) line: String,
171    pub(crate) line_number: usize,
172}
173
174impl ParseLineError {
175    /// Gets what caused this error.
176    #[must_use]
177    pub const fn cause(&self) -> ParseLineErrorKind {
178        self.cause
179    }
180
181    /// Gets the line that caused this error.
182    #[must_use]
183    pub fn line(&self) -> &str {
184        &self.line
185    }
186
187    /// Gets the line number at which this error occured.
188    #[must_use]
189    pub const fn line_number(&self) -> usize {
190        self.line_number
191    }
192}
193
194impl Display for ParseLineError {
195    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
196        write!(
197            f,
198            "{} on line {}: \"{}\"",
199            self.cause, self.line_number, self.line
200        )
201    }
202}
203
204/// Reasons that could cause a line to not be parsed successfully.
205#[derive(Clone, Copy, Debug, Eq, PartialEq)]
206pub enum ParseLineErrorKind {
207    /// A timestamp is incorrectly formatted.
208    InvalidTimestamp,
209
210    /// A line describing a leap second is incorrectly formatted.
211    InvalidLeapSecondLine,
212
213    /// The [TAI]-[UTC] difference on a line describing a leap second is incorrectly formatted.
214    ///
215    /// [TAI]: https://en.wikipedia.org/wiki/International_Atomic_Time
216    /// [UTC]: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
217    InvalidTaiDiff,
218
219    /// The line containing the hash of the data is incorrectly formatted.
220    InvalidHash,
221}
222
223impl Display for ParseLineErrorKind {
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        let output = match self {
226            Self::InvalidTimestamp => "invalid timestamp",
227            Self::InvalidLeapSecondLine => "invalid leapsecond line",
228            Self::InvalidTaiDiff => "invalid TAI difference",
229            Self::InvalidHash => "invalid hash",
230        };
231
232        write!(f, "{output}")
233    }
234}