1use core::fmt;
4
5#[derive(Debug)]
12#[non_exhaustive]
13pub enum ArcsecError {
14 Singular,
16 InsufficientStars {
18 found: usize,
20 required: usize,
22 },
23 InsufficientQuads {
28 found: usize,
30 required: usize,
32 },
33 BadSolution {
36 ratio: f64,
39 },
40 CatalogNotFound(std::path::PathBuf),
42 CatalogIo(std::io::Error),
44 InvalidParameter(String),
46 Cancelled,
48 IndexNotFound(std::path::PathBuf),
50 OutsideSearchRadius {
54 separation_deg: f64,
56 },
57}
58
59impl fmt::Display for ArcsecError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::Singular => write!(f, "singular matrix in LSQ solver"),
63 Self::InsufficientStars { found, required } => {
64 write!(f, "insufficient stars: found {found}, required {required}")
65 }
66 Self::InsufficientQuads { found, required } => {
67 write!(f, "insufficient quads: found {found}, required {required}")
68 }
69 Self::BadSolution { ratio } => {
70 write!(
71 f,
72 "bad solution: plate singular-value ratio {ratio:.4} exceeds {}",
73 crate::math::lsq::MAX_PLATE_ANISOTROPY
74 )
75 }
76 Self::CatalogNotFound(p) => write!(f, "catalog not found: {}", p.display()),
77 Self::CatalogIo(e) => write!(f, "catalog I/O error: {e}"),
78 Self::InvalidParameter(msg) => write!(f, "invalid parameter: {msg}"),
79 Self::Cancelled => write!(f, "cancelled"),
80 Self::IndexNotFound(p) => write!(f, "no index files found at {}", p.display()),
81 Self::OutsideSearchRadius { separation_deg } => write!(
82 f,
83 "the blind index places this field {separation_deg:.1}° from the start \
84 position, outside the search radius"
85 ),
86 }
87 }
88}
89
90impl core::error::Error for ArcsecError {
91 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
92 match self {
93 Self::CatalogIo(e) => Some(e),
94 _ => None,
95 }
96 }
97}
98
99impl From<std::io::Error> for ArcsecError {
100 fn from(e: std::io::Error) -> Self {
101 Self::CatalogIo(e)
102 }
103}
104
105pub type Result<T> = core::result::Result<T, ArcsecError>;
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use core::error::Error as _;
112
113 #[test]
115 fn display_messages() {
116 let cases = [
117 (ArcsecError::Singular, "singular matrix in LSQ solver"),
118 (
119 ArcsecError::InsufficientStars {
120 found: 3,
121 required: 5,
122 },
123 "insufficient stars: found 3, required 5",
124 ),
125 (
126 ArcsecError::InsufficientQuads {
127 found: 0,
128 required: 4,
129 },
130 "insufficient quads: found 0, required 4",
131 ),
132 (
133 ArcsecError::BadSolution { ratio: 1.23456 },
134 "bad solution: plate singular-value ratio 1.2346 exceeds 1.08",
135 ),
136 (
137 ArcsecError::CatalogNotFound(std::path::PathBuf::from("/db")),
138 "catalog not found: /db",
139 ),
140 (
141 ArcsecError::InvalidParameter("fov".into()),
142 "invalid parameter: fov",
143 ),
144 (ArcsecError::Cancelled, "cancelled"),
145 (
146 ArcsecError::IndexNotFound(std::path::PathBuf::from("/ix")),
147 "no index files found at /ix",
148 ),
149 (
150 ArcsecError::OutsideSearchRadius {
151 separation_deg: 42.04,
152 },
153 "the blind index places this field 42.0° from the start position, \
154 outside the search radius",
155 ),
156 ];
157 for (err, want) in cases {
158 assert_eq!(err.to_string(), want);
159 assert!(err.source().is_none());
160 }
161 }
162
163 #[test]
164 fn io_errors_convert_and_keep_their_source() {
165 let err: ArcsecError = std::io::Error::new(std::io::ErrorKind::NotFound, "gone").into();
166 assert_eq!(err.to_string(), "catalog I/O error: gone");
167 let source = err.source().expect("source");
168 assert_eq!(source.to_string(), "gone");
169 assert!(
170 matches!(err, ArcsecError::CatalogIo(ref e) if e.kind() == std::io::ErrorKind::NotFound)
171 );
172 }
173}