1#[derive(Debug, Clone, thiserror::Error)]
10pub enum CoreError {
11 #[error("Invalid parameter '{name}': {message}")]
13 InvalidParameter { name: String, message: String },
14
15 #[error("Resource not found: {0}")]
17 NotFound(String),
18
19 #[error("Operation not supported: {0}")]
21 NotSupported(String),
22
23 #[error("Dimension mismatch: expected {expected}, got {actual}")]
25 DimensionMismatch { expected: usize, actual: usize },
26
27 #[error("Platform capability not available: {0}")]
29 CapabilityNotAvailable(String),
30
31 #[error("Memory allocation failed: {0}")]
33 MemoryError(String),
34
35 #[error("I/O error: {0}")]
37 IoError(String),
38
39 #[error("Serialization error: {0}")]
41 SerializationError(String),
42
43 #[error("Configuration error: {0}")]
45 ConfigError(String),
46
47 #[error("Operation timed out: {0}")]
49 Timeout(String),
50
51 #[error("Internal error: {0}")]
53 Internal(String),
54}
55
56impl From<std::io::Error> for CoreError {
57 fn from(err: std::io::Error) -> Self {
58 CoreError::IoError(err.to_string())
59 }
60}
61
62impl From<serde_json::Error> for CoreError {
63 fn from(err: serde_json::Error) -> Self {
64 CoreError::SerializationError(err.to_string())
65 }
66}
67
68pub type CoreResult<T> = Result<T, CoreError>;
70
71pub use crate::{OxirsError, Result as OxirsResult};
73
74pub mod validation {
76 use super::{CoreError, CoreResult};
77 use std::fmt;
78
79 pub fn check_positive<T>(value: T, name: &str) -> CoreResult<T>
81 where
82 T: PartialOrd + Default + fmt::Display + Copy,
83 {
84 if value <= T::default() {
85 Err(CoreError::InvalidParameter {
86 name: name.to_string(),
87 message: format!("Value must be positive, got {value}"),
88 })
89 } else {
90 Ok(value)
91 }
92 }
93
94 pub fn check_finite_f32(value: f32, name: &str) -> CoreResult<f32> {
96 if !value.is_finite() {
97 Err(CoreError::InvalidParameter {
98 name: name.to_string(),
99 message: format!("Value must be finite, got {value}"),
100 })
101 } else {
102 Ok(value)
103 }
104 }
105
106 pub fn check_finite_f64(value: f64, name: &str) -> CoreResult<f64> {
108 if !value.is_finite() {
109 Err(CoreError::InvalidParameter {
110 name: name.to_string(),
111 message: format!("Value must be finite, got {value}"),
112 })
113 } else {
114 Ok(value)
115 }
116 }
117
118 pub fn check_finite_array(values: &[f32]) -> CoreResult<()> {
120 for (i, &value) in values.iter().enumerate() {
121 if !value.is_finite() {
122 return Err(CoreError::InvalidParameter {
123 name: format!("array[{i}]"),
124 message: format!("Value must be finite, got {value}"),
125 });
126 }
127 }
128 Ok(())
129 }
130
131 pub fn check_dimensions(expected: usize, actual: usize, _context: &str) -> CoreResult<()> {
133 if expected != actual {
134 Err(CoreError::DimensionMismatch { expected, actual })
135 } else {
136 Ok(())
137 }
138 }
139
140 pub fn check_non_empty<T>(slice: &[T], name: &str) -> CoreResult<()> {
142 if slice.is_empty() {
143 Err(CoreError::InvalidParameter {
144 name: name.to_string(),
145 message: "Value must not be empty".to_string(),
146 })
147 } else {
148 Ok(())
149 }
150 }
151
152 pub fn check_non_empty_str(value: &str, name: &str) -> CoreResult<()> {
154 if value.is_empty() {
155 Err(CoreError::InvalidParameter {
156 name: name.to_string(),
157 message: "Value must not be empty".to_string(),
158 })
159 } else {
160 Ok(())
161 }
162 }
163
164 pub fn check_range<T>(value: T, min: T, max: T, name: &str) -> CoreResult<T>
166 where
167 T: PartialOrd + fmt::Display + Copy,
168 {
169 if value < min || value > max {
170 Err(CoreError::InvalidParameter {
171 name: name.to_string(),
172 message: format!("Value must be between {min} and {max}, got {value}"),
173 })
174 } else {
175 Ok(value)
176 }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::validation::*;
183 use super::*;
184
185 #[test]
186 fn test_core_error_display() {
187 let error = CoreError::InvalidParameter {
188 name: "test".to_string(),
189 message: "test message".to_string(),
190 };
191 assert_eq!(error.to_string(), "Invalid parameter 'test': test message");
192
193 let error = CoreError::NotFound("resource".to_string());
194 assert_eq!(error.to_string(), "Resource not found: resource");
195
196 let error = CoreError::DimensionMismatch {
197 expected: 3,
198 actual: 5,
199 };
200 assert_eq!(error.to_string(), "Dimension mismatch: expected 3, got 5");
201 }
202
203 #[test]
204 fn test_from_io_error() {
205 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
206 let core_error = CoreError::from(io_error);
207 match core_error {
208 CoreError::IoError(msg) => assert!(msg.contains("file not found")),
209 _ => panic!("Expected IoError"),
210 }
211 }
212
213 #[test]
214 fn test_from_serde_error() {
215 let serde_error = serde_json::from_str::<i32>("invalid").unwrap_err();
216 let core_error = CoreError::from(serde_error);
217 match core_error {
218 CoreError::SerializationError(_) => {} _ => panic!("Expected SerializationError"),
220 }
221 }
222
223 #[test]
224 fn test_check_positive() {
225 assert_eq!(
227 check_positive(5, "test").expect("value should be positive"),
228 5
229 );
230 assert_eq!(
231 check_positive(1.5f32, "test").expect("value should be positive"),
232 1.5f32
233 );
234
235 assert!(check_positive(0, "test").is_err());
237 assert!(check_positive(-1, "test").is_err());
238 assert!(check_positive(-1.5f32, "test").is_err());
239
240 let err = check_positive(0, "test_param").unwrap_err();
242 match err {
243 CoreError::InvalidParameter { name, message } => {
244 assert_eq!(name, "test_param");
245 assert!(message.contains("must be positive"));
246 }
247 _ => panic!("Expected InvalidParameter"),
248 }
249 }
250
251 #[test]
252 fn test_check_finite_f32() {
253 assert_eq!(
255 check_finite_f32(1.5, "test").expect("value should be finite f32"),
256 1.5
257 );
258 assert_eq!(
259 check_finite_f32(0.0, "test").expect("value should be finite f32"),
260 0.0
261 );
262 assert_eq!(
263 check_finite_f32(-1.5, "test").expect("value should be finite f32"),
264 -1.5
265 );
266
267 assert!(check_finite_f32(f32::INFINITY, "test").is_err());
269 assert!(check_finite_f32(f32::NEG_INFINITY, "test").is_err());
270 assert!(check_finite_f32(f32::NAN, "test").is_err());
271
272 let err = check_finite_f32(f32::INFINITY, "test_param").unwrap_err();
274 match err {
275 CoreError::InvalidParameter { name, message } => {
276 assert_eq!(name, "test_param");
277 assert!(message.contains("must be finite"));
278 }
279 _ => panic!("Expected InvalidParameter"),
280 }
281 }
282
283 #[test]
284 fn test_check_finite_f64() {
285 assert_eq!(
287 check_finite_f64(1.5, "test").expect("value should be finite f64"),
288 1.5
289 );
290 assert_eq!(
291 check_finite_f64(0.0, "test").expect("value should be finite f64"),
292 0.0
293 );
294 assert_eq!(
295 check_finite_f64(-1.5, "test").expect("value should be finite f64"),
296 -1.5
297 );
298
299 assert!(check_finite_f64(f64::INFINITY, "test").is_err());
301 assert!(check_finite_f64(f64::NEG_INFINITY, "test").is_err());
302 assert!(check_finite_f64(f64::NAN, "test").is_err());
303 }
304
305 #[test]
306 fn test_check_finite_array() {
307 let finite_array = [1.0, 2.5, -3.0, 0.0];
309 assert!(check_finite_array(&finite_array).is_ok());
310
311 assert!(check_finite_array(&[]).is_ok());
313
314 let inf_array = [1.0, f32::INFINITY, 3.0];
316 let err = check_finite_array(&inf_array).unwrap_err();
317 match err {
318 CoreError::InvalidParameter { name, message } => {
319 assert_eq!(name, "array[1]");
320 assert!(message.contains("must be finite"));
321 }
322 _ => panic!("Expected InvalidParameter"),
323 }
324
325 let nan_array = [1.0, f32::NAN, 3.0];
327 assert!(check_finite_array(&nan_array).is_err());
328 }
329
330 #[test]
331 fn test_check_dimensions() {
332 assert!(check_dimensions(5, 5, "test").is_ok());
334 assert!(check_dimensions(0, 0, "test").is_ok());
335
336 let err = check_dimensions(5, 3, "test").unwrap_err();
338 match err {
339 CoreError::DimensionMismatch { expected, actual } => {
340 assert_eq!(expected, 5);
341 assert_eq!(actual, 3);
342 }
343 _ => panic!("Expected DimensionMismatch"),
344 }
345 }
346
347 #[test]
348 fn test_check_non_empty() {
349 let data = [1, 2, 3];
351 assert!(check_non_empty(&data, "test").is_ok());
352
353 let empty: &[i32] = &[];
355 let err = check_non_empty(empty, "test_param").unwrap_err();
356 match err {
357 CoreError::InvalidParameter { name, message } => {
358 assert_eq!(name, "test_param");
359 assert!(message.contains("must not be empty"));
360 }
361 _ => panic!("Expected InvalidParameter"),
362 }
363 }
364
365 #[test]
366 fn test_check_non_empty_str() {
367 assert!(check_non_empty_str("hello", "test").is_ok());
369
370 let err = check_non_empty_str("", "test_param").unwrap_err();
372 match err {
373 CoreError::InvalidParameter { name, message } => {
374 assert_eq!(name, "test_param");
375 assert!(message.contains("must not be empty"));
376 }
377 _ => panic!("Expected InvalidParameter"),
378 }
379 }
380
381 #[test]
382 fn test_check_range() {
383 assert_eq!(
385 check_range(5, 1, 10, "test").expect("value should be in range"),
386 5
387 );
388 assert_eq!(
389 check_range(1, 1, 10, "test").expect("value should be in range"),
390 1
391 );
392 assert_eq!(
393 check_range(10, 1, 10, "test").expect("value should be in range"),
394 10
395 );
396 assert_eq!(
397 check_range(2.5f32, 1.0, 5.0, "test").expect("value should be in range"),
398 2.5f32
399 );
400
401 let err = check_range(0, 1, 10, "test_param").unwrap_err();
403 match err {
404 CoreError::InvalidParameter { name, message } => {
405 assert_eq!(name, "test_param");
406 assert!(message.contains("between 1 and 10"));
407 assert!(message.contains("got 0"));
408 }
409 _ => panic!("Expected InvalidParameter"),
410 }
411
412 let err = check_range(11, 1, 10, "test_param").unwrap_err();
414 match err {
415 CoreError::InvalidParameter { name, message } => {
416 assert_eq!(name, "test_param");
417 assert!(message.contains("between 1 and 10"));
418 assert!(message.contains("got 11"));
419 }
420 _ => panic!("Expected InvalidParameter"),
421 }
422 }
423
424 #[test]
425 fn test_error_variants() {
426 let error = CoreError::NotSupported("test operation".to_string());
427 assert_eq!(error.to_string(), "Operation not supported: test operation");
428
429 let error = CoreError::CapabilityNotAvailable("SIMD".to_string());
430 assert_eq!(error.to_string(), "Platform capability not available: SIMD");
431
432 let error = CoreError::MemoryError("allocation failed".to_string());
433 assert_eq!(
434 error.to_string(),
435 "Memory allocation failed: allocation failed"
436 );
437
438 let error = CoreError::ConfigError("invalid setting".to_string());
439 assert_eq!(error.to_string(), "Configuration error: invalid setting");
440
441 let error = CoreError::Timeout("5 seconds".to_string());
442 assert_eq!(error.to_string(), "Operation timed out: 5 seconds");
443
444 let error = CoreError::Internal("unexpected state".to_string());
445 assert_eq!(error.to_string(), "Internal error: unexpected state");
446 }
447}