1use serde_json::Value;
7use std::error::Error;
8use std::fmt;
9use std::path::PathBuf;
10use std::time::Instant;
11
12#[derive(Debug)]
14pub enum HtmlGenerationError {
15 FileDiscoveryError {
17 directory: String,
19 base_name: String,
21 source: Box<dyn Error + Send + Sync>,
23 recovery_suggestions: Vec<String>,
25 },
26 FileLoadingError {
28 file_path: PathBuf,
30 file_type: String,
32 file_size: usize,
34 source: Box<dyn Error + Send + Sync>,
36 recoverable: bool,
38 recovery_suggestions: Vec<String>,
40 },
41 JsonParsingError {
43 file_path: PathBuf,
45 line_number: Option<usize>,
47 column_number: Option<usize>,
49 parsing_error: String,
51 content_snippet: Option<String>,
53 recovery_suggestions: Vec<String>,
55 },
56 ValidationError {
58 file_path: PathBuf,
60 validation_type: String,
62 validation_error: String,
64 expected_structure: String,
66 actual_structure: String,
68 recovery_suggestions: Vec<String>,
70 },
71 NormalizationError {
73 stage: String,
75 processed_count: usize,
77 total_count: usize,
79 source: Box<dyn Error + Send + Sync>,
81 partial_data_available: bool,
83 recovery_suggestions: Vec<String>,
85 },
86 TemplateError {
88 stage: String,
90 processed_size: usize,
92 source: Box<dyn Error + Send + Sync>,
94 recovery_suggestions: Vec<String>,
96 },
97 MemoryLimitError {
99 current_usage: usize,
101 memory_limit: usize,
103 operation: String,
105 recovery_suggestions: Vec<String>,
107 },
108 MultipleErrors {
110 errors: Vec<HtmlGenerationError>,
112 can_continue: bool,
114 recovery_suggestions: Vec<String>,
116 },
117}
118
119impl fmt::Display for HtmlGenerationError {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self {
122 HtmlGenerationError::FileDiscoveryError {
123 directory,
124 base_name,
125 source,
126 recovery_suggestions,
127 } => {
128 writeln!(f, "ā File Discovery Error")?;
129 writeln!(f, " Directory: {directory}")?;
130 writeln!(f, " Base name: {base_name}")?;
131 writeln!(f, " Error: {source}")?;
132 if !recovery_suggestions.is_empty() {
133 writeln!(f, " š” Suggestions:")?;
134 for suggestion in recovery_suggestions {
135 writeln!(f, " ⢠{suggestion}")?;
136 }
137 }
138 Ok(())
139 }
140 HtmlGenerationError::FileLoadingError {
141 file_path,
142 file_type,
143 file_size,
144 source,
145 recoverable,
146 recovery_suggestions,
147 } => {
148 writeln!(f, "ā File Loading Error")?;
149 writeln!(f, " File: {}", file_path.display())?;
150 writeln!(f, " Type: {file_type}")?;
151 writeln!(f, " Size: {:.1} KB", *file_size as f64 / 1024.0)?;
152 writeln!(f, " Error: {source}")?;
153 writeln!(
154 f,
155 " Recoverable: {}",
156 if *recoverable { "Yes" } else { "No" }
157 )?;
158 if !recovery_suggestions.is_empty() {
159 writeln!(f, " š” Suggestions:")?;
160 for suggestion in recovery_suggestions {
161 writeln!(f, " ⢠{suggestion}")?;
162 }
163 }
164 Ok(())
165 }
166 HtmlGenerationError::JsonParsingError {
167 file_path,
168 line_number,
169 column_number,
170 parsing_error,
171 content_snippet,
172 recovery_suggestions,
173 } => {
174 writeln!(f, "ā JSON Parsing Error")?;
175 writeln!(f, " File: {}", file_path.display())?;
176 if let Some(line) = line_number {
177 write!(f, " Location: line {line}")?;
178 if let Some(col) = column_number {
179 write!(f, ", column {col}")?;
180 }
181 writeln!(f)?;
182 }
183 writeln!(f, " Error: {parsing_error}")?;
184 if let Some(snippet) = content_snippet {
185 writeln!(f, " Context:")?;
186 writeln!(f, " {snippet}")?;
187 }
188 if !recovery_suggestions.is_empty() {
189 writeln!(f, " š” Suggestions:")?;
190 for suggestion in recovery_suggestions {
191 writeln!(f, " ⢠{suggestion}")?;
192 }
193 }
194 Ok(())
195 }
196 HtmlGenerationError::ValidationError {
197 file_path,
198 validation_type,
199 validation_error,
200 expected_structure,
201 actual_structure,
202 recovery_suggestions,
203 } => {
204 writeln!(f, "ā Data Validation Error")?;
205 writeln!(f, " File: {}", file_path.display())?;
206 writeln!(f, " Validation: {validation_type}")?;
207 writeln!(f, " Error: {validation_error}")?;
208 writeln!(f, " Expected: {expected_structure}")?;
209 writeln!(f, " Found: {actual_structure}")?;
210 if !recovery_suggestions.is_empty() {
211 writeln!(f, " š” Suggestions:")?;
212 for suggestion in recovery_suggestions {
213 writeln!(f, " ⢠{suggestion}")?;
214 }
215 }
216 Ok(())
217 }
218 HtmlGenerationError::NormalizationError {
219 stage,
220 processed_count,
221 total_count,
222 source,
223 partial_data_available,
224 recovery_suggestions,
225 } => {
226 writeln!(f, "ā Data Normalization Error")?;
227 writeln!(f, " Stage: {stage}")?;
228 writeln!(
229 f,
230 " Progress: {processed_count}/{total_count} ({:.1}%)",
231 (*processed_count as f64 / *total_count as f64) * 100.0
232 )?;
233 writeln!(f, " Error: {source}")?;
234 writeln!(
235 f,
236 " Partial data available: {}",
237 if *partial_data_available { "Yes" } else { "No" }
238 )?;
239 if !recovery_suggestions.is_empty() {
240 writeln!(f, " š” Suggestions:")?;
241 for suggestion in recovery_suggestions {
242 writeln!(f, " ⢠{suggestion}")?;
243 }
244 }
245 Ok(())
246 }
247 HtmlGenerationError::TemplateError {
248 stage,
249 processed_size,
250 source,
251 recovery_suggestions,
252 } => {
253 writeln!(f, "ā Template Generation Error")?;
254 writeln!(f, " Stage: {stage}")?;
255 writeln!(f, " Processed: {:.1} KB", *processed_size as f64 / 1024.0)?;
256 writeln!(f, " Error: {source}")?;
257 if !recovery_suggestions.is_empty() {
258 writeln!(f, " š” Suggestions:")?;
259 for suggestion in recovery_suggestions {
260 writeln!(f, " ⢠{suggestion}")?;
261 }
262 }
263 Ok(())
264 }
265 HtmlGenerationError::MemoryLimitError {
266 current_usage,
267 memory_limit,
268 operation,
269 recovery_suggestions,
270 } => {
271 writeln!(f, "ā Memory Limit Exceeded")?;
272 writeln!(
273 f,
274 " Current usage: {:.1} MB",
275 *current_usage as f64 / 1024.0 / 1024.0
276 )?;
277 writeln!(
278 f,
279 " Memory limit: {:.1} MB",
280 *memory_limit as f64 / 1024.0 / 1024.0
281 )?;
282 writeln!(f, " Operation: {operation}")?;
283 writeln!(
284 f,
285 " Usage: {:.1}%",
286 (*current_usage as f64 / *memory_limit as f64) * 100.0
287 )?;
288 if !recovery_suggestions.is_empty() {
289 writeln!(f, " š” Suggestions:")?;
290 for suggestion in recovery_suggestions {
291 writeln!(f, " ⢠{suggestion}")?;
292 }
293 }
294 Ok(())
295 }
296 HtmlGenerationError::MultipleErrors {
297 errors,
298 can_continue,
299 recovery_suggestions,
300 } => {
301 writeln!(f, "ā Multiple Errors Occurred ({} errors)", errors.len())?;
302 writeln!(
303 f,
304 " Can continue: {}",
305 if *can_continue { "Yes" } else { "No" }
306 )?;
307 for (i, error) in errors.iter().enumerate() {
308 writeln!(f, " Error {}: {}", i + 1, error)?;
309 }
310 if !recovery_suggestions.is_empty() {
311 writeln!(f, " š” Suggestions:")?;
312 for suggestion in recovery_suggestions {
313 writeln!(f, " ⢠{suggestion}")?;
314 }
315 }
316 Ok(())
317 }
318 }
319 }
320}
321
322impl Error for HtmlGenerationError {
323 fn source(&self) -> Option<&(dyn Error + 'static)> {
324 match self {
325 HtmlGenerationError::FileDiscoveryError { source, .. } => Some(source.as_ref()),
326 HtmlGenerationError::FileLoadingError { source, .. } => Some(source.as_ref()),
327 HtmlGenerationError::NormalizationError { source, .. } => Some(source.as_ref()),
328 HtmlGenerationError::TemplateError { source, .. } => Some(source.as_ref()),
329 _ => None,
330 }
331 }
332}
333
334#[derive(Debug, Clone)]
336pub struct ErrorRecoveryContext {
337 pub attempt_recovery: bool,
339 pub max_retries: usize,
341 pub allow_partial_data: bool,
343 pub use_fallbacks: bool,
345 pub verbose_errors: bool,
347}
348
349impl Default for ErrorRecoveryContext {
350 fn default() -> Self {
351 Self {
352 attempt_recovery: true,
353 max_retries: 3,
354 allow_partial_data: true,
355 use_fallbacks: true,
356 verbose_errors: false,
357 }
358 }
359}
360
361#[derive(Debug, Default)]
363pub struct ErrorRecoveryStats {
364 pub total_errors: usize,
366 pub recovered_errors: usize,
368 pub fallback_errors: usize,
370 pub unrecoverable_errors: usize,
372 pub retry_attempts: usize,
374 pub recovery_time_ms: u64,
376}
377
378pub struct HtmlErrorHandler {
380 recovery_context: ErrorRecoveryContext,
382 stats: ErrorRecoveryStats,
384 start_time: Option<Instant>,
386}
387
388impl HtmlErrorHandler {
389 pub fn new() -> Self {
391 Self {
392 recovery_context: ErrorRecoveryContext::default(),
393 stats: ErrorRecoveryStats::default(),
394 start_time: None,
395 }
396 }
397
398 pub fn with_context(context: ErrorRecoveryContext) -> Self {
400 Self {
401 recovery_context: context,
402 stats: ErrorRecoveryStats::default(),
403 start_time: None,
404 }
405 }
406
407 pub fn handle_file_discovery_error(
409 &mut self,
410 directory: &str,
411 base_name: &str,
412 error: Box<dyn Error + Send + Sync>,
413 ) -> Result<Vec<String>, Box<HtmlGenerationError>> {
414 self.start_recovery_timing();
415 self.stats.total_errors += 1;
416
417 let recovery_suggestions = vec![
418 format!("Check if directory '{directory}' exists and is readable"),
419 "Verify the base name pattern matches your JSON files".to_string(),
420 "Ensure JSON files follow the naming convention: {base_name}_{type}.json".to_string(),
421 "Check file permissions for the directory".to_string(),
422 ];
423
424 if self.recovery_context.attempt_recovery {
425 if let Ok(alternatives) = self.find_alternative_directories(directory, base_name) {
427 if !alternatives.is_empty() {
428 self.stats.recovered_errors += 1;
429 self.end_recovery_timing();
430 return Ok(alternatives);
431 }
432 }
433 }
434
435 self.stats.unrecoverable_errors += 1;
436 self.end_recovery_timing();
437
438 Err(Box::new(HtmlGenerationError::FileDiscoveryError {
439 directory: directory.to_string(),
440 base_name: base_name.to_string(),
441 source: error,
442 recovery_suggestions,
443 }))
444 }
445
446 pub fn handle_file_loading_error(
448 &mut self,
449 file_path: PathBuf,
450 file_type: &str,
451 file_size: usize,
452 error: Box<dyn Error + Send + Sync>,
453 ) -> Result<Option<Value>, Box<HtmlGenerationError>> {
454 self.start_recovery_timing();
455 self.stats.total_errors += 1;
456
457 let recoverable = self.is_file_error_recoverable(&error);
458 let recovery_suggestions = self.get_file_loading_suggestions(file_type, file_size, &error);
459
460 if recoverable && self.recovery_context.attempt_recovery {
461 for attempt in 1..=self.recovery_context.max_retries {
463 self.stats.retry_attempts += 1;
464
465 if let Ok(recovered_data) = self.attempt_file_recovery(&file_path, file_type) {
466 tracing::info!(
467 "ā
Recovered file {} after {} attempts",
468 file_path.display(),
469 attempt
470 );
471 self.stats.recovered_errors += 1;
472 self.end_recovery_timing();
473 return Ok(Some(recovered_data));
474 }
475
476 std::thread::sleep(std::time::Duration::from_millis(100 * attempt as u64));
478 }
479
480 if self.recovery_context.use_fallbacks {
482 if let Ok(fallback_data) = self.get_fallback_data(file_type) {
483 tracing::info!("ā ļø Using fallback data for {}", file_type);
484 self.stats.fallback_errors += 1;
485 self.end_recovery_timing();
486 return Ok(Some(fallback_data));
487 }
488 }
489 }
490
491 self.stats.unrecoverable_errors += 1;
492 self.end_recovery_timing();
493
494 Err(Box::new(HtmlGenerationError::FileLoadingError {
495 file_path,
496 file_type: file_type.to_string(),
497 file_size,
498 source: error,
499 recoverable,
500 recovery_suggestions,
501 }))
502 }
503
504 pub fn handle_json_parsing_error(
506 &mut self,
507 file_path: PathBuf,
508 parsing_error: &str,
509 ) -> HtmlGenerationError {
510 self.start_recovery_timing();
511 self.stats.total_errors += 1;
512
513 let (line_number, column_number, content_snippet) =
514 self.extract_parsing_context(&file_path, parsing_error);
515
516 let recovery_suggestions = vec![
517 "Check JSON syntax for missing commas, brackets, or quotes".to_string(),
518 "Validate JSON structure using a JSON validator tool".to_string(),
519 "Ensure file encoding is UTF-8".to_string(),
520 "Check for trailing commas which are not valid in JSON".to_string(),
521 "Verify that all strings are properly quoted".to_string(),
522 ];
523
524 self.stats.unrecoverable_errors += 1;
525 self.end_recovery_timing();
526
527 HtmlGenerationError::JsonParsingError {
528 file_path,
529 line_number,
530 column_number,
531 parsing_error: parsing_error.to_string(),
532 content_snippet,
533 recovery_suggestions,
534 }
535 }
536
537 pub fn handle_validation_error(
539 &mut self,
540 file_path: PathBuf,
541 validation_type: &str,
542 validation_error: &str,
543 json_data: &Value,
544 ) -> HtmlGenerationError {
545 self.start_recovery_timing();
546 self.stats.total_errors += 1;
547
548 let expected_structure = self.get_expected_structure(validation_type);
549 let actual_structure = self.analyze_json_structure(json_data);
550
551 let recovery_suggestions =
552 self.get_validation_recovery_suggestions(validation_type, json_data);
553
554 self.stats.unrecoverable_errors += 1;
555 self.end_recovery_timing();
556
557 HtmlGenerationError::ValidationError {
558 file_path,
559 validation_type: validation_type.to_string(),
560 validation_error: validation_error.to_string(),
561 expected_structure,
562 actual_structure,
563 recovery_suggestions,
564 }
565 }
566
567 pub fn get_stats(&self) -> &ErrorRecoveryStats {
569 &self.stats
570 }
571
572 pub fn print_recovery_summary(&self) {
574 if self.stats.total_errors == 0 {
575 return;
576 }
577
578 tracing::info!("\nš Error Recovery Summary:");
579 tracing::info!(" Total errors: {}", self.stats.total_errors);
580 tracing::info!(" Recovered: {}", self.stats.recovered_errors);
581 tracing::info!(" Used fallbacks: {}", self.stats.fallback_errors);
582 tracing::info!(" Unrecoverable: {}", self.stats.unrecoverable_errors);
583 tracing::info!(" Retry attempts: {}", self.stats.retry_attempts);
584 tracing::info!(" Recovery time: {}ms", self.stats.recovery_time_ms);
585
586 let success_rate = if self.stats.total_errors > 0 {
587 ((self.stats.recovered_errors + self.stats.fallback_errors) as f64
588 / self.stats.total_errors as f64)
589 * 100.0
590 } else {
591 100.0
592 };
593 tracing::info!(" Success rate: {:.1}%", success_rate);
594 }
595
596 fn start_recovery_timing(&mut self) {
599 self.start_time = Some(Instant::now());
600 }
601
602 fn end_recovery_timing(&mut self) {
603 if let Some(start) = self.start_time.take() {
604 self.stats.recovery_time_ms += start.elapsed().as_millis() as u64;
605 }
606 }
607
608 fn find_alternative_directories(
609 &self,
610 _directory: &str,
611 _base_name: &str,
612 ) -> Result<Vec<String>, Box<dyn Error>> {
613 Ok(vec![])
616 }
617
618 #[allow(clippy::borrowed_box)]
619 fn is_file_error_recoverable(&self, error: &Box<dyn Error + Send + Sync>) -> bool {
620 let error_str = error.to_string().to_lowercase();
621
622 error_str.contains("permission denied")
624 || error_str.contains("temporarily unavailable")
625 || error_str.contains("resource busy")
626 || error_str.contains("interrupted")
627 }
628
629 #[allow(clippy::borrowed_box)]
630 fn get_file_loading_suggestions(
631 &self,
632 file_type: &str,
633 file_size: usize,
634 error: &Box<dyn Error + Send + Sync>,
635 ) -> Vec<String> {
636 let mut suggestions = vec![];
637 let error_str = error.to_string().to_lowercase();
638
639 if error_str.contains("permission") {
640 suggestions.push("Check file permissions and ensure read access".to_string());
641 }
642
643 if error_str.contains("not found") {
644 suggestions.push(format!(
645 "Verify the {file_type} file exists in the expected location"
646 ));
647 suggestions
648 .push("Check the file naming convention matches the expected pattern".to_string());
649 }
650
651 if file_size > 100 * 1024 * 1024 {
652 suggestions.push("Consider using streaming mode for large files".to_string());
654 suggestions.push("Increase memory limits if processing large datasets".to_string());
655 }
656
657 suggestions.push("Retry the operation after a brief delay".to_string());
658 suggestions.push("Check available disk space and memory".to_string());
659
660 suggestions
661 }
662
663 fn attempt_file_recovery(
664 &self,
665 file_path: &PathBuf,
666 _file_type: &str,
667 ) -> Result<Value, Box<dyn Error>> {
668 let content = std::fs::read_to_string(file_path)?;
670 let json_value: Value = serde_json::from_str(&content)?;
671 Ok(json_value)
672 }
673
674 fn get_fallback_data(&self, file_type: &str) -> Result<Value, Box<dyn Error>> {
675 let fallback = match file_type {
677 "memory_analysis" => serde_json::json!({
678 "allocations": [],
679 "summary": {
680 "total_allocations": 0,
681 "active_allocations": 0,
682 "total_memory": 0
683 }
684 }),
685 "performance" => serde_json::json!({
686 "memory_performance": {
687 "active_memory": 0,
688 "peak_memory": 0,
689 "total_allocated": 0
690 },
691 "allocation_distribution": {}
692 }),
693 "unsafe_ffi" => serde_json::json!({
694 "summary": {
695 "unsafe_count": 0,
696 "ffi_count": 0,
697 "safety_violations": 0
698 },
699 "enhanced_ffi_data": [],
700 "boundary_events": []
701 }),
702 "lifetime" => serde_json::json!({
703 "lifecycle_events": []
704 }),
705 "complex_types" => serde_json::json!({
706 "categorized_types": {},
707 "generic_types": []
708 }),
709 _ => serde_json::json!({}),
710 };
711
712 Ok(fallback)
713 }
714
715 fn extract_parsing_context(
716 &self,
717 file_path: &PathBuf,
718 parsing_error: &str,
719 ) -> (Option<usize>, Option<usize>, Option<String>) {
720 let line_regex = regex::Regex::new(r"line (\d+)").ok();
722 let col_regex = regex::Regex::new(r"column (\d+)").ok();
723
724 let line_number: Option<usize> = line_regex
725 .and_then(|re| re.captures(parsing_error))
726 .and_then(|caps| caps.get(1))
727 .and_then(|m| m.as_str().parse().ok());
728
729 let column_number: Option<usize> = col_regex
730 .and_then(|re| re.captures(parsing_error))
731 .and_then(|caps| caps.get(1))
732 .and_then(|m| m.as_str().parse().ok());
733
734 let content_snippet =
736 if let (Some(line), Ok(content)) = (line_number, std::fs::read_to_string(file_path)) {
737 let lines: Vec<&str> = content.lines().collect();
738 if line > 0 && line <= lines.len() {
739 let start = (line.saturating_sub(3)).max(1);
740 let end = (line + 2).min(lines.len());
741 let snippet_lines: Vec<String> = (start..=end)
742 .map(|i| {
743 let marker = if i == line { ">>> " } else { " " };
744 format!(
745 "{marker}{i:3}: {}",
746 lines.get(i.saturating_sub(1)).unwrap_or(&"")
747 )
748 })
749 .collect();
750 Some(snippet_lines.join("\n"))
751 } else {
752 None
753 }
754 } else {
755 None
756 };
757
758 (line_number, column_number, content_snippet)
759 }
760
761 fn get_expected_structure(&self, validation_type: &str) -> String {
762 match validation_type {
763 "memory_analysis" => "Object with 'allocations' array and 'summary' object".to_string(),
764 "performance" => {
765 "Object with 'memory_performance' and 'allocation_distribution'".to_string()
766 }
767 "unsafe_ffi" => {
768 "Object with 'summary', 'enhanced_ffi_data', and 'boundary_events'".to_string()
769 }
770 "lifetime" => "Object with 'lifecycle_events' array".to_string(),
771 "complex_types" => "Object with 'categorized_types' and 'generic_types'".to_string(),
772 _ => "Valid JSON object or array".to_string(),
773 }
774 }
775
776 fn analyze_json_structure(&self, json_data: &Value) -> String {
777 match json_data {
778 Value::Object(obj) => {
779 let keys: Vec<String> = obj.keys().cloned().collect();
780 format!("Object with keys: [{}]", keys.join(", "))
781 }
782 Value::Array(arr) => {
783 format!("Array with {} elements", arr.len())
784 }
785 Value::String(_) => "String value".to_string(),
786 Value::Number(_) => "Number value".to_string(),
787 Value::Bool(_) => "Boolean value".to_string(),
788 Value::Null => "Null value".to_string(),
789 }
790 }
791
792 fn get_validation_recovery_suggestions(
793 &self,
794 validation_type: &str,
795 json_data: &Value,
796 ) -> Vec<String> {
797 let mut suggestions = vec![];
798
799 match validation_type {
800 "memory_analysis" => {
801 suggestions.push("Ensure the JSON contains an 'allocations' array".to_string());
802 suggestions.push("Add a 'summary' object with allocation statistics".to_string());
803 if let Value::Object(obj) = json_data {
804 if !obj.contains_key("allocations") {
805 suggestions.push("Add missing 'allocations' field as an array".to_string());
806 }
807 if !obj.contains_key("summary") {
808 suggestions.push("Add missing 'summary' field as an object".to_string());
809 }
810 }
811 }
812 "unsafe_ffi" => {
813 suggestions.push("Ensure the JSON contains 'enhanced_ffi_data' array".to_string());
814 suggestions
815 .push("Add 'boundary_events' array for FFI boundary tracking".to_string());
816 suggestions.push("Include 'summary' object with FFI statistics".to_string());
817 }
818 _ => {
819 suggestions
820 .push("Check the JSON structure matches the expected format".to_string());
821 suggestions
822 .push("Refer to the documentation for the correct data format".to_string());
823 }
824 }
825
826 suggestions.push("Validate JSON syntax and structure".to_string());
827 suggestions.push("Check for required fields and correct data types".to_string());
828
829 suggestions
830 }
831}
832
833impl Default for HtmlErrorHandler {
834 fn default() -> Self {
835 Self::new()
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842 use std::io;
843
844 #[test]
845 fn test_error_handler_creation() {
846 let handler = HtmlErrorHandler::new();
847 assert_eq!(handler.stats.total_errors, 0);
848 assert!(handler.recovery_context.attempt_recovery);
849 }
850
851 #[test]
852 fn test_file_loading_error_handling() {
853 let mut handler = HtmlErrorHandler::new();
854 let error = Box::new(io::Error::new(io::ErrorKind::NotFound, "File not found"));
855
856 let result = handler.handle_file_loading_error(
857 PathBuf::from("test.json"),
858 "memory_analysis",
859 1024,
860 error,
861 );
862
863 assert!(result.is_err());
864 assert_eq!(handler.stats.total_errors, 1);
865 }
866
867 #[test]
868 fn test_fallback_data_generation() {
869 let handler = HtmlErrorHandler::new();
870
871 let fallback = handler
872 .get_fallback_data("memory_analysis")
873 .expect("Failed to get fallback data");
874 assert!(fallback.is_object());
875 assert!(fallback.get("allocations").is_some());
876 assert!(fallback.get("summary").is_some());
877 }
878
879 #[test]
880 fn test_error_recovery_context() {
881 let context = ErrorRecoveryContext {
882 attempt_recovery: false,
883 max_retries: 1,
884 allow_partial_data: false,
885 use_fallbacks: false,
886 verbose_errors: true,
887 };
888
889 let handler = HtmlErrorHandler::with_context(context);
890 assert!(!handler.recovery_context.attempt_recovery);
891 assert_eq!(handler.recovery_context.max_retries, 1);
892 }
893}