fitscube_rs/error.rs
1//! Error types for fitscube-rs.
2//!
3//! Mirrors the exception hierarchy of the original `fitscube` Python package
4//! ([`fitscube.exceptions`](https://github.com/AlecThomson/fitscube)): a base
5//! [`FitsCubeError`] with dedicated variants for a missing target (FREQ/TIME)
6//! axis and an inaccessible channel, plus the I/O and parsing errors that arise
7//! in the Rust implementation.
8use std::path::PathBuf;
9
10use thiserror::Error;
11
12/// Top-level error for all fitscube-rs operations.
13///
14/// `TargetAxisMissing` and `ChannelMissing` correspond to the Python
15/// `TargetAxisMissingException` and `ChannelMissingException`.
16#[derive(Debug, Error)]
17pub enum FitsCubeError {
18 /// Underlying cfitsio error.
19 #[error("FITS I/O error: {0}")]
20 Fits(#[from] fitsio::errors::Error),
21
22 /// Filesystem I/O error.
23 #[error("I/O error: {0}")]
24 Io(#[from] std::io::Error),
25
26 /// ndarray shape mismatch when reshaping a flat plane buffer.
27 #[error("shape error: {0}")]
28 Shape(#[from] ndarray::ShapeError),
29
30 /// A required header keyword was absent.
31 #[error("missing header keyword: {0}")]
32 MissingKeyword(String),
33
34 /// The FREQ/TIME (or other requested) axis was not found in the WCS.
35 ///
36 /// Equivalent to Python `TargetAxisMissingException`.
37 #[error("target axis missing: {0}")]
38 TargetAxisMissing(String),
39
40 /// A requested channel/timestep could not be accessed.
41 ///
42 /// Equivalent to Python `ChannelMissingException`.
43 #[error("channel missing: {0}")]
44 ChannelMissing(String),
45
46 /// The output file already exists and `overwrite` was not set.
47 #[error("output file {0} already exists (use overwrite)")]
48 OutputExists(PathBuf),
49
50 /// Mutually exclusive spectral-input options were combined, or a count
51 /// mismatch was detected.
52 #[error("invalid spectral input: {0}")]
53 InvalidSpec(String),
54
55 /// A FITS image had an unexpected dimensionality.
56 #[error("unsupported NAXIS={0}")]
57 UnsupportedNaxis(i64),
58
59 /// Failed to parse a DATE-OBS timestamp.
60 #[error("could not parse DATE-OBS {value:?}: {msg}")]
61 TimeParse { value: String, msg: String },
62
63 /// Catch-all for invariant violations.
64 #[error("{0}")]
65 Other(String),
66}
67
68/// Map the shared [`atfits_rs::AtfitsError`] into the fitscube-rs hierarchy.
69///
70/// The low-level cfitsio helpers (keyword editing, image creation, axis lookup)
71/// live in `atfits-rs`; this lets a `?` on any of them flow into a
72/// [`FitsCubeError`] with the matching variant.
73impl From<atfits_rs::AtfitsError> for FitsCubeError {
74 fn from(e: atfits_rs::AtfitsError) -> Self {
75 use atfits_rs::AtfitsError as A;
76 match e {
77 A::Fits(e) => FitsCubeError::Fits(e),
78 A::Io(e) => FitsCubeError::Io(e),
79 A::MissingKeyword(s) => FitsCubeError::MissingKeyword(s),
80 A::TargetAxisMissing(s) => FitsCubeError::TargetAxisMissing(s),
81 A::UnsupportedNaxis(n) => FitsCubeError::UnsupportedNaxis(n),
82 A::Other(s) => FitsCubeError::Other(s),
83 }
84 }
85}
86
87/// Convenience result alias.
88pub type Result<T> = std::result::Result<T, FitsCubeError>;