Skip to main content

weavatrix_scan/
report.rs

1use std::path::PathBuf;
2
3#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct ScannedFile {
6    #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
7    pub absolute: PathBuf,
8    pub relative: String,
9    pub bytes: u64,
10    pub content_hash: Option<String>,
11}
12
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
15pub enum SkipKind {
16    Binary,
17    FileSystemBoundary,
18    Extension,
19    Ignored,
20    IoError,
21    MaxDepth,
22    Oversized,
23    PathEscape,
24    StandardDirectory,
25    Hidden,
26    Override,
27    Symlink,
28    SymlinkLoop,
29    ScanLimit,
30}
31
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct SkippedEntry {
35    pub relative: String,
36    pub kind: SkipKind,
37    pub detail: Option<String>,
38}
39
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct ScanWarning {
43    pub relative: Option<String>,
44    pub message: String,
45}
46
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
49pub enum IgnoreSourceKind {
50    GitGlobal,
51    GitExclude,
52    GitIgnore,
53    DotIgnore,
54    Custom,
55    Explicit,
56    Override,
57}
58
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct IgnoreSourceEvidence {
62    pub kind: IgnoreSourceKind,
63    pub location: String,
64    pub content_hash: String,
65}
66
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum ScanTermination {
70    MaxEntries,
71    MaxTotalBytes,
72    Timeout,
73    Cancelled,
74}
75
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct ScanReport {
79    #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
80    pub root: PathBuf,
81    pub files: Vec<ScannedFile>,
82    pub skipped: Vec<SkippedEntry>,
83    pub warnings: Vec<ScanWarning>,
84    /// Every ignore input that participated in path selection.
85    #[cfg_attr(feature = "serde", serde(default))]
86    pub ignore_sources: Vec<IgnoreSourceEvidence>,
87    pub revision: String,
88    /// False when local I/O or ignore-rule errors made evidence partial.
89    #[cfg_attr(feature = "serde", serde(default = "default_complete"))]
90    pub complete: bool,
91    /// Why a bounded scan stopped before exhausting the tree.
92    #[cfg_attr(feature = "serde", serde(default))]
93    pub termination: Option<ScanTermination>,
94    /// False when selection depended on host-level configuration.
95    #[cfg_attr(feature = "serde", serde(default = "default_portable"))]
96    pub portable: bool,
97    #[cfg_attr(feature = "serde", serde(skip, default = "default_record_skipped"))]
98    record_skipped: bool,
99}
100
101#[cfg(feature = "serde")]
102const fn default_complete() -> bool {
103    true
104}
105
106#[cfg(feature = "serde")]
107const fn default_record_skipped() -> bool {
108    true
109}
110
111#[cfg(feature = "serde")]
112const fn default_portable() -> bool {
113    true
114}
115
116impl ScanReport {
117    /// Computes the deterministic changed-file set from an older report.
118    #[must_use]
119    pub fn delta_from(&self, previous: &Self) -> crate::ScanDelta {
120        crate::ScanDelta::between(previous, self)
121    }
122
123    pub(crate) fn new(root: PathBuf, record_skipped: bool) -> Self {
124        Self {
125            root,
126            files: Vec::new(),
127            skipped: Vec::new(),
128            warnings: Vec::new(),
129            ignore_sources: Vec::new(),
130            revision: String::new(),
131            complete: true,
132            termination: None,
133            portable: true,
134            record_skipped,
135        }
136    }
137
138    pub(crate) fn skip(&mut self, relative: String, kind: SkipKind, detail: Option<String>) {
139        if self.record_skipped {
140            self.skipped.push(SkippedEntry {
141                relative,
142                kind,
143                detail,
144            });
145        }
146    }
147
148    pub(crate) fn warn(&mut self, relative: Option<String>, message: impl Into<String>) {
149        self.complete = false;
150        self.warnings.push(ScanWarning {
151            relative,
152            message: message.into(),
153        });
154    }
155
156    pub(crate) fn terminate(&mut self, reason: ScanTermination) {
157        self.complete = false;
158        self.termination.get_or_insert(reason);
159    }
160
161    pub(crate) fn finish_recording(&mut self) {
162        self.record_skipped = true;
163    }
164}