Skip to main content

t2t_core/
errors.rs

1//! Why a string is not a time, and why a time does not fit another type.
2
3use derive_more::{Display, Error};
4
5/// Why a string is not a [`Timedelta`](crate::Timedelta), nor a point counted from boot.
6///
7/// # Examples
8/// ```
9/// use t2t_core::{ParseTimedeltaError, Timedelta};
10///
11/// assert_eq!("90 seconds".parse::<Timedelta>(), Err(ParseTimedeltaError), "no such unit");
12/// ```
13#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq, Hash)]
14#[display(
15    "parse timedelta error: expected `0`, or counts with units from coarsest to finest, as `1m30s`"
16)]
17pub struct ParseTimedeltaError;
18
19/// Why a string is not a [`Timestamp`](crate::Timestamp).
20///
21/// # Examples
22/// ```
23/// use t2t_core::{ParseTimestampError, Timestamp};
24///
25/// let local = "2026-09-16T09:45:35+02:00".parse::<Timestamp>();
26/// assert_eq!(local, Err(ParseTimestampError), "an instant is read in UTC alone");
27/// ```
28#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq, Hash)]
29#[display(
30    "parse timestamp error: expected RFC 3339 in UTC from 1677 to 2262, as `2026-09-16T07:45:35Z`"
31)]
32pub struct ParseTimestampError;
33
34/// Why a string is not a [`TaiTimestamp`](crate::TaiTimestamp).
35///
36/// # Examples
37/// ```
38/// use t2t_core::{ParseTaiTimestampError, TaiTimestamp};
39///
40/// let utc = "2026-09-16T07:46:12Z".parse::<TaiTimestamp>();
41/// assert_eq!(utc, Err(ParseTaiTimestampError), "an instant in UTC is no instant in TAI");
42/// ```
43#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq, Hash)]
44#[display(
45    "parse TAI timestamp error: expected an RFC 3339 date and time in TAI from 1677 to 2262, as \
46     `2026-09-16T07:46:12 TAI`"
47)]
48pub struct ParseTaiTimestampError;
49
50/// Why a string is not a [`Tickdelta`](crate::Tickdelta), nor a [`Tickstamp`](crate::Tickstamp).
51///
52/// # Examples
53/// ```
54/// use t2t_core::{ParseTickdeltaError, Tickdelta};
55///
56/// assert_eq!("24".parse::<Tickdelta>(), Err(ParseTickdeltaError), "a count names its unit");
57/// ```
58#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq, Hash)]
59#[display("parse tickdelta error: expected a count of ticks, as `24 ticks`")]
60pub struct ParseTickdeltaError;
61
62/// Why a string is not a [`TickRate`](crate::TickRate).
63///
64/// # Examples
65/// ```
66/// use t2t_core::{ParseTickRateError, TickRate};
67///
68/// assert_eq!("0 Hz".parse::<TickRate>(), Err(ParseTickRateError), "a counter that never ticks");
69/// ```
70#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq, Hash)]
71#[display("parse tick rate error: expected a count of hertz above zero, as `24000000 Hz`")]
72pub struct ParseTickRateError;
73
74/// Why a time does not fit the type it was converted to: a negative span for a
75/// [`Duration`](core::time::Duration), or a value past what a count of nanoseconds holds.
76///
77/// # Examples
78/// ```
79/// use core::time::Duration;
80///
81/// use t2t_core::{OutOfRangeError, Timedelta};
82///
83/// let backwards = Duration::try_from(-Timedelta::SECOND);
84/// assert_eq!(backwards, Err(OutOfRangeError), "a duration never runs backwards");
85/// ```
86#[derive(Debug, Display, Error, Clone, Copy, PartialEq, Eq, Hash)]
87#[display("out of range error: the time is outside what the target type holds")]
88pub struct OutOfRangeError;
89
90/// `count` narrowed to a `T`, or the refusal of one past what a `T` holds.
91#[inline]
92pub(crate) fn narrow<T, N: TryInto<T>>(count: N) -> Result<T, OutOfRangeError> {
93    count.try_into().map_err(|_past_the_range| OutOfRangeError)
94}
95
96#[cfg(test)]
97mod tests {
98    use alloc::string::{String, ToString as _};
99
100    use rstest::rstest;
101
102    use crate::{
103        OutOfRangeError, ParseTaiTimestampError, ParseTickRateError, ParseTickdeltaError,
104        ParseTimedeltaError, ParseTimestampError,
105    };
106
107    #[rstest]
108    #[case::a_span(
109        ParseTimedeltaError.to_string(),
110        "parse timedelta error: expected `0`, or counts with units from coarsest to finest, as `1m30s`"
111    )]
112    #[case::an_instant(
113        ParseTimestampError.to_string(),
114        "parse timestamp error: expected RFC 3339 in UTC from 1677 to 2262, as `2026-09-16T07:45:35Z`"
115    )]
116    #[case::a_tai_instant(
117        ParseTaiTimestampError.to_string(),
118        "parse TAI timestamp error: expected an RFC 3339 date and time in TAI from 1677 to 2262, \
119         as `2026-09-16T07:46:12 TAI`"
120    )]
121    #[case::a_count_of_ticks(
122        ParseTickdeltaError.to_string(),
123        "parse tickdelta error: expected a count of ticks, as `24 ticks`"
124    )]
125    #[case::a_rate(
126        ParseTickRateError.to_string(),
127        "parse tick rate error: expected a count of hertz above zero, as `24000000 Hz`"
128    )]
129    #[case::a_conversion(
130        OutOfRangeError.to_string(),
131        "out of range error: the time is outside what the target type holds"
132    )]
133    fn each_refusal_says_why(#[case] message: String, #[case] text: &str) {
134        assert_eq!(message, text, "the message names what would have been read");
135    }
136}