1use std::io;
8use std::num::{ParseFloatError, ParseIntError};
9use std::str::Utf8Error;
10
11use thiserror::Error;
12
13pub type Result<T> = std::result::Result<T, ValknutError>;
15
16#[derive(Error, Debug)]
18pub enum ValknutError {
19 #[error("I/O error: {message}")]
21 Io {
22 message: String,
24 #[source]
26 source: io::Error,
27 },
28
29 #[error("Configuration error: {message}")]
31 Config {
32 message: String,
34 field: Option<String>,
36 },
37
38 #[error("Parse error in {language}: {message}")]
40 Parse {
41 language: String,
43 message: String,
45 file_path: Option<String>,
47 line: Option<usize>,
49 column: Option<usize>,
51 },
52
53 #[error("Mathematical error: {message}")]
55 Math {
56 message: String,
58 context: Option<String>,
60 },
61
62 #[error("Graph analysis error: {message}")]
64 Graph {
65 message: String,
67 element: Option<String>,
69 },
70
71 #[error("LSH error: {message}")]
73 Lsh {
74 message: String,
76 parameters: Option<String>,
78 },
79
80 #[error("Pipeline error at stage '{stage}': {message}")]
82 Pipeline {
83 stage: String,
85 message: String,
87 processed_count: Option<usize>,
89 },
90
91 #[error("Cache error: {message}")]
93 Cache {
94 message: String,
96 key: Option<String>,
98 },
99
100 #[error("Serialization error: {message}")]
102 Serialization {
103 message: String,
105 data_type: Option<String>,
107 #[source]
109 source: Option<Box<dyn std::error::Error + Send + Sync>>,
110 },
111
112 #[error("Validation error: {message}")]
114 Validation {
115 message: String,
117 field: Option<String>,
119 expected: Option<String>,
121 actual: Option<String>,
123 },
124
125 #[error("Resource exhaustion: {message}")]
127 ResourceExhaustion {
128 message: String,
130 resource_type: String,
132 current_usage: Option<String>,
134 limit: Option<String>,
136 },
137
138 #[error("Concurrency error: {message}")]
140 Concurrency {
141 message: String,
143 thread_id: Option<String>,
145 },
146
147 #[error("Feature not available: {feature}")]
149 FeatureUnavailable {
150 feature: String,
152 reason: Option<String>,
154 },
155
156 #[error("Internal error: {message}")]
158 Internal {
159 message: String,
161 context: Option<String>,
163 },
164
165 #[error("Unsupported: {message}")]
167 Unsupported {
168 message: String,
170 },
171}
172
173impl ValknutError {
175 pub fn io(message: impl Into<String>, source: io::Error) -> Self {
177 Self::Io {
178 message: message.into(),
179 source,
180 }
181 }
182
183 pub fn config(message: impl Into<String>) -> Self {
185 Self::Config {
186 message: message.into(),
187 field: None,
188 }
189 }
190
191 pub fn config_field(message: impl Into<String>, field: impl Into<String>) -> Self {
193 Self::Config {
194 message: message.into(),
195 field: Some(field.into()),
196 }
197 }
198
199 pub fn parse(language: impl Into<String>, message: impl Into<String>) -> Self {
201 Self::Parse {
202 language: language.into(),
203 message: message.into(),
204 file_path: None,
205 line: None,
206 column: None,
207 }
208 }
209
210 pub fn parse_with_location(
212 language: impl Into<String>,
213 message: impl Into<String>,
214 file_path: impl Into<String>,
215 line: Option<usize>,
216 column: Option<usize>,
217 ) -> Self {
218 Self::Parse {
219 language: language.into(),
220 message: message.into(),
221 file_path: Some(file_path.into()),
222 line,
223 column,
224 }
225 }
226
227 pub fn math(message: impl Into<String>) -> Self {
229 Self::Math {
230 message: message.into(),
231 context: None,
232 }
233 }
234
235 pub fn math_with_context(message: impl Into<String>, context: impl Into<String>) -> Self {
237 Self::Math {
238 message: message.into(),
239 context: Some(context.into()),
240 }
241 }
242
243 pub fn graph(message: impl Into<String>) -> Self {
245 Self::Graph {
246 message: message.into(),
247 element: None,
248 }
249 }
250
251 pub fn lsh(message: impl Into<String>) -> Self {
253 Self::Lsh {
254 message: message.into(),
255 parameters: None,
256 }
257 }
258
259 pub fn pipeline(stage: impl Into<String>, message: impl Into<String>) -> Self {
261 Self::Pipeline {
262 stage: stage.into(),
263 message: message.into(),
264 processed_count: None,
265 }
266 }
267
268 pub fn validation(message: impl Into<String>) -> Self {
270 Self::Validation {
271 message: message.into(),
272 field: None,
273 expected: None,
274 actual: None,
275 }
276 }
277
278 pub fn feature_unavailable(feature: impl Into<String>, reason: impl Into<String>) -> Self {
280 Self::FeatureUnavailable {
281 feature: feature.into(),
282 reason: Some(reason.into()),
283 }
284 }
285
286 pub fn internal(message: impl Into<String>) -> Self {
288 Self::Internal {
289 message: message.into(),
290 context: None,
291 }
292 }
293
294 pub fn unsupported(message: impl Into<String>) -> Self {
296 Self::Unsupported {
297 message: message.into(),
298 }
299 }
300
301 pub fn with_context(mut self, context: impl Into<String>) -> Self {
303 match &mut self {
304 Self::Math { context: ctx, .. } | Self::Internal { context: ctx, .. } => {
305 *ctx = Some(context.into());
306 }
307 _ => {} }
309 self
310 }
311}
312
313impl From<io::Error> for ValknutError {
317 fn from(err: io::Error) -> Self {
319 Self::io("I/O operation failed", err)
320 }
321}
322
323impl From<serde_json::Error> for ValknutError {
325 fn from(err: serde_json::Error) -> Self {
327 Self::Serialization {
328 message: format!("JSON serialization failed: {err}"),
329 data_type: Some("JSON".to_string()),
330 source: Some(Box::new(err)),
331 }
332 }
333}
334
335impl From<serde_yaml::Error> for ValknutError {
337 fn from(err: serde_yaml::Error) -> Self {
339 Self::Serialization {
340 message: format!("YAML serialization failed: {err}"),
341 data_type: Some("YAML".to_string()),
342 source: Some(Box::new(err)),
343 }
344 }
345}
346
347impl From<ParseIntError> for ValknutError {
349 fn from(err: ParseIntError) -> Self {
351 Self::validation(format!("Invalid integer: {err}"))
352 }
353}
354
355impl From<ParseFloatError> for ValknutError {
357 fn from(err: ParseFloatError) -> Self {
359 Self::validation(format!("Invalid float: {err}"))
360 }
361}
362
363impl From<Utf8Error> for ValknutError {
365 fn from(err: Utf8Error) -> Self {
367 Self::parse("unknown", format!("UTF-8 encoding error: {err}"))
368 }
369}
370
371#[macro_export]
373macro_rules! valknut_error {
374 ($kind:ident, $msg:expr) => {
375 $crate::core::errors::ValknutError::$kind($msg.to_string())
376 };
377 ($kind:ident, $msg:expr, $($arg:tt)*) => {
378 $crate::core::errors::ValknutError::$kind(format!($msg, $($arg)*))
379 };
380}
381
382pub trait ResultExt<T> {
384 fn with_context<F>(self, f: F) -> Result<T>
386 where
387 F: FnOnce() -> String;
388
389 fn context(self, msg: &'static str) -> Result<T>;
391}
392
393impl<T, E> ResultExt<T> for std::result::Result<T, E>
395where
396 E: Into<ValknutError>,
397{
398 fn with_context<F>(self, f: F) -> Result<T>
400 where
401 F: FnOnce() -> String,
402 {
403 self.map_err(|e| e.into().with_context(f()))
404 }
405
406 fn context(self, msg: &'static str) -> Result<T> {
408 self.map_err(|e| e.into().with_context(msg))
409 }
410}
411
412impl ValknutError {
416 pub fn map_io(message: impl Into<String>) -> impl FnOnce(std::io::Error) -> Self {
418 move |e| Self::io(message, e)
419 }
420
421 pub fn map_serialization(
423 operation: impl Into<String>,
424 ) -> impl FnOnce(Box<dyn std::error::Error + Send + Sync>) -> Self {
425 move |e| Self::Serialization {
426 message: format!("Serialization failed during {}: {}", operation.into(), e),
427 data_type: None,
428 source: Some(e),
429 }
430 }
431
432 pub fn map_json_parse(context: impl Into<String>) -> impl FnOnce(serde_json::Error) -> Self {
434 move |e| Self::internal(format!("Failed to parse JSON {}: {}", context.into(), e))
435 }
436
437 pub fn map_internal(
439 operation: impl Into<String>,
440 ) -> impl FnOnce(Box<dyn std::error::Error + Send + Sync>) -> Self {
441 move |e| Self::internal(format!("Internal error during {}: {}", operation.into(), e))
442 }
443
444 pub fn map_generic<E>(operation: impl Into<String>) -> impl FnOnce(E) -> Self
446 where
447 E: std::fmt::Display,
448 {
449 move |e| Self::internal(format!("Failed during {}: {}", operation.into(), e))
450 }
451}
452
453pub trait ValknutResultExt<T> {
455 fn map_io_err(self, message: impl Into<String>) -> Result<T>;
457
458 fn map_json_err(self, context: impl Into<String>) -> Result<T>;
460
461 fn map_generic_err(self, operation: impl Into<String>) -> Result<T>;
463}
464
465impl<T, E> ValknutResultExt<T> for std::result::Result<T, E>
467where
468 E: std::fmt::Display,
469{
470 fn map_io_err(self, message: impl Into<String>) -> Result<T> {
472 self.map_err(|e| ValknutError::internal(format!("{}: {}", message.into(), e)))
473 }
474
475 fn map_json_err(self, context: impl Into<String>) -> Result<T> {
477 self.map_err(|e| ValknutError::internal(format!("JSON error in {}: {}", context.into(), e)))
478 }
479
480 fn map_generic_err(self, operation: impl Into<String>) -> Result<T> {
482 self.map_err(|e| {
483 ValknutError::internal(format!("Failed during {}: {}", operation.into(), e))
484 })
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491 use std::num::{ParseFloatError, ParseIntError};
492
493 #[test]
494 fn test_error_creation() {
495 let err = ValknutError::config("Invalid configuration");
496 assert!(matches!(err, ValknutError::Config { .. }));
497
498 let err = ValknutError::parse("python", "Syntax error");
499 assert!(matches!(err, ValknutError::Parse { .. }));
500 }
501
502 #[test]
503 fn test_error_with_context() {
504 let err =
505 ValknutError::internal("Something went wrong").with_context("During file processing");
506
507 if let ValknutError::Internal { context, .. } = err {
508 assert_eq!(context, Some("During file processing".to_string()));
509 } else {
510 panic!("Expected Internal error");
511 }
512 }
513
514 #[test]
515 fn test_result_extension() {
516 let result: std::result::Result<i32, std::io::Error> = Err(std::io::Error::new(
517 std::io::ErrorKind::NotFound,
518 "File not found",
519 ));
520
521 let valknut_result = result.context("Failed to read configuration file");
522 assert!(valknut_result.is_err());
523 }
524
525 #[test]
526 fn test_io_error_creation() {
527 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "Access denied");
528 let err = ValknutError::io("Failed to write file", io_err);
529
530 if let ValknutError::Io { message, source } = &err {
531 assert_eq!(message, "Failed to write file");
532 assert_eq!(source.kind(), std::io::ErrorKind::PermissionDenied);
533 } else {
534 panic!("Expected Io error");
535 }
536 }
537
538 #[test]
539 fn test_config_field_error() {
540 let err = ValknutError::config_field("Invalid value", "max_files");
541
542 if let ValknutError::Config { message, field } = err {
543 assert_eq!(message, "Invalid value");
544 assert_eq!(field, Some("max_files".to_string()));
545 } else {
546 panic!("Expected Config error");
547 }
548 }
549
550 #[test]
551 fn test_parse_with_location() {
552 let err = ValknutError::parse_with_location(
553 "rust",
554 "Missing semicolon",
555 "main.rs",
556 Some(42),
557 Some(10),
558 );
559
560 if let ValknutError::Parse {
561 language,
562 message,
563 file_path,
564 line,
565 column,
566 } = err
567 {
568 assert_eq!(language, "rust");
569 assert_eq!(message, "Missing semicolon");
570 assert_eq!(file_path, Some("main.rs".to_string()));
571 assert_eq!(line, Some(42));
572 assert_eq!(column, Some(10));
573 } else {
574 panic!("Expected Parse error");
575 }
576 }
577
578 #[test]
579 fn test_math_with_context() {
580 let err = ValknutError::math_with_context("Division by zero", "normalize_features");
581
582 if let ValknutError::Math { message, context } = err {
583 assert_eq!(message, "Division by zero");
584 assert_eq!(context, Some("normalize_features".to_string()));
585 } else {
586 panic!("Expected Math error");
587 }
588 }
589
590 #[test]
591 fn test_graph_error() {
592 let err = ValknutError::graph("Cycle detected");
593
594 if let ValknutError::Graph { message, element } = err {
595 assert_eq!(message, "Cycle detected");
596 assert_eq!(element, None);
597 } else {
598 panic!("Expected Graph error");
599 }
600 }
601
602 #[test]
603 fn test_lsh_error() {
604 let err = ValknutError::lsh("Invalid hash function");
605
606 if let ValknutError::Lsh {
607 message,
608 parameters,
609 } = err
610 {
611 assert_eq!(message, "Invalid hash function");
612 assert_eq!(parameters, None);
613 } else {
614 panic!("Expected Lsh error");
615 }
616 }
617
618 #[test]
619 fn test_pipeline_error() {
620 let err = ValknutError::pipeline("feature_extraction", "Timeout exceeded");
621
622 if let ValknutError::Pipeline {
623 stage,
624 message,
625 processed_count,
626 } = err
627 {
628 assert_eq!(stage, "feature_extraction");
629 assert_eq!(message, "Timeout exceeded");
630 assert_eq!(processed_count, None);
631 } else {
632 panic!("Expected Pipeline error");
633 }
634 }
635
636 #[test]
637 fn test_validation_error() {
638 let err = ValknutError::validation("Invalid range");
639
640 if let ValknutError::Validation {
641 message,
642 field,
643 expected,
644 actual,
645 } = err
646 {
647 assert_eq!(message, "Invalid range");
648 assert_eq!(field, None);
649 assert_eq!(expected, None);
650 assert_eq!(actual, None);
651 } else {
652 panic!("Expected Validation error");
653 }
654 }
655
656 #[test]
657 fn test_feature_unavailable() {
658 let err = ValknutError::feature_unavailable("SIMD operations", "CPU does not support AVX2");
659
660 if let ValknutError::FeatureUnavailable { feature, reason } = err {
661 assert_eq!(feature, "SIMD operations");
662 assert_eq!(reason, Some("CPU does not support AVX2".to_string()));
663 } else {
664 panic!("Expected FeatureUnavailable error");
665 }
666 }
667
668 #[test]
669 fn test_unsupported_error() {
670 let err = ValknutError::unsupported("Language not supported");
671
672 if let ValknutError::Unsupported { message } = err {
673 assert_eq!(message, "Language not supported");
674 } else {
675 panic!("Expected Unsupported error");
676 }
677 }
678
679 #[test]
680 fn test_from_io_error() {
681 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
682 let valknut_err: ValknutError = io_err.into();
683
684 assert!(matches!(valknut_err, ValknutError::Io { .. }));
685 }
686
687 #[test]
688 fn test_from_json_error() {
689 let json_err = serde_json::from_str::<i32>("invalid json").unwrap_err();
690 let valknut_err: ValknutError = json_err.into();
691
692 if let ValknutError::Serialization { data_type, .. } = valknut_err {
693 assert_eq!(data_type, Some("JSON".to_string()));
694 } else {
695 panic!("Expected Serialization error");
696 }
697 }
698
699 #[test]
700 fn test_from_yaml_error() {
701 let yaml_err = serde_yaml::from_str::<i32>("invalid: yaml: content").unwrap_err();
702 let valknut_err: ValknutError = yaml_err.into();
703
704 if let ValknutError::Serialization { data_type, .. } = valknut_err {
705 assert_eq!(data_type, Some("YAML".to_string()));
706 } else {
707 panic!("Expected Serialization error");
708 }
709 }
710
711 #[test]
712 fn test_from_parse_int_error() {
713 let parse_err = "not_a_number".parse::<i32>().unwrap_err();
714 let valknut_err: ValknutError = parse_err.into();
715
716 assert!(matches!(valknut_err, ValknutError::Validation { .. }));
717 }
718
719 #[test]
720 fn test_from_parse_float_error() {
721 let parse_err = "not_a_float".parse::<f64>().unwrap_err();
722 let valknut_err: ValknutError = parse_err.into();
723
724 assert!(matches!(valknut_err, ValknutError::Validation { .. }));
725 }
726
727 #[test]
728 fn test_from_utf8_error() {
729 let invalid_utf8 = vec![0, 159, 146, 150]; let utf8_err = std::str::from_utf8(&invalid_utf8).unwrap_err();
731 let valknut_err: ValknutError = utf8_err.into();
732
733 assert!(matches!(valknut_err, ValknutError::Parse { .. }));
734 }
735
736 #[test]
737 fn test_with_context_math_error() {
738 let mut err = ValknutError::math("Overflow occurred");
739 err = err.with_context("In statistical calculation");
740
741 if let ValknutError::Math { context, .. } = err {
742 assert_eq!(context, Some("In statistical calculation".to_string()));
743 } else {
744 panic!("Expected Math error with context");
745 }
746 }
747
748 #[test]
749 fn test_with_context_non_contextual_error() {
750 let err = ValknutError::config("Bad config");
751 let err_with_context = err.with_context("Should not change");
752
753 if let ValknutError::Config { message, .. } = err_with_context {
755 assert_eq!(message, "Bad config");
756 } else {
757 panic!("Expected Config error");
758 }
759 }
760
761 #[test]
762 fn test_result_ext_with_context() {
763 let result: std::result::Result<i32, std::io::Error> = Err(std::io::Error::new(
764 std::io::ErrorKind::InvalidInput,
765 "Bad input",
766 ));
767
768 let valknut_result = result.with_context(|| "Processing failed".to_string());
769 assert!(valknut_result.is_err());
770
771 let err = valknut_result.unwrap_err();
773 assert!(matches!(err, ValknutError::Io { .. }));
774 }
775
776 #[test]
777 fn test_error_display_formatting() {
778 let err = ValknutError::parse_with_location(
779 "python",
780 "Syntax error",
781 "test.py",
782 Some(10),
783 Some(5),
784 );
785 let display = format!("{}", err);
786 assert!(display.contains("Parse error in python"));
787 assert!(display.contains("Syntax error"));
788 }
789
790 #[test]
791 fn test_error_debug_formatting() {
792 let err = ValknutError::config_field("Invalid threshold", "complexity_max");
793 let debug = format!("{:?}", err);
794 assert!(debug.contains("Config"));
795 assert!(debug.contains("Invalid threshold"));
796 assert!(debug.contains("complexity_max"));
797 }
798}