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}