1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ConversionError {
10 UtcHistoryUnsupported,
12 InvalidLeapSecond,
15 OutOfRange,
17 Ut1HorizonExceeded,
20 NonFinite,
22 UtcBeforeDefinition,
30}
31
32impl core::fmt::Display for ConversionError {
33 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34 match self {
35 Self::UtcHistoryUnsupported => {
36 f.write_str("UTC history is unavailable for the requested date")
37 }
38 Self::InvalidLeapSecond => {
39 f.write_str("leap-second label is not present in the compiled UTC history")
40 }
41 Self::OutOfRange => f.write_str("converted value is out of representable range"),
42 Self::Ut1HorizonExceeded => {
43 f.write_str("UT1 conversion exceeds the ΔT model or data horizon")
44 }
45 Self::NonFinite => f.write_str("time value must be finite (not NaN or infinity)"),
46 Self::UtcBeforeDefinition => f.write_str(
47 "date precedes 1961-01-01, before which UTC was not defined; \
48 use TimeContext::allow_pre_definition_utc() to permit extrapolation",
49 ),
50 }
51 }
52}
53
54impl std::error::Error for ConversionError {}
55
56#[derive(Debug)]
62pub enum TimeDataError {
63 Io(std::io::Error),
65 Download(String),
67 Parse(String),
69 Integrity(String),
71}
72
73impl core::fmt::Display for TimeDataError {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 match self {
76 Self::Io(err) => write!(f, "I/O error: {err}"),
77 Self::Download(msg) => write!(f, "download error: {msg}"),
78 Self::Parse(msg) => write!(f, "parse error: {msg}"),
79 Self::Integrity(msg) => write!(f, "integrity error: {msg}"),
80 }
81 }
82}
83
84impl std::error::Error for TimeDataError {
85 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
86 match self {
87 Self::Io(err) => Some(err),
88 _ => None,
89 }
90 }
91}
92
93impl From<std::io::Error> for TimeDataError {
94 fn from(err: std::io::Error) -> Self {
95 Self::Io(err)
96 }
97}
98
99impl From<tempoch_time_data::TimeDataError> for TimeDataError {
100 fn from(err: tempoch_time_data::TimeDataError) -> Self {
101 match err {
102 tempoch_time_data::TimeDataError::Io(e) => Self::Io(e),
103 tempoch_time_data::TimeDataError::Download(msg) => Self::Download(msg),
104 tempoch_time_data::TimeDataError::Parse(msg) => Self::Parse(msg),
105 tempoch_time_data::TimeDataError::Integrity(msg) => Self::Integrity(msg),
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use std::error::Error;
114
115 fn io_error(msg: &str) -> std::io::Error {
116 std::io::Error::other(msg.to_string())
117 }
118
119 #[test]
120 fn display_all_variants() {
121 let cases: &[(ConversionError, &str)] = &[
122 (ConversionError::UtcHistoryUnsupported, "history"),
123 (ConversionError::InvalidLeapSecond, "leap-second"),
124 (ConversionError::OutOfRange, "range"),
125 (ConversionError::Ut1HorizonExceeded, "horizon"),
126 (ConversionError::NonFinite, "finite"),
127 (ConversionError::UtcBeforeDefinition, "1961"),
128 ];
129 for (variant, fragment) in cases {
130 let s = variant.to_string();
131 assert!(s.contains(fragment), "{variant:?}: got {s:?}");
132 }
133 }
134
135 #[test]
136 fn time_data_error_display_and_source() {
137 let io = TimeDataError::Io(io_error("disk full"));
138 assert!(io.to_string().contains("I/O error: disk full"));
139 assert!(io.source().is_some());
140
141 let download = TimeDataError::Download("network timeout".to_string());
142 assert!(download
143 .to_string()
144 .contains("download error: network timeout"));
145 assert!(download.source().is_none());
146
147 let parse = TimeDataError::Parse("bad row".to_string());
148 assert!(parse.to_string().contains("parse error: bad row"));
149 assert!(parse.source().is_none());
150
151 let integrity = TimeDataError::Integrity("checksum mismatch".to_string());
152 assert!(integrity
153 .to_string()
154 .contains("integrity error: checksum mismatch"));
155 assert!(integrity.source().is_none());
156 }
157
158 #[test]
159 fn time_data_error_from_mappings_cover_all_variants() {
160 let io_mapped: TimeDataError = io_error("io map").into();
161 assert!(matches!(io_mapped, TimeDataError::Io(_)));
162
163 let mapped_download: TimeDataError =
164 tempoch_time_data::TimeDataError::Download("d".to_string()).into();
165 assert!(matches!(mapped_download, TimeDataError::Download(msg) if msg == "d"));
166
167 let mapped_parse: TimeDataError =
168 tempoch_time_data::TimeDataError::Parse("p".to_string()).into();
169 assert!(matches!(mapped_parse, TimeDataError::Parse(msg) if msg == "p"));
170
171 let mapped_integrity: TimeDataError =
172 tempoch_time_data::TimeDataError::Integrity("i".to_string()).into();
173 assert!(matches!(mapped_integrity, TimeDataError::Integrity(msg) if msg == "i"));
174
175 let mapped_io: TimeDataError = tempoch_time_data::TimeDataError::Io(io_error("x")).into();
176 assert!(matches!(mapped_io, TimeDataError::Io(_)));
177 }
178}