1use std::fmt;
2use std::io;
3
4pub type Result<T> = std::result::Result<T, FitsError>;
5
6#[derive(Debug)]
7pub enum FitsError {
8 Io(io::Error),
9 InvalidKeyword {
11 name: String,
12 },
13 InvalidValue {
15 card: String,
16 },
17 InvalidBitpix {
19 code: i64,
20 },
21 MissingEnd,
23 MissingKeyword {
25 name: &'static str,
26 },
27 KeywordOutOfRange {
31 name: &'static str,
32 },
33 UnexpectedEof,
35 DataUnitOverflow,
38 DataUnitTooLarge {
44 bytes: usize,
45 },
46 DataSizeMismatch {
49 expected: usize,
50 got: usize,
51 },
52 HduIndexOutOfBounds {
54 index: usize,
55 len: usize,
56 },
57 NotAnImage,
60 ImageHasGroups,
63 NotABinTable,
65 NotRandomGroups,
67 NotAnAsciiTable,
69 NotCompressedImage,
73 NotCompressedTable,
76 ConflictingWcsKeywords {
79 detail: &'static str,
80 },
81 UnsupportedCompression {
83 name: String,
84 },
85 InvalidTform {
87 tform: String,
88 },
89 VariableLengthColumn {
92 code: char,
93 },
94 NotAVla {
96 code: char,
97 },
98 NotABitColumn {
100 code: char,
101 },
102 NotAComplexColumn {
104 code: char,
105 },
106 NonNumericColumn {
109 code: char,
110 },
111 ColumnIndexOutOfBounds {
113 index: usize,
114 len: usize,
115 },
116 ColumnNotFound {
118 name: String,
119 },
120 RowWidthMismatch {
122 computed: usize,
123 declared: usize,
124 },
125 TileShapeRankMismatch {
128 tile_rank: usize,
129 image_rank: usize,
130 },
131}
132
133impl fmt::Display for FitsError {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 match self {
136 FitsError::Io(e) => write!(f, "I/O error: {e}"),
137 FitsError::InvalidKeyword { name } => write!(f, "invalid keyword name {name:?}"),
138 FitsError::InvalidValue { card } => {
139 write!(f, "cannot parse value field of card {card:?}")
140 }
141 FitsError::InvalidBitpix { code } => write!(f, "invalid BITPIX value {code}"),
142 FitsError::MissingEnd => write!(f, "header unit ended without an END record"),
143 FitsError::MissingKeyword { name } => write!(f, "missing mandatory keyword {name}"),
144 FitsError::KeywordOutOfRange { name } => {
145 write!(f, "keyword {name} has an out-of-range value")
146 }
147 FitsError::UnexpectedEof => write!(f, "unexpected end of stream inside a FITS unit"),
148 FitsError::DataUnitOverflow => {
149 write!(f, "header-implied data-unit size overflows 64 bits")
150 }
151 FitsError::DataUnitTooLarge { bytes } => {
152 write!(
153 f,
154 "header-implied data-unit size ({bytes} bytes) is too large to allocate"
155 )
156 }
157 FitsError::DataSizeMismatch { expected, got } => {
158 write!(
159 f,
160 "decoded data unit has {got} elements, header implies {expected}"
161 )
162 }
163 FitsError::HduIndexOutOfBounds { index, len } => {
164 write!(f, "HDU index {index} out of bounds (file has {len} HDUs)")
165 }
166 FitsError::NotAnImage => write!(f, "HDU is not an image array"),
167 FitsError::ImageHasGroups => {
168 write!(
169 f,
170 "image HDU has group structure (PCOUNT ≠ 0 or GCOUNT ≠ 1)"
171 )
172 }
173 FitsError::NotABinTable => write!(f, "HDU is not a binary table"),
174 FitsError::NotRandomGroups => write!(f, "HDU is not a random-groups primary"),
175 FitsError::NotAnAsciiTable => write!(f, "HDU is not an ASCII table"),
176 FitsError::NotCompressedImage => write!(f, "HDU is not a tiled-compressed image"),
177 FitsError::NotCompressedTable => write!(f, "HDU is not a tiled-compressed table"),
178 FitsError::ConflictingWcsKeywords { detail } => {
179 write!(f, "conflicting WCS keywords: {detail}")
180 }
181 FitsError::UnsupportedCompression { name } => {
182 write!(f, "unsupported tiled compression: {name}")
183 }
184 FitsError::InvalidTform { tform } => write!(f, "invalid column format {tform:?}"),
185 FitsError::VariableLengthColumn { code } => write!(
186 f,
187 "column format '{code}' is a variable-length array; use the column reader's vla()"
188 ),
189 FitsError::NotAVla { code } => {
190 write!(f, "column format '{code}' is not a variable-length array")
191 }
192 FitsError::NotABitColumn { code } => {
193 write!(f, "column format '{code}' is not an X bit array")
194 }
195 FitsError::NotAComplexColumn { code } => {
196 write!(f, "column format '{code}' is not a C/M complex column")
197 }
198 FitsError::NonNumericColumn { code } => {
199 write!(f, "column format '{code}' has no numeric physical value")
200 }
201 FitsError::ColumnIndexOutOfBounds { index, len } => {
202 write!(
203 f,
204 "column index {index} out of bounds (table has {len} columns)"
205 )
206 }
207 FitsError::ColumnNotFound { name } => {
208 write!(f, "no column named {name:?} in the table")
209 }
210 FitsError::RowWidthMismatch { computed, declared } => write!(
211 f,
212 "column widths sum to {computed} bytes but NAXIS1 declares {declared}"
213 ),
214 FitsError::TileShapeRankMismatch {
215 tile_rank,
216 image_rank,
217 } => write!(
218 f,
219 "tile shape has {tile_rank} axes but the image has {image_rank}"
220 ),
221 }
222 }
223}
224
225impl std::error::Error for FitsError {
226 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
227 match self {
228 FitsError::Io(e) => Some(e),
229 _ => None,
230 }
231 }
232}
233
234impl From<io::Error> for FitsError {
235 fn from(e: io::Error) -> Self {
236 FitsError::Io(e)
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn display_messages_are_specific() {
246 assert_eq!(
247 FitsError::InvalidBitpix { code: 7 }.to_string(),
248 "invalid BITPIX value 7"
249 );
250 assert_eq!(
251 FitsError::DataUnitOverflow.to_string(),
252 "header-implied data-unit size overflows 64 bits"
253 );
254 assert_eq!(
255 FitsError::DataUnitTooLarge { bytes: 1 << 60 }.to_string(),
256 "header-implied data-unit size (1152921504606846976 bytes) is too large to allocate"
257 );
258 assert_eq!(
259 FitsError::MissingKeyword { name: "NAXIS" }.to_string(),
260 "missing mandatory keyword NAXIS"
261 );
262 }
263
264 #[test]
265 fn io_error_is_preserved_as_source() {
266 let io_err = io::Error::new(io::ErrorKind::UnexpectedEof, "boom");
267 let err = FitsError::from(io_err);
268 assert!(matches!(err, FitsError::Io(_)));
269 assert!(std::error::Error::source(&err).is_some());
270 }
271}