1use core::fmt;
4
5#[cfg(not(feature = "std"))]
6extern crate alloc;
7#[cfg(not(feature = "std"))]
8use alloc::{string::String, string::ToString, vec::Vec};
9
10#[derive(Debug, Clone, thiserror::Error)]
15#[non_exhaustive]
16pub enum FerrayError {
17 #[error("shape mismatch: {message}")]
19 ShapeMismatch {
20 message: String,
22 },
23
24 #[error("broadcast failure: cannot broadcast shapes {shape_a:?} and {shape_b:?}")]
26 BroadcastFailure {
27 shape_a: Vec<usize>,
29 shape_b: Vec<usize>,
31 },
32
33 #[error("axis {axis} is out of bounds for array with {ndim} dimensions")]
35 AxisOutOfBounds {
36 axis: usize,
38 ndim: usize,
40 },
41
42 #[error("index {index} is out of bounds for axis {axis} with size {size}")]
44 IndexOutOfBounds {
45 index: isize,
47 axis: usize,
49 size: usize,
51 },
52
53 #[error("singular matrix: {message}")]
55 SingularMatrix {
56 message: String,
58 },
59
60 #[error("convergence failure after {iterations} iterations: {message}")]
62 ConvergenceFailure {
63 iterations: usize,
65 message: String,
67 },
68
69 #[error("invalid dtype: {message}")]
71 InvalidDtype {
72 message: String,
74 },
75
76 #[error("numerical instability: {message}")]
78 NumericalInstability {
79 message: String,
81 },
82
83 #[error("I/O error: {message}")]
85 IoError {
86 message: String,
88 },
89
90 #[error("invalid value: {message}")]
92 InvalidValue {
93 message: String,
95 },
96}
97
98pub type FerrayResult<T> = Result<T, FerrayError>;
100
101impl FerrayError {
102 #[must_use]
122 pub const fn is_linalg_error(&self) -> bool {
123 matches!(
124 self,
125 Self::SingularMatrix { .. }
126 | Self::ConvergenceFailure { .. }
127 | Self::NumericalInstability { .. }
128 )
129 }
130}
131
132impl FerrayError {
133 pub fn shape_mismatch(msg: impl fmt::Display) -> Self {
135 Self::ShapeMismatch {
136 message: msg.to_string(),
137 }
138 }
139
140 #[must_use]
142 pub fn broadcast_failure(a: &[usize], b: &[usize]) -> Self {
143 Self::BroadcastFailure {
144 shape_a: a.to_vec(),
145 shape_b: b.to_vec(),
146 }
147 }
148
149 #[must_use]
151 pub const fn axis_out_of_bounds(axis: usize, ndim: usize) -> Self {
152 Self::AxisOutOfBounds { axis, ndim }
153 }
154
155 #[must_use]
157 pub const fn index_out_of_bounds(index: isize, axis: usize, size: usize) -> Self {
158 Self::IndexOutOfBounds { index, axis, size }
159 }
160
161 pub fn invalid_dtype(msg: impl fmt::Display) -> Self {
163 Self::InvalidDtype {
164 message: msg.to_string(),
165 }
166 }
167
168 pub fn invalid_value(msg: impl fmt::Display) -> Self {
170 Self::InvalidValue {
171 message: msg.to_string(),
172 }
173 }
174
175 pub fn io_error(msg: impl fmt::Display) -> Self {
177 Self::IoError {
178 message: msg.to_string(),
179 }
180 }
181}
182
183#[cfg(feature = "std")]
184impl From<std::io::Error> for FerrayError {
185 fn from(e: std::io::Error) -> Self {
186 Self::IoError {
187 message: e.to_string(),
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn error_display_shape_mismatch() {
198 let e = FerrayError::shape_mismatch("expected (3,4), got (3,5)");
199 assert!(e.to_string().contains("expected (3,4), got (3,5)"));
200 }
201
202 #[test]
203 fn error_display_axis_out_of_bounds() {
204 let e = FerrayError::axis_out_of_bounds(5, 3);
205 assert!(e.to_string().contains("axis 5"));
206 assert!(e.to_string().contains("3 dimensions"));
207 }
208
209 #[test]
210 fn error_display_broadcast_failure() {
211 let e = FerrayError::broadcast_failure(&[4, 3], &[2, 5]);
212 let s = e.to_string();
213 assert!(s.contains("[4, 3]"));
214 assert!(s.contains("[2, 5]"));
215 }
216
217 #[test]
218 fn error_is_non_exhaustive() {
219 let e = FerrayError::invalid_value("test");
223 assert!(matches!(e, FerrayError::InvalidValue { .. }));
224
225 let e2 = FerrayError::shape_mismatch("bad shape");
226 assert!(matches!(e2, FerrayError::ShapeMismatch { .. }));
227 }
228
229 #[test]
230 fn from_io_error() {
231 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
232 let ferray_err: FerrayError = io_err.into();
233 assert!(ferray_err.to_string().contains("file missing"));
234 }
235
236 #[test]
239 fn is_linalg_error_returns_true_for_linalg_variants() {
240 assert!(
241 FerrayError::SingularMatrix {
242 message: "test".into()
243 }
244 .is_linalg_error()
245 );
246 assert!(
247 FerrayError::ConvergenceFailure {
248 iterations: 100,
249 message: "test".into()
250 }
251 .is_linalg_error()
252 );
253 assert!(
254 FerrayError::NumericalInstability {
255 message: "test".into()
256 }
257 .is_linalg_error()
258 );
259 }
260
261 #[test]
262 fn is_linalg_error_returns_false_for_other_variants() {
263 assert!(!FerrayError::shape_mismatch("test").is_linalg_error());
264 assert!(!FerrayError::invalid_dtype("test").is_linalg_error());
265 assert!(!FerrayError::invalid_value("test").is_linalg_error());
266 assert!(!FerrayError::io_error("test").is_linalg_error());
267 assert!(!FerrayError::axis_out_of_bounds(2, 1).is_linalg_error());
268 }
269}