Skip to main content

weavatrix_scan/
walk_types.rs

1use std::ffi::OsStr;
2use std::fmt;
3use std::io;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ErrorPolicy {
8    Continue,
9    Abort,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct WalkOptions {
14    pub min_depth: usize,
15    pub max_depth: Option<usize>,
16    pub max_open: usize,
17    pub same_file_system: bool,
18    pub follow_links: bool,
19    pub collect_metadata: bool,
20    pub error_policy: ErrorPolicy,
21}
22
23impl Default for WalkOptions {
24    fn default() -> Self {
25        Self {
26            min_depth: 0,
27            max_depth: None,
28            max_open: 64,
29            same_file_system: false,
30            follow_links: false,
31            collect_metadata: false,
32            error_policy: ErrorPolicy::Continue,
33        }
34    }
35}
36
37impl WalkOptions {
38    #[must_use]
39    pub const fn with_min_depth(mut self, min_depth: usize) -> Self {
40        self.min_depth = min_depth;
41        self
42    }
43
44    #[must_use]
45    pub const fn with_max_depth(mut self, max_depth: Option<usize>) -> Self {
46        self.max_depth = max_depth;
47        self
48    }
49
50    #[must_use]
51    pub const fn with_max_open(mut self, max_open: usize) -> Self {
52        self.max_open = if max_open == 0 { 1 } else { max_open };
53        self
54    }
55
56    #[must_use]
57    pub const fn with_same_file_system(mut self, enabled: bool) -> Self {
58        self.same_file_system = enabled;
59        self
60    }
61
62    #[must_use]
63    pub const fn with_follow_links(mut self, enabled: bool) -> Self {
64        self.follow_links = enabled;
65        self
66    }
67
68    #[must_use]
69    pub const fn with_metadata(mut self, enabled: bool) -> Self {
70        self.collect_metadata = enabled;
71        self
72    }
73
74    #[must_use]
75    pub const fn with_error_policy(mut self, policy: ErrorPolicy) -> Self {
76        self.error_policy = policy;
77        self
78    }
79
80    pub(crate) fn normalized(mut self) -> Self {
81        self.max_open = self.max_open.max(1);
82        if let Some(max_depth) = self.max_depth
83            && self.min_depth > max_depth
84        {
85            self.min_depth = max_depth;
86        }
87        self
88    }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum WalkOperation {
93    Canonicalize,
94    ReadDirectory,
95    ReadEntry,
96    ReadMetadata,
97}
98
99#[derive(Debug)]
100pub struct WalkError {
101    pub(crate) path: PathBuf,
102    pub(crate) depth: usize,
103    pub(crate) operation: WalkOperation,
104    pub(crate) source: io::Error,
105}
106
107impl WalkError {
108    pub(crate) fn new(
109        path: impl Into<PathBuf>,
110        depth: usize,
111        operation: WalkOperation,
112        source: io::Error,
113    ) -> Self {
114        Self {
115            path: path.into(),
116            depth,
117            operation,
118            source,
119        }
120    }
121
122    #[must_use]
123    pub fn path(&self) -> &Path {
124        &self.path
125    }
126
127    #[must_use]
128    pub const fn depth(&self) -> usize {
129        self.depth
130    }
131
132    #[must_use]
133    pub const fn operation(&self) -> WalkOperation {
134        self.operation
135    }
136
137    #[must_use]
138    pub const fn io_error(&self) -> &io::Error {
139        &self.source
140    }
141
142    pub(crate) fn into_parts(self) -> (PathBuf, io::Error) {
143        (self.path, self.source)
144    }
145
146    pub(crate) fn rebase_depth(&mut self, depth_offset: usize) {
147        self.depth += depth_offset;
148    }
149}
150
151impl fmt::Display for WalkError {
152    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(
154            formatter,
155            "{} at depth {} for {}: {}",
156            operation_name(self.operation),
157            self.depth,
158            self.path.display(),
159            self.source
160        )
161    }
162}
163
164impl std::error::Error for WalkError {
165    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
166        Some(&self.source)
167    }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum WalkSkipReason {
172    MaxDepth,
173    FileSystemBoundary,
174    PathEscape,
175    SymlinkLoop,
176}
177
178#[derive(Debug, Clone)]
179pub struct WalkEntry {
180    pub(crate) root_components: usize,
181    pub(crate) path: PathBuf,
182    pub(crate) depth: usize,
183    pub(crate) is_file: bool,
184    pub(crate) is_directory: bool,
185    pub(crate) is_symlink: bool,
186    pub(crate) bytes: Option<u64>,
187    pub(crate) skip_reason: Option<WalkSkipReason>,
188}
189
190impl WalkEntry {
191    #[must_use]
192    pub fn path(&self) -> &Path {
193        &self.path
194    }
195
196    #[must_use]
197    pub fn relative_path(&self) -> &Path {
198        let mut components = self.path.components();
199        for _ in 0..self.root_components {
200            if components.next().is_none() {
201                return self.path.as_path();
202            }
203        }
204        components.as_path()
205    }
206
207    #[must_use]
208    pub fn file_name(&self) -> &OsStr {
209        self.path
210            .file_name()
211            .unwrap_or_else(|| self.path.as_os_str())
212    }
213
214    #[must_use]
215    pub const fn depth(&self) -> usize {
216        self.depth
217    }
218
219    #[must_use]
220    pub const fn is_file(&self) -> bool {
221        self.is_file
222    }
223
224    #[must_use]
225    pub const fn is_dir(&self) -> bool {
226        self.is_directory
227    }
228
229    #[must_use]
230    pub const fn is_symlink(&self) -> bool {
231        self.is_symlink
232    }
233
234    #[must_use]
235    pub const fn bytes(&self) -> Option<u64> {
236        self.bytes
237    }
238
239    #[must_use]
240    pub const fn skip_reason(&self) -> Option<WalkSkipReason> {
241        self.skip_reason
242    }
243
244    pub(crate) fn rebase(&mut self, root: &Path, depth_offset: usize) {
245        self.root_components = root.components().count();
246        self.depth += depth_offset;
247    }
248
249    pub(crate) fn clear_depth_skip(&mut self) {
250        if self.skip_reason == Some(WalkSkipReason::MaxDepth) {
251            self.skip_reason = None;
252        }
253    }
254}
255
256const fn operation_name(operation: WalkOperation) -> &'static str {
257    match operation {
258        WalkOperation::Canonicalize => "canonicalize",
259        WalkOperation::ReadDirectory => "read directory",
260        WalkOperation::ReadEntry => "read entry",
261        WalkOperation::ReadMetadata => "read metadata",
262    }
263}