1use std::path::PathBuf;
2
3#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct FileIdentity {
6 pub file_system: u64,
7 pub file: u64,
8}
9
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12pub struct FileVersion {
13 #[cfg_attr(feature = "serde", serde(default))]
14 pub modified_ns: Option<u128>,
15 #[cfg_attr(feature = "serde", serde(default))]
16 pub changed_ns: Option<u128>,
17 #[cfg_attr(feature = "serde", serde(default))]
18 pub identity: Option<FileIdentity>,
19}
20
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ScannedFile {
24 #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
25 pub absolute: PathBuf,
26 pub relative: String,
27 pub bytes: u64,
28 pub content_hash: Option<String>,
29 #[cfg_attr(feature = "serde", serde(default))]
31 pub content_fingerprint: Option<String>,
32 #[cfg_attr(feature = "serde", serde(default))]
33 pub version: FileVersion,
34 #[cfg_attr(feature = "serde", serde(default))]
35 pub binary_checked: bool,
36}
37
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct CompactScannedFile {
45 pub relative: Box<str>,
46 pub bytes: u64,
47 #[cfg_attr(feature = "serde", serde(default))]
49 pub content: Option<Box<CompactContentEvidence>>,
50}
51
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct CompactContentEvidence {
56 pub content_hash: Option<Box<str>>,
57 #[cfg_attr(feature = "serde", serde(default))]
59 pub content_fingerprint: Option<Box<str>>,
60 #[cfg_attr(feature = "serde", serde(default))]
61 pub version: FileVersion,
62 #[cfg_attr(feature = "serde", serde(default))]
63 pub binary_checked: bool,
64}
65
66impl CompactScannedFile {
67 #[must_use]
69 pub fn content_hash(&self) -> Option<&str> {
70 self.content
71 .as_deref()
72 .and_then(|content| content.content_hash.as_deref())
73 }
74}
75
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
78pub struct ScanCacheStats {
79 pub reused_hashes: u64,
80 pub content_reads: u64,
81 #[cfg_attr(feature = "serde", serde(default))]
83 pub fingerprint_reads: u64,
84}
85
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
88pub enum SkipKind {
89 Binary,
90 FileSystemBoundary,
91 Extension,
92 Ignored,
93 IoError,
94 MaxDepth,
95 Oversized,
96 PathEscape,
97 StandardDirectory,
98 Hidden,
99 Override,
100 Symlink,
101 SymlinkLoop,
102 ScanLimit,
103 ConcurrentModification,
104}
105
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct SkippedEntry {
109 pub relative: String,
110 pub kind: SkipKind,
111 pub detail: Option<String>,
112}
113
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct ScanWarning {
117 pub relative: Option<String>,
118 pub message: String,
119}
120
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
123pub enum IgnoreSourceKind {
124 GitGlobal,
125 GitExclude,
126 GitIgnore,
127 DotIgnore,
128 Custom,
129 Explicit,
130 Override,
131}
132
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct IgnoreSourceEvidence {
136 pub kind: IgnoreSourceKind,
137 pub location: String,
138 pub content_hash: String,
139}
140
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum ScanTermination {
144 MaxEntries,
145 MaxTotalBytes,
146 Timeout,
147 Cancelled,
148}
149
150#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct ScanReport {
153 #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
154 pub root: PathBuf,
155 pub files: Vec<ScannedFile>,
156 pub skipped: Vec<SkippedEntry>,
157 pub warnings: Vec<ScanWarning>,
158 #[cfg_attr(feature = "serde", serde(default))]
160 pub ignore_sources: Vec<IgnoreSourceEvidence>,
161 pub revision: String,
162 #[cfg_attr(feature = "serde", serde(default = "default_complete"))]
164 pub complete: bool,
165 #[cfg_attr(feature = "serde", serde(default))]
167 pub termination: Option<ScanTermination>,
168 #[cfg_attr(feature = "serde", serde(default = "default_portable"))]
170 pub portable: bool,
171 #[cfg_attr(feature = "serde", serde(default))]
173 pub cache: ScanCacheStats,
174 #[cfg_attr(feature = "serde", serde(skip, default = "default_record_skipped"))]
175 record_skipped: bool,
176}
177
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct CompactScanReport {
186 #[cfg_attr(feature = "serde", serde(with = "crate::path_serde"))]
187 pub root: PathBuf,
188 pub files: Vec<CompactScannedFile>,
189 pub skipped: Vec<SkippedEntry>,
190 pub warnings: Vec<ScanWarning>,
191 #[cfg_attr(feature = "serde", serde(default))]
192 pub ignore_sources: Vec<IgnoreSourceEvidence>,
193 pub revision: String,
194 #[cfg_attr(feature = "serde", serde(default = "default_complete"))]
195 pub complete: bool,
196 #[cfg_attr(feature = "serde", serde(default))]
197 pub termination: Option<ScanTermination>,
198 #[cfg_attr(feature = "serde", serde(default = "default_portable"))]
199 pub portable: bool,
200 #[cfg_attr(feature = "serde", serde(default))]
201 pub cache: ScanCacheStats,
202}
203
204impl CompactScanReport {
205 #[must_use]
207 pub fn absolute_path(&self, file: &CompactScannedFile) -> PathBuf {
208 self.root.join(file.relative.as_ref())
209 }
210}
211
212#[cfg(feature = "serde")]
213const fn default_complete() -> bool {
214 true
215}
216
217#[cfg(feature = "serde")]
218const fn default_record_skipped() -> bool {
219 true
220}
221
222#[cfg(feature = "serde")]
223const fn default_portable() -> bool {
224 true
225}
226
227impl ScanReport {
228 #[must_use]
230 pub fn delta_from(&self, previous: &Self) -> crate::ScanDelta {
231 crate::ScanDelta::between(previous, self)
232 }
233
234 pub(crate) fn new(root: PathBuf, record_skipped: bool) -> Self {
235 Self {
236 root,
237 files: Vec::new(),
238 skipped: Vec::new(),
239 warnings: Vec::new(),
240 ignore_sources: Vec::new(),
241 revision: String::new(),
242 complete: true,
243 termination: None,
244 portable: true,
245 cache: ScanCacheStats::default(),
246 record_skipped,
247 }
248 }
249
250 pub(crate) fn skip(&mut self, relative: String, kind: SkipKind, detail: Option<String>) {
251 if self.record_skipped {
252 self.skipped.push(SkippedEntry {
253 relative,
254 kind,
255 detail,
256 });
257 }
258 }
259
260 pub(crate) fn skip_borrowed(&mut self, relative: &str, kind: SkipKind, detail: Option<String>) {
261 if self.record_skipped {
262 self.skipped.push(SkippedEntry {
263 relative: relative.to_owned(),
264 kind,
265 detail,
266 });
267 }
268 }
269
270 pub(crate) fn warn(&mut self, relative: Option<String>, message: impl Into<String>) {
271 self.complete = false;
272 self.warnings.push(ScanWarning {
273 relative,
274 message: message.into(),
275 });
276 }
277
278 pub(crate) fn terminate(&mut self, reason: ScanTermination) {
279 self.complete = false;
280 self.termination.get_or_insert(reason);
281 }
282
283 pub(crate) fn finish_recording(&mut self) {
284 self.record_skipped = true;
285 }
286}