memscope_rs/cli/commands/html_from_json/
error_handler.rs

1//! Comprehensive error handling module for HTML generation
2//!
3//! This module provides detailed error handling, recovery mechanisms,
4//! and user-friendly error messages for the HTML generation process.
5
6use serde_json::Value;
7use std::error::Error;
8use std::fmt;
9use std::path::PathBuf;
10use std::time::Instant;
11
12/// Comprehensive error types for HTML generation
13#[derive(Debug)]
14pub enum HtmlGenerationError {
15    /// JSON file discovery failed
16    FileDiscoveryError {
17        /// Directory that was searched
18        directory: String,
19        /// Base name pattern used
20        base_name: String,
21        /// Underlying error
22        source: Box<dyn Error + Send + Sync>,
23        /// Suggested recovery actions
24        recovery_suggestions: Vec<String>,
25    },
26    /// JSON file loading failed
27    FileLoadingError {
28        /// Path to the file that failed
29        file_path: PathBuf,
30        /// File type (e.g., "memory_analysis")
31        file_type: String,
32        /// File size in bytes
33        file_size: usize,
34        /// Underlying error
35        source: Box<dyn Error + Send + Sync>,
36        /// Whether this is a recoverable error
37        recoverable: bool,
38        /// Recovery suggestions
39        recovery_suggestions: Vec<String>,
40    },
41    /// JSON parsing failed
42    JsonParsingError {
43        /// Path to the file with parsing error
44        file_path: PathBuf,
45        /// Line number where error occurred (if available)
46        line_number: Option<usize>,
47        /// Column number where error occurred (if available)
48        column_number: Option<usize>,
49        /// Parsing error details
50        parsing_error: String,
51        /// JSON content snippet around error (if available)
52        content_snippet: Option<String>,
53        /// Recovery suggestions
54        recovery_suggestions: Vec<String>,
55    },
56    /// Data validation failed
57    ValidationError {
58        /// File that failed validation
59        file_path: PathBuf,
60        /// Type of validation that failed
61        validation_type: String,
62        /// Specific validation error
63        validation_error: String,
64        /// Expected data structure
65        expected_structure: String,
66        /// Actual data structure found
67        actual_structure: String,
68        /// Recovery suggestions
69        recovery_suggestions: Vec<String>,
70    },
71    /// Data normalization failed
72    NormalizationError {
73        /// Stage where normalization failed
74        stage: String,
75        /// Number of items processed before failure
76        processed_count: usize,
77        /// Total items to process
78        total_count: usize,
79        /// Underlying error
80        source: Box<dyn Error + Send + Sync>,
81        /// Whether partial data can be used
82        partial_data_available: bool,
83        /// Recovery suggestions
84        recovery_suggestions: Vec<String>,
85    },
86    /// Template generation failed
87    TemplateError {
88        /// Template stage that failed
89        stage: String,
90        /// Template size processed so far
91        processed_size: usize,
92        /// Underlying error
93        source: Box<dyn Error + Send + Sync>,
94        /// Recovery suggestions
95        recovery_suggestions: Vec<String>,
96    },
97    /// Memory limit exceeded
98    MemoryLimitError {
99        /// Current memory usage in bytes
100        current_usage: usize,
101        /// Memory limit in bytes
102        memory_limit: usize,
103        /// Operation that caused the limit to be exceeded
104        operation: String,
105        /// Recovery suggestions
106        recovery_suggestions: Vec<String>,
107    },
108    /// Multiple errors occurred
109    MultipleErrors {
110        /// List of errors that occurred
111        errors: Vec<HtmlGenerationError>,
112        /// Whether processing can continue with partial data
113        can_continue: bool,
114        /// Recovery suggestions
115        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/// Error recovery context
335#[derive(Debug, Clone)]
336pub struct ErrorRecoveryContext {
337    /// Whether to attempt recovery
338    pub attempt_recovery: bool,
339    /// Maximum number of retry attempts
340    pub max_retries: usize,
341    /// Whether to continue with partial data
342    pub allow_partial_data: bool,
343    /// Whether to use fallback mechanisms
344    pub use_fallbacks: bool,
345    /// Verbose error reporting
346    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/// Error recovery statistics
362#[derive(Debug, Default)]
363pub struct ErrorRecoveryStats {
364    /// Total errors encountered
365    pub total_errors: usize,
366    /// Errors successfully recovered
367    pub recovered_errors: usize,
368    /// Errors that required fallbacks
369    pub fallback_errors: usize,
370    /// Errors that were unrecoverable
371    pub unrecoverable_errors: usize,
372    /// Total retry attempts made
373    pub retry_attempts: usize,
374    /// Time spent on error recovery
375    pub recovery_time_ms: u64,
376}
377
378/// Comprehensive error handler for HTML generation
379pub struct HtmlErrorHandler {
380    /// Recovery context configuration
381    recovery_context: ErrorRecoveryContext,
382    /// Recovery statistics
383    stats: ErrorRecoveryStats,
384    /// Start time for timing recovery operations
385    start_time: Option<Instant>,
386}
387
388impl HtmlErrorHandler {
389    /// Create a new error handler with default settings
390    pub fn new() -> Self {
391        Self {
392            recovery_context: ErrorRecoveryContext::default(),
393            stats: ErrorRecoveryStats::default(),
394            start_time: None,
395        }
396    }
397
398    /// Create error handler with custom recovery context
399    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    /// Handle file discovery errors with recovery
408    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>, HtmlGenerationError> {
414        self.start_recovery_timing();
415        self.stats.total_errors += 1;
416
417        let recovery_suggestions = vec![
418            format!("Check if directory '{}' exists and is readable", directory),
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            // Attempt to find alternative directories or patterns
426            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(HtmlGenerationError::FileDiscoveryError {
439            directory: directory.to_string(),
440            base_name: base_name.to_string(),
441            source: error,
442            recovery_suggestions,
443        })
444    }
445
446    /// Handle file loading errors with recovery
447    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>, 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            // Attempt recovery strategies
462            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                    println!(
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                // Wait before retry
477                std::thread::sleep(std::time::Duration::from_millis(100 * attempt as u64));
478            }
479
480            // Try fallback if available
481            if self.recovery_context.use_fallbacks {
482                if let Ok(fallback_data) = self.get_fallback_data(file_type) {
483                    println!("āš ļø  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(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    /// Handle JSON parsing errors with detailed context
505    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    /// Handle validation errors with detailed analysis
538    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    /// Get error recovery statistics
568    pub fn get_stats(&self) -> &ErrorRecoveryStats {
569        &self.stats
570    }
571
572    /// Print error recovery summary
573    pub fn print_recovery_summary(&self) {
574        if self.stats.total_errors == 0 {
575            return;
576        }
577
578        println!("\nšŸ“Š Error Recovery Summary:");
579        println!("   Total errors: {}", self.stats.total_errors);
580        println!("   Recovered: {}", self.stats.recovered_errors);
581        println!("   Used fallbacks: {}", self.stats.fallback_errors);
582        println!("   Unrecoverable: {}", self.stats.unrecoverable_errors);
583        println!("   Retry attempts: {}", self.stats.retry_attempts);
584        println!("   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        println!("   Success rate: {:.1}%", success_rate);
594    }
595
596    // Private helper methods
597
598    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        // Implementation would search for alternative directories
614        // For now, return empty to indicate no alternatives found
615        Ok(vec![])
616    }
617
618    fn is_file_error_recoverable(&self, error: &Box<dyn Error + Send + Sync>) -> bool {
619        let error_str = error.to_string().to_lowercase();
620
621        // Recoverable errors
622        error_str.contains("permission denied")
623            || error_str.contains("temporarily unavailable")
624            || error_str.contains("resource busy")
625            || error_str.contains("interrupted")
626    }
627
628    fn get_file_loading_suggestions(
629        &self,
630        file_type: &str,
631        file_size: usize,
632        error: &Box<dyn Error + Send + Sync>,
633    ) -> Vec<String> {
634        let mut suggestions = vec![];
635        let error_str = error.to_string().to_lowercase();
636
637        if error_str.contains("permission") {
638            suggestions.push("Check file permissions and ensure read access".to_string());
639        }
640
641        if error_str.contains("not found") {
642            suggestions.push(format!(
643                "Verify the {} file exists in the expected location",
644                file_type
645            ));
646            suggestions
647                .push("Check the file naming convention matches the expected pattern".to_string());
648        }
649
650        if file_size > 100 * 1024 * 1024 {
651            // > 100MB
652            suggestions.push("Consider using streaming mode for large files".to_string());
653            suggestions.push("Increase memory limits if processing large datasets".to_string());
654        }
655
656        suggestions.push("Retry the operation after a brief delay".to_string());
657        suggestions.push("Check available disk space and memory".to_string());
658
659        suggestions
660    }
661
662    fn attempt_file_recovery(
663        &self,
664        file_path: &PathBuf,
665        _file_type: &str,
666    ) -> Result<Value, Box<dyn Error>> {
667        // Attempt to read the file again
668        let content = std::fs::read_to_string(file_path)?;
669        let json_value: Value = serde_json::from_str(&content)?;
670        Ok(json_value)
671    }
672
673    fn get_fallback_data(&self, file_type: &str) -> Result<Value, Box<dyn Error>> {
674        // Provide minimal fallback data structures
675        let fallback = match file_type {
676            "memory_analysis" => serde_json::json!({
677                "allocations": [],
678                "summary": {
679                    "total_allocations": 0,
680                    "active_allocations": 0,
681                    "total_memory": 0
682                }
683            }),
684            "performance" => serde_json::json!({
685                "memory_performance": {
686                    "active_memory": 0,
687                    "peak_memory": 0,
688                    "total_allocated": 0
689                },
690                "allocation_distribution": {}
691            }),
692            "unsafe_ffi" => serde_json::json!({
693                "summary": {
694                    "unsafe_count": 0,
695                    "ffi_count": 0,
696                    "safety_violations": 0
697                },
698                "enhanced_ffi_data": [],
699                "boundary_events": []
700            }),
701            "lifetime" => serde_json::json!({
702                "lifecycle_events": []
703            }),
704            "complex_types" => serde_json::json!({
705                "categorized_types": {},
706                "generic_types": []
707            }),
708            _ => serde_json::json!({}),
709        };
710
711        Ok(fallback)
712    }
713
714    fn extract_parsing_context(
715        &self,
716        file_path: &PathBuf,
717        parsing_error: &str,
718    ) -> (Option<usize>, Option<usize>, Option<String>) {
719        // Extract line and column information from parsing error
720        let line_regex = regex::Regex::new(r"line (\d+)").ok();
721        let col_regex = regex::Regex::new(r"column (\d+)").ok();
722
723        let line_number: Option<usize> = line_regex
724            .and_then(|re| re.captures(parsing_error))
725            .and_then(|caps| caps.get(1))
726            .and_then(|m| m.as_str().parse().ok());
727
728        let column_number: Option<usize> = col_regex
729            .and_then(|re| re.captures(parsing_error))
730            .and_then(|caps| caps.get(1))
731            .and_then(|m| m.as_str().parse().ok());
732
733        // Try to read file content around the error
734        let content_snippet =
735            if let (Some(line), Ok(content)) = (line_number, std::fs::read_to_string(file_path)) {
736                let lines: Vec<&str> = content.lines().collect();
737                if line > 0 && line <= lines.len() {
738                    let start = (line.saturating_sub(3)).max(1);
739                    let end = (line + 2).min(lines.len());
740                    let snippet_lines: Vec<String> = (start..=end)
741                        .map(|i| {
742                            let marker = if i == line { ">>> " } else { "    " };
743                            format!(
744                                "{}{:3}: {}",
745                                marker,
746                                i,
747                                lines.get(i.saturating_sub(1)).unwrap_or(&"")
748                            )
749                        })
750                        .collect();
751                    Some(snippet_lines.join("\n"))
752                } else {
753                    None
754                }
755            } else {
756                None
757            };
758
759        (line_number, column_number, content_snippet)
760    }
761
762    fn get_expected_structure(&self, validation_type: &str) -> String {
763        match validation_type {
764            "memory_analysis" => "Object with 'allocations' array and 'summary' object".to_string(),
765            "performance" => {
766                "Object with 'memory_performance' and 'allocation_distribution'".to_string()
767            }
768            "unsafe_ffi" => {
769                "Object with 'summary', 'enhanced_ffi_data', and 'boundary_events'".to_string()
770            }
771            "lifetime" => "Object with 'lifecycle_events' array".to_string(),
772            "complex_types" => "Object with 'categorized_types' and 'generic_types'".to_string(),
773            _ => "Valid JSON object or array".to_string(),
774        }
775    }
776
777    fn analyze_json_structure(&self, json_data: &Value) -> String {
778        match json_data {
779            Value::Object(obj) => {
780                let keys: Vec<String> = obj.keys().cloned().collect();
781                format!("Object with keys: [{}]", keys.join(", "))
782            }
783            Value::Array(arr) => {
784                format!("Array with {} elements", arr.len())
785            }
786            Value::String(_) => "String value".to_string(),
787            Value::Number(_) => "Number value".to_string(),
788            Value::Bool(_) => "Boolean value".to_string(),
789            Value::Null => "Null value".to_string(),
790        }
791    }
792
793    fn get_validation_recovery_suggestions(
794        &self,
795        validation_type: &str,
796        json_data: &Value,
797    ) -> Vec<String> {
798        let mut suggestions = vec![];
799
800        match validation_type {
801            "memory_analysis" => {
802                suggestions.push("Ensure the JSON contains an 'allocations' array".to_string());
803                suggestions.push("Add a 'summary' object with allocation statistics".to_string());
804                if let Value::Object(obj) = json_data {
805                    if !obj.contains_key("allocations") {
806                        suggestions.push("Add missing 'allocations' field as an array".to_string());
807                    }
808                    if !obj.contains_key("summary") {
809                        suggestions.push("Add missing 'summary' field as an object".to_string());
810                    }
811                }
812            }
813            "unsafe_ffi" => {
814                suggestions.push("Ensure the JSON contains 'enhanced_ffi_data' array".to_string());
815                suggestions
816                    .push("Add 'boundary_events' array for FFI boundary tracking".to_string());
817                suggestions.push("Include 'summary' object with FFI statistics".to_string());
818            }
819            _ => {
820                suggestions
821                    .push("Check the JSON structure matches the expected format".to_string());
822                suggestions
823                    .push("Refer to the documentation for the correct data format".to_string());
824            }
825        }
826
827        suggestions.push("Validate JSON syntax and structure".to_string());
828        suggestions.push("Check for required fields and correct data types".to_string());
829
830        suggestions
831    }
832}
833
834impl Default for HtmlErrorHandler {
835    fn default() -> Self {
836        Self::new()
837    }
838}
839
840#[cfg(test)]
841mod tests {
842    use super::*;
843    use std::io;
844
845    #[test]
846    fn test_error_handler_creation() {
847        let handler = HtmlErrorHandler::new();
848        assert_eq!(handler.stats.total_errors, 0);
849        assert!(handler.recovery_context.attempt_recovery);
850    }
851
852    #[test]
853    fn test_file_loading_error_handling() {
854        let mut handler = HtmlErrorHandler::new();
855        let error = Box::new(io::Error::new(io::ErrorKind::NotFound, "File not found"));
856
857        let result = handler.handle_file_loading_error(
858            PathBuf::from("test.json"),
859            "memory_analysis",
860            1024,
861            error,
862        );
863
864        assert!(result.is_err());
865        assert_eq!(handler.stats.total_errors, 1);
866    }
867
868    #[test]
869    fn test_fallback_data_generation() {
870        let handler = HtmlErrorHandler::new();
871
872        let fallback = handler.get_fallback_data("memory_analysis").unwrap();
873        assert!(fallback.is_object());
874        assert!(fallback.get("allocations").is_some());
875        assert!(fallback.get("summary").is_some());
876    }
877
878    #[test]
879    fn test_error_recovery_context() {
880        let context = ErrorRecoveryContext {
881            attempt_recovery: false,
882            max_retries: 1,
883            allow_partial_data: false,
884            use_fallbacks: false,
885            verbose_errors: true,
886        };
887
888        let handler = HtmlErrorHandler::with_context(context);
889        assert!(!handler.recovery_context.attempt_recovery);
890        assert_eq!(handler.recovery_context.max_retries, 1);
891    }
892}