Skip to main content

revault_lockbox_api/model/
recovery_report.rs

1use crate::{LockboxEntry, RecoveryReportOptions};
2
3/// Summary produced by lockbox recovery and salvage scans.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct RecoveryReport {
6    /// Intact file entries discovered by the scan.
7    pub intact_files: Vec<LockboxEntry>,
8    /// Total number of intact files, including entries omitted from
9    /// `intact_files` by reporting limits.
10    pub intact_file_count: usize,
11    /// Number of file records that were only partially recoverable.
12    pub partial_files: usize,
13    /// Number of corrupt records encountered.
14    pub corrupt_records: usize,
15    /// Whether the latest TOC could be read.
16    pub toc_recovered: bool,
17    /// Whether variable metadata was recovered from the latest commit root.
18    pub variables_recovered: bool,
19    /// Number of variable values recovered.
20    pub variable_count: usize,
21    /// Whether form metadata was recovered from the latest commit root.
22    pub forms_recovered: bool,
23    /// Number of latest form definitions recovered.
24    pub form_definition_count: usize,
25    /// Number of form records recovered.
26    pub form_record_count: usize,
27}
28
29impl RecoveryReport {
30    /// Render a human-readable report.
31    pub fn render(&self, options: &RecoveryReportOptions) -> String {
32        let mut out = String::new();
33        out.push_str("Recovery report\n\n");
34        out.push_str("Summary:\n");
35        out.push_str(&format!("  Intact files: {}\n", self.intact_file_count));
36        out.push_str(&format!("  Partial files: {}\n", self.partial_files));
37        out.push_str(&format!("  Corrupt records: {}\n", self.corrupt_records));
38        out.push_str(&format!(
39            "  Latest TOC: {}\n",
40            if self.toc_recovered {
41                "intact"
42            } else {
43                "not used or damaged"
44            }
45        ));
46        out.push_str(&format!(
47            "  Variables: {} ({})\n",
48            self.variable_count,
49            if self.variables_recovered {
50                "recovered"
51            } else {
52                "not recovered"
53            }
54        ));
55        out.push_str(&format!(
56            "  Forms: {} definitions, {} items ({})\n",
57            self.form_definition_count,
58            self.form_record_count,
59            if self.forms_recovered {
60                "recovered"
61            } else {
62                "not recovered"
63            }
64        ));
65
66        if options.verbose {
67            out.push_str("\nIntact:\n");
68            let limit = options
69                .max_intact_entries
70                .unwrap_or(self.intact_files.len());
71            for entry in self.intact_files.iter().take(limit) {
72                out.push_str(&format!("  {}\n", entry.path));
73            }
74            if self.intact_files.len() > limit {
75                out.push_str(&format!(
76                    "  ... {} more intact files omitted\n",
77                    self.intact_files.len() - limit
78                ));
79            }
80        }
81
82        if self.partial_files > 0 || self.corrupt_records > 0 {
83            out.push_str("\nAttention required:\n");
84            if self.partial_files > 0 {
85                out.push_str(&format!(
86                    "  Partial files detected: {}\n",
87                    self.partial_files
88                ));
89            }
90            if self.corrupt_records > 0 {
91                out.push_str(&format!(
92                    "  Corrupt records detected: {}\n",
93                    self.corrupt_records
94                ));
95            }
96        }
97
98        out
99    }
100}