1use crate::sdk_bridge::ErrorCode;
7use crate::RecognitionError;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::Arc;
11use thiserror::Error;
12use tracing::{error, warn};
13
14#[derive(Debug, Error)]
16pub enum ErrorBridgeError {
17 #[error("Error conversion failed: {0}")]
19 ConversionFailed(String),
20
21 #[error("Unknown error type: {0}")]
23 UnknownErrorType(String),
24
25 #[error("Error context missing: {0}")]
27 ContextMissing(String),
28}
29
30pub trait ToVoirsError {
32 fn to_voirs_error(&self) -> RecognitionError;
34
35 fn to_error_code(&self) -> ErrorCode;
37
38 fn error_context(&self) -> ErrorContext {
40 ErrorContext::default()
41 }
42}
43
44pub trait RecoverableError {
46 fn is_recoverable(&self) -> bool;
48
49 fn recovery_action(&self) -> Option<RecoveryAction>;
51
52 fn retry_delay_ms(&self) -> Option<u64> {
54 None
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ErrorContext {
61 pub operation: String,
63
64 pub component: String,
66
67 pub context_data: HashMap<String, String>,
69
70 pub timestamp: chrono::DateTime<chrono::Utc>,
72
73 pub thread_id: Option<String>,
75
76 pub stack_trace: Option<String>,
78}
79
80impl Default for ErrorContext {
81 fn default() -> Self {
82 Self {
83 operation: "unknown".to_string(),
84 component: "voirs-recognizer".to_string(),
85 context_data: HashMap::new(),
86 timestamp: chrono::Utc::now(),
87 thread_id: None,
88 stack_trace: None,
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
95pub enum RecoveryAction {
96 Retry,
98
99 UseFallback,
101
102 SkipAndContinue,
104
105 ResetAndRetry,
107
108 ReduceQualityAndRetry,
110
111 ClearCacheAndRetry,
113
114 ManualInterventionRequired,
116}
117
118pub struct ErrorReporter {
120 error_history: parking_lot::RwLock<Vec<ErrorReport>>,
122
123 error_counts: parking_lot::RwLock<HashMap<String, usize>>,
125
126 max_history_size: usize,
128
129 callbacks: parking_lot::RwLock<Vec<ErrorCallback>>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ErrorReport {
136 pub error_code: ErrorCode,
138
139 pub message: String,
141
142 pub context: ErrorContext,
144
145 pub recovery_attempted: Option<RecoveryAction>,
147
148 pub recovery_successful: bool,
150
151 pub severity: ErrorSeverity,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
157pub enum ErrorSeverity {
158 Debug,
160
161 Info,
163
164 Warning,
166
167 Error,
169
170 Critical,
172
173 Fatal,
175}
176
177pub type ErrorCallback = Arc<dyn Fn(&ErrorReport) + Send + Sync>;
179
180impl ErrorReporter {
181 #[must_use]
183 pub fn new() -> Self {
184 Self {
185 error_history: parking_lot::RwLock::new(Vec::new()),
186 error_counts: parking_lot::RwLock::new(HashMap::new()),
187 max_history_size: 1000,
188 callbacks: parking_lot::RwLock::new(Vec::new()),
189 }
190 }
191
192 pub fn report(&self, report: ErrorReport) {
194 match report.severity {
196 ErrorSeverity::Debug => tracing::debug!("Error: {}", report.message),
197 ErrorSeverity::Info => tracing::info!("Error: {}", report.message),
198 ErrorSeverity::Warning => warn!("Error: {}", report.message),
199 ErrorSeverity::Error => error!("Error: {}", report.message),
200 ErrorSeverity::Critical => error!("CRITICAL Error: {}", report.message),
201 ErrorSeverity::Fatal => error!("FATAL Error: {}", report.message),
202 }
203
204 {
206 let mut counts = self.error_counts.write();
207 let error_type = format!("{:?}", report.error_code);
208 *counts.entry(error_type).or_insert(0) += 1;
209 }
210
211 {
213 let callbacks = self.callbacks.read();
214 for callback in callbacks.iter() {
215 callback(&report);
216 }
217 }
218
219 {
221 let mut history = self.error_history.write();
222 history.push(report);
223
224 if history.len() > self.max_history_size {
226 let excess = history.len() - self.max_history_size;
227 history.drain(0..excess);
228 }
229 }
230 }
231
232 pub fn register_callback(&self, callback: ErrorCallback) {
234 self.callbacks.write().push(callback);
235 }
236
237 pub fn get_history(&self) -> Vec<ErrorReport> {
239 self.error_history.read().clone()
240 }
241
242 pub fn get_error_counts(&self) -> HashMap<String, usize> {
244 self.error_counts.read().clone()
245 }
246
247 pub fn clear_history(&self) {
249 self.error_history.write().clear();
250 }
251
252 pub fn get_error_rate(&self, window_minutes: u32) -> f64 {
254 let history = self.error_history.read();
255 let now = chrono::Utc::now();
256 let window_start = now - chrono::Duration::minutes(i64::from(window_minutes));
257
258 let recent_errors = history
259 .iter()
260 .filter(|r| r.context.timestamp > window_start)
261 .count();
262
263 recent_errors as f64 / f64::from(window_minutes)
264 }
265
266 pub fn get_errors_by_severity(&self, severity: ErrorSeverity) -> Vec<ErrorReport> {
268 self.error_history
269 .read()
270 .iter()
271 .filter(|r| r.severity == severity)
272 .cloned()
273 .collect()
274 }
275
276 pub fn get_most_common_errors(&self, limit: usize) -> Vec<(String, usize)> {
278 let counts = self.error_counts.read();
279 let mut counts_vec: Vec<_> = counts.iter().map(|(k, v)| (k.clone(), *v)).collect();
280 counts_vec.sort_by(|a, b| b.1.cmp(&a.1));
281 counts_vec.truncate(limit);
282 counts_vec
283 }
284}
285
286impl Default for ErrorReporter {
287 fn default() -> Self {
288 Self::new()
289 }
290}
291
292impl ToVoirsError for RecognitionError {
294 fn to_voirs_error(&self) -> RecognitionError {
295 match self {
298 RecognitionError::ModelLoadError { message, .. } => RecognitionError::ModelLoadError {
299 message: message.clone(),
300 source: None,
301 },
302 RecognitionError::ModelError { message, .. } => RecognitionError::ModelError {
303 message: message.clone(),
304 source: None,
305 },
306 RecognitionError::AudioProcessingError { message, .. } => {
307 RecognitionError::AudioProcessingError {
308 message: message.clone(),
309 source: None,
310 }
311 }
312 RecognitionError::TranscriptionError { message, .. } => {
313 RecognitionError::TranscriptionError {
314 message: message.clone(),
315 source: None,
316 }
317 }
318 RecognitionError::PhonemeRecognitionError { message, .. } => {
319 RecognitionError::PhonemeRecognitionError {
320 message: message.clone(),
321 source: None,
322 }
323 }
324 RecognitionError::AudioAnalysisError { message, .. } => {
325 RecognitionError::AudioAnalysisError {
326 message: message.clone(),
327 source: None,
328 }
329 }
330 RecognitionError::ConfigurationError { message } => {
331 RecognitionError::ConfigurationError {
332 message: message.clone(),
333 }
334 }
335 RecognitionError::FeatureNotSupported { feature } => {
336 RecognitionError::FeatureNotSupported {
337 feature: feature.clone(),
338 }
339 }
340 RecognitionError::InvalidInput { message } => RecognitionError::InvalidInput {
341 message: message.clone(),
342 },
343 RecognitionError::ResourceError { message, .. } => RecognitionError::ResourceError {
344 message: message.clone(),
345 source: None,
346 },
347 RecognitionError::UnsupportedFormat(msg) => {
348 RecognitionError::UnsupportedFormat(msg.clone())
349 }
350 RecognitionError::InvalidFormat(msg) => RecognitionError::InvalidFormat(msg.clone()),
351 RecognitionError::ModelNotFound {
352 model,
353 available,
354 suggestions,
355 } => RecognitionError::ModelNotFound {
356 model: model.clone(),
357 available: available.clone(),
358 suggestions: suggestions.clone(),
359 },
360 RecognitionError::LanguageNotSupported {
361 language,
362 supported,
363 suggestions,
364 } => RecognitionError::LanguageNotSupported {
365 language: language.clone(),
366 supported: supported.clone(),
367 suggestions: suggestions.clone(),
368 },
369 RecognitionError::DeviceNotAvailable {
370 device,
371 reason,
372 fallback,
373 } => RecognitionError::DeviceNotAvailable {
374 device: device.clone(),
375 reason: reason.clone(),
376 fallback: fallback.clone(),
377 },
378 RecognitionError::InsufficientMemory {
379 required_mb,
380 available_mb,
381 recommendation,
382 } => RecognitionError::InsufficientMemory {
383 required_mb: *required_mb,
384 available_mb: *available_mb,
385 recommendation: recommendation.clone(),
386 },
387 RecognitionError::RecognitionTimeout {
388 timeout_ms,
389 audio_duration_ms,
390 suggestion,
391 } => RecognitionError::RecognitionTimeout {
392 timeout_ms: *timeout_ms,
393 audio_duration_ms: *audio_duration_ms,
394 suggestion: suggestion.clone(),
395 },
396 RecognitionError::MemoryError { message, .. } => RecognitionError::MemoryError {
397 message: message.clone(),
398 source: None,
399 },
400 RecognitionError::TrainingError { message, .. } => RecognitionError::TrainingError {
401 message: message.clone(),
402 source: None,
403 },
404 RecognitionError::SynchronizationError { message } => {
405 RecognitionError::SynchronizationError {
406 message: message.clone(),
407 }
408 }
409 }
410 }
411
412 fn to_error_code(&self) -> ErrorCode {
413 match self {
414 RecognitionError::ModelLoadError { .. } => ErrorCode::ModelLoadError,
415 RecognitionError::ModelError { .. } => ErrorCode::InferenceError,
416 RecognitionError::AudioProcessingError { .. } => ErrorCode::AudioProcessingError,
417 RecognitionError::TranscriptionError { .. } => ErrorCode::InferenceError,
418 RecognitionError::PhonemeRecognitionError { .. } => ErrorCode::InferenceError,
419 RecognitionError::AudioAnalysisError { .. } => ErrorCode::AudioProcessingError,
420 RecognitionError::ConfigurationError { .. } => ErrorCode::ConfigError,
421 RecognitionError::FeatureNotSupported { .. } => ErrorCode::GenericError,
422 RecognitionError::InvalidInput { .. } => ErrorCode::GenericError,
423 RecognitionError::ResourceError { .. } => ErrorCode::ResourceExhausted,
424 RecognitionError::UnsupportedFormat(_) => ErrorCode::GenericError,
425 RecognitionError::InvalidFormat(_) => ErrorCode::GenericError,
426 RecognitionError::ModelNotFound { .. } => ErrorCode::ModelLoadError,
427 RecognitionError::LanguageNotSupported { .. } => ErrorCode::GenericError,
428 RecognitionError::DeviceNotAvailable { .. } => ErrorCode::GpuError,
429 RecognitionError::InsufficientMemory { .. } => ErrorCode::MemoryError,
430 RecognitionError::RecognitionTimeout { .. } => ErrorCode::TimeoutError,
431 RecognitionError::MemoryError { .. } => ErrorCode::MemoryError,
432 RecognitionError::TrainingError { .. } => ErrorCode::InferenceError,
433 RecognitionError::SynchronizationError { .. } => ErrorCode::GenericError,
434 }
435 }
436
437 fn error_context(&self) -> ErrorContext {
438 ErrorContext {
439 operation: "recognition".to_string(),
440 component: "voirs-recognizer".to_string(),
441 context_data: {
442 let mut data = HashMap::new();
443 data.insert("error_variant".to_string(), self.to_string());
444 data
445 },
446 timestamp: chrono::Utc::now(),
447 thread_id: Some(format!("{:?}", std::thread::current().id())),
448 stack_trace: None,
449 }
450 }
451}
452
453impl RecoverableError for RecognitionError {
455 fn is_recoverable(&self) -> bool {
456 match self {
457 RecognitionError::ModelLoadError { .. } => false, RecognitionError::ModelError { .. } => true, RecognitionError::AudioProcessingError { .. } => true, RecognitionError::TranscriptionError { .. } => true, RecognitionError::PhonemeRecognitionError { .. } => true, RecognitionError::AudioAnalysisError { .. } => true, RecognitionError::ConfigurationError { .. } => false, RecognitionError::FeatureNotSupported { .. } => false, RecognitionError::InvalidInput { .. } => false, RecognitionError::ResourceError { .. } => true, RecognitionError::UnsupportedFormat(_) => false, RecognitionError::InvalidFormat(_) => false, RecognitionError::ModelNotFound { .. } => false, RecognitionError::LanguageNotSupported { .. } => false, RecognitionError::DeviceNotAvailable { .. } => true, RecognitionError::InsufficientMemory { .. } => true, RecognitionError::RecognitionTimeout { .. } => true, RecognitionError::MemoryError { .. } => true, RecognitionError::TrainingError { .. } => true, RecognitionError::SynchronizationError { .. } => false, }
478 }
479
480 fn recovery_action(&self) -> Option<RecoveryAction> {
481 match self {
482 RecognitionError::ModelError { .. } => Some(RecoveryAction::Retry),
483 RecognitionError::AudioProcessingError { .. } => Some(RecoveryAction::UseFallback),
484 RecognitionError::TranscriptionError { .. } => Some(RecoveryAction::Retry),
485 RecognitionError::PhonemeRecognitionError { .. } => Some(RecoveryAction::Retry),
486 RecognitionError::AudioAnalysisError { .. } => Some(RecoveryAction::UseFallback),
487 RecognitionError::ResourceError { .. } => Some(RecoveryAction::Retry),
488 RecognitionError::DeviceNotAvailable { .. } => Some(RecoveryAction::UseFallback),
489 RecognitionError::InsufficientMemory { .. } => {
490 Some(RecoveryAction::ReduceQualityAndRetry)
491 }
492 RecognitionError::RecognitionTimeout { .. } => {
493 Some(RecoveryAction::ReduceQualityAndRetry)
494 }
495 RecognitionError::MemoryError { .. } => Some(RecoveryAction::ReduceQualityAndRetry),
496 RecognitionError::TrainingError { .. } => Some(RecoveryAction::Retry),
497 _ => None,
498 }
499 }
500
501 fn retry_delay_ms(&self) -> Option<u64> {
502 match self {
503 RecognitionError::ModelError { .. } => Some(1000), RecognitionError::AudioProcessingError { .. } => Some(500), RecognitionError::TranscriptionError { .. } => Some(1000), RecognitionError::PhonemeRecognitionError { .. } => Some(1000), RecognitionError::AudioAnalysisError { .. } => Some(500), RecognitionError::ResourceError { .. } => Some(2000), RecognitionError::DeviceNotAvailable { .. } => Some(500), RecognitionError::InsufficientMemory { .. } => Some(1000), RecognitionError::RecognitionTimeout { .. } => Some(5000), RecognitionError::MemoryError { .. } => Some(1000), RecognitionError::TrainingError { .. } => Some(2000), _ => None,
515 }
516 }
517}
518
519static ERROR_REPORTER: once_cell::sync::Lazy<ErrorReporter> =
521 once_cell::sync::Lazy::new(ErrorReporter::new);
522
523#[must_use]
525pub fn global_error_reporter() -> &'static ErrorReporter {
526 &ERROR_REPORTER
527}
528
529pub fn report_error(
531 error: &RecognitionError,
532 severity: ErrorSeverity,
533 recovery_attempted: Option<RecoveryAction>,
534 recovery_successful: bool,
535) {
536 let report = ErrorReport {
537 error_code: error.to_error_code(),
538 message: error.to_string(),
539 context: error.error_context(),
540 recovery_attempted,
541 recovery_successful,
542 severity,
543 };
544
545 global_error_reporter().report(report);
546}
547
548#[cfg(test)]
549mod tests {
550 use super::*;
551
552 #[test]
553 fn test_error_context_creation() {
554 let context = ErrorContext::default();
555 assert_eq!(context.component, "voirs-recognizer");
556 assert!(!context.operation.is_empty());
557 }
558
559 #[test]
560 fn test_error_reporter_creation() {
561 let reporter = ErrorReporter::new();
562 assert_eq!(reporter.get_history().len(), 0);
563 assert_eq!(reporter.get_error_counts().len(), 0);
564 }
565
566 #[test]
567 fn test_error_reporting() {
568 let reporter = ErrorReporter::new();
569
570 let report = ErrorReport {
571 error_code: ErrorCode::InferenceError,
572 message: "Test error".to_string(),
573 context: ErrorContext::default(),
574 recovery_attempted: None,
575 recovery_successful: false,
576 severity: ErrorSeverity::Error,
577 };
578
579 reporter.report(report);
580
581 let history = reporter.get_history();
582 assert_eq!(history.len(), 1);
583 assert_eq!(history[0].message, "Test error");
584 }
585
586 #[test]
587 fn test_error_counts() {
588 let reporter = ErrorReporter::new();
589
590 for _ in 0..3 {
591 let report = ErrorReport {
592 error_code: ErrorCode::InferenceError,
593 message: "Test error".to_string(),
594 context: ErrorContext::default(),
595 recovery_attempted: None,
596 recovery_successful: false,
597 severity: ErrorSeverity::Error,
598 };
599 reporter.report(report);
600 }
601
602 let counts = reporter.get_error_counts();
603 assert_eq!(counts.get("InferenceError"), Some(&3));
604 }
605
606 #[test]
607 fn test_error_rate_calculation() {
608 let reporter = ErrorReporter::new();
609
610 for _ in 0..5 {
611 let report = ErrorReport {
612 error_code: ErrorCode::InferenceError,
613 message: "Test error".to_string(),
614 context: ErrorContext::default(),
615 recovery_attempted: None,
616 recovery_successful: false,
617 severity: ErrorSeverity::Error,
618 };
619 reporter.report(report);
620 }
621
622 let rate = reporter.get_error_rate(1);
623 assert!(rate >= 5.0);
624 }
625
626 #[test]
627 fn test_errors_by_severity() {
628 let reporter = ErrorReporter::new();
629
630 let report1 = ErrorReport {
631 error_code: ErrorCode::InferenceError,
632 message: "Error 1".to_string(),
633 context: ErrorContext::default(),
634 recovery_attempted: None,
635 recovery_successful: false,
636 severity: ErrorSeverity::Error,
637 };
638
639 let report2 = ErrorReport {
640 error_code: ErrorCode::ConfigError,
641 message: "Error 2".to_string(),
642 context: ErrorContext::default(),
643 recovery_attempted: None,
644 recovery_successful: false,
645 severity: ErrorSeverity::Critical,
646 };
647
648 reporter.report(report1);
649 reporter.report(report2);
650
651 let errors = reporter.get_errors_by_severity(ErrorSeverity::Error);
652 assert_eq!(errors.len(), 1);
653 assert_eq!(errors[0].message, "Error 1");
654 }
655
656 #[test]
657 fn test_recognition_error_conversion() {
658 let error = RecognitionError::ModelError {
659 message: "Test".to_string(),
660 source: None,
661 };
662 assert_eq!(error.to_error_code(), ErrorCode::InferenceError);
663 assert!(error.is_recoverable());
664 assert_eq!(error.recovery_action(), Some(RecoveryAction::Retry));
665 }
666
667 #[test]
668 fn test_most_common_errors() {
669 let reporter = ErrorReporter::new();
670
671 for _ in 0..5 {
673 reporter.report(ErrorReport {
674 error_code: ErrorCode::InferenceError,
675 message: "Inference error".to_string(),
676 context: ErrorContext::default(),
677 recovery_attempted: None,
678 recovery_successful: false,
679 severity: ErrorSeverity::Error,
680 });
681 }
682
683 for _ in 0..3 {
684 reporter.report(ErrorReport {
685 error_code: ErrorCode::ConfigError,
686 message: "Config error".to_string(),
687 context: ErrorContext::default(),
688 recovery_attempted: None,
689 recovery_successful: false,
690 severity: ErrorSeverity::Error,
691 });
692 }
693
694 let most_common = reporter.get_most_common_errors(5);
695 assert_eq!(most_common.len(), 2);
696 assert_eq!(most_common[0].1, 5); assert_eq!(most_common[1].1, 3); }
699
700 #[test]
701 fn test_error_callback() {
702 let reporter = ErrorReporter::new();
703 let called = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
704 let called_clone = called.clone();
705
706 let callback: ErrorCallback = Arc::new(move |_report| {
707 called_clone.store(true, std::sync::atomic::Ordering::SeqCst);
708 });
709
710 reporter.register_callback(callback);
711
712 reporter.report(ErrorReport {
713 error_code: ErrorCode::InferenceError,
714 message: "Test".to_string(),
715 context: ErrorContext::default(),
716 recovery_attempted: None,
717 recovery_successful: false,
718 severity: ErrorSeverity::Error,
719 });
720
721 assert!(called.load(std::sync::atomic::Ordering::SeqCst));
722 }
723}