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