1use core::fmt;
4
5#[cfg(feature = "no_std")]
6extern crate alloc;
7#[cfg(feature = "no_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 pub fn shape_mismatch(msg: impl fmt::Display) -> Self {
104 Self::ShapeMismatch {
105 message: msg.to_string(),
106 }
107 }
108
109 pub fn broadcast_failure(a: &[usize], b: &[usize]) -> Self {
111 Self::BroadcastFailure {
112 shape_a: a.to_vec(),
113 shape_b: b.to_vec(),
114 }
115 }
116
117 pub fn axis_out_of_bounds(axis: usize, ndim: usize) -> Self {
119 Self::AxisOutOfBounds { axis, ndim }
120 }
121
122 pub fn index_out_of_bounds(index: isize, axis: usize, size: usize) -> Self {
124 Self::IndexOutOfBounds { index, axis, size }
125 }
126
127 pub fn invalid_dtype(msg: impl fmt::Display) -> Self {
129 Self::InvalidDtype {
130 message: msg.to_string(),
131 }
132 }
133
134 pub fn invalid_value(msg: impl fmt::Display) -> Self {
136 Self::InvalidValue {
137 message: msg.to_string(),
138 }
139 }
140
141 pub fn io_error(msg: impl fmt::Display) -> Self {
143 Self::IoError {
144 message: msg.to_string(),
145 }
146 }
147}
148
149#[cfg(not(feature = "no_std"))]
150impl From<std::io::Error> for FerrayError {
151 fn from(e: std::io::Error) -> Self {
152 Self::IoError {
153 message: e.to_string(),
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn error_display_shape_mismatch() {
164 let e = FerrayError::shape_mismatch("expected (3,4), got (3,5)");
165 assert!(e.to_string().contains("expected (3,4), got (3,5)"));
166 }
167
168 #[test]
169 fn error_display_axis_out_of_bounds() {
170 let e = FerrayError::axis_out_of_bounds(5, 3);
171 assert!(e.to_string().contains("axis 5"));
172 assert!(e.to_string().contains("3 dimensions"));
173 }
174
175 #[test]
176 fn error_display_broadcast_failure() {
177 let e = FerrayError::broadcast_failure(&[4, 3], &[2, 5]);
178 let s = e.to_string();
179 assert!(s.contains("[4, 3]"));
180 assert!(s.contains("[2, 5]"));
181 }
182
183 #[test]
184 fn error_is_non_exhaustive() {
185 let e = FerrayError::invalid_value("test");
189 assert!(matches!(e, FerrayError::InvalidValue { .. }));
190
191 let e2 = FerrayError::shape_mismatch("bad shape");
192 assert!(matches!(e2, FerrayError::ShapeMismatch { .. }));
193 }
194
195 #[test]
196 fn from_io_error() {
197 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
198 let ferray_err: FerrayError = io_err.into();
199 assert!(ferray_err.to_string().contains("file missing"));
200 }
201}