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}
47
48impl fmt::Display for ArcsecError {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::Singular => write!(f, "singular matrix in LSQ solver"),
52 Self::InsufficientStars { found, required } => {
53 write!(f, "insufficient stars: found {found}, required {required}")
54 }
55 Self::InsufficientQuads { found, required } => {
56 write!(f, "insufficient quads: found {found}, required {required}")
57 }
58 Self::BadSolution { ratio } => {
59 write!(
60 f,
61 "bad solution: plate singular-value ratio {ratio:.4} exceeds {}",
62 crate::math::lsq::MAX_PLATE_ANISOTROPY
63 )
64 }
65 Self::CatalogNotFound(p) => write!(f, "catalog not found: {}", p.display()),
66 Self::CatalogIo(e) => write!(f, "catalog I/O error: {e}"),
67 Self::InvalidParameter(msg) => write!(f, "invalid parameter: {msg}"),
68 }
69 }
70}
71
72impl core::error::Error for ArcsecError {
73 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
74 match self {
75 Self::CatalogIo(e) => Some(e),
76 _ => None,
77 }
78 }
79}
80
81impl From<std::io::Error> for ArcsecError {
82 fn from(e: std::io::Error) -> Self {
83 Self::CatalogIo(e)
84 }
85}
86
87pub type Result<T> = core::result::Result<T, ArcsecError>;
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use core::error::Error as _;
94
95 #[test]
97 fn display_messages() {
98 let cases = [
99 (ArcsecError::Singular, "singular matrix in LSQ solver"),
100 (
101 ArcsecError::InsufficientStars {
102 found: 3,
103 required: 5,
104 },
105 "insufficient stars: found 3, required 5",
106 ),
107 (
108 ArcsecError::InsufficientQuads {
109 found: 0,
110 required: 4,
111 },
112 "insufficient quads: found 0, required 4",
113 ),
114 (
115 ArcsecError::BadSolution { ratio: 1.23456 },
116 "bad solution: plate singular-value ratio 1.2346 exceeds 1.08",
117 ),
118 (
119 ArcsecError::CatalogNotFound(std::path::PathBuf::from("/db")),
120 "catalog not found: /db",
121 ),
122 (
123 ArcsecError::InvalidParameter("fov".into()),
124 "invalid parameter: fov",
125 ),
126 ];
127 for (err, want) in cases {
128 assert_eq!(err.to_string(), want);
129 assert!(err.source().is_none());
130 }
131 }
132
133 #[test]
134 fn io_errors_convert_and_keep_their_source() {
135 let err: ArcsecError = std::io::Error::new(std::io::ErrorKind::NotFound, "gone").into();
136 assert_eq!(err.to_string(), "catalog I/O error: gone");
137 let source = err.source().expect("source");
138 assert_eq!(source.to_string(), "gone");
139 assert!(
140 matches!(err, ArcsecError::CatalogIo(ref e) if e.kind() == std::io::ErrorKind::NotFound)
141 );
142 }
143}