weavatrix_scan/
walk_types.rs1use 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}