strict_path/validator/
virtual_root.rs1use crate::path::virtual_path::VirtualPath;
3use crate::validator::path_history::PathHistory;
4use crate::PathBoundary;
5use crate::Result;
6use std::marker::PhantomData;
7use std::path::Path;
8#[cfg(feature = "tempfile")]
9use std::sync::Arc;
10
11#[cfg(feature = "tempfile")]
13use tempfile::TempDir;
14
15#[derive(Clone)]
17pub struct VirtualRoot<Marker = ()> {
18 pub(crate) root: PathBoundary<Marker>,
19 #[cfg(feature = "tempfile")]
21 pub(crate) _temp_dir: Option<Arc<TempDir>>, pub(crate) _marker: PhantomData<Marker>,
23}
24
25impl<Marker> VirtualRoot<Marker> {
26 #[inline]
39 pub fn try_new<P: AsRef<Path>>(root_path: P) -> Result<Self> {
40 let root = PathBoundary::try_new(root_path)?;
41 Ok(Self {
42 root,
43 #[cfg(feature = "tempfile")]
44 _temp_dir: None,
45 _marker: PhantomData,
46 })
47 }
48
49 #[inline]
61 pub fn try_new_create<P: AsRef<Path>>(root_path: P) -> Result<Self> {
62 let root = PathBoundary::try_new_create(root_path)?;
63 Ok(Self {
64 root,
65 #[cfg(feature = "tempfile")]
66 _temp_dir: None,
67 _marker: PhantomData,
68 })
69 }
70
71 #[inline]
76 pub fn virtual_join<P: AsRef<Path>>(&self, candidate_path: P) -> Result<VirtualPath<Marker>> {
77 let user_candidate = candidate_path.as_ref().to_path_buf();
79 let anchored = PathHistory::new(user_candidate).canonicalize_anchored(&self.root)?;
80
81 let validated = anchored.boundary_check(self.root.stated_path())?;
83
84 let jp = crate::path::strict_path::StrictPath::new(
86 std::sync::Arc::new(self.root.clone()),
87 validated,
88 );
89 Ok(jp.virtualize())
90 }
91
92 #[inline]
94 pub(crate) fn path(&self) -> &Path {
95 self.root.path()
96 }
97
98 #[inline]
103 pub fn interop_path(&self) -> &std::ffi::OsStr {
104 self.root.interop_path()
105 }
106
107 #[inline]
109 pub fn exists(&self) -> bool {
110 self.root.exists()
111 }
112
113 #[inline]
118 pub fn as_unvirtual(&self) -> &PathBoundary<Marker> {
119 &self.root
120 }
121
122 #[inline]
127 pub fn unvirtual(self) -> PathBoundary<Marker> {
128 self.root
129 }
130
131 #[cfg(feature = "dirs")]
144 pub fn try_new_os_config(app_name: &str) -> Result<Self> {
145 let root = crate::PathBoundary::try_new_os_config(app_name)?;
146 Ok(Self {
147 root,
148 #[cfg(feature = "tempfile")]
149 _temp_dir: None,
150 _marker: PhantomData,
151 })
152 }
153
154 #[cfg(feature = "dirs")]
156 pub fn try_new_os_data(app_name: &str) -> Result<Self> {
157 let root = crate::PathBoundary::try_new_os_data(app_name)?;
158 Ok(Self {
159 root,
160 #[cfg(feature = "tempfile")]
161 _temp_dir: None,
162 _marker: PhantomData,
163 })
164 }
165
166 #[cfg(feature = "dirs")]
168 pub fn try_new_os_cache(app_name: &str) -> Result<Self> {
169 let root = crate::PathBoundary::try_new_os_cache(app_name)?;
170 Ok(Self {
171 root,
172 #[cfg(feature = "tempfile")]
173 _temp_dir: None,
174 _marker: PhantomData,
175 })
176 }
177
178 #[cfg(feature = "dirs")]
180 pub fn try_new_os_config_local(app_name: &str) -> Result<Self> {
181 let root = crate::PathBoundary::try_new_os_config_local(app_name)?;
182 Ok(Self {
183 root,
184 #[cfg(feature = "tempfile")]
185 _temp_dir: None,
186 _marker: PhantomData,
187 })
188 }
189
190 #[cfg(feature = "dirs")]
192 pub fn try_new_os_data_local(app_name: &str) -> Result<Self> {
193 let root = crate::PathBoundary::try_new_os_data_local(app_name)?;
194 Ok(Self {
195 root,
196 #[cfg(feature = "tempfile")]
197 _temp_dir: None,
198 _marker: PhantomData,
199 })
200 }
201
202 #[cfg(feature = "dirs")]
204 pub fn try_new_os_home() -> Result<Self> {
205 let root = crate::PathBoundary::try_new_os_home()?;
206 Ok(Self {
207 root,
208 #[cfg(feature = "tempfile")]
209 _temp_dir: None,
210 _marker: PhantomData,
211 })
212 }
213
214 #[cfg(feature = "dirs")]
216 pub fn try_new_os_desktop() -> Result<Self> {
217 let root = crate::PathBoundary::try_new_os_desktop()?;
218 Ok(Self {
219 root,
220 #[cfg(feature = "tempfile")]
221 _temp_dir: None,
222 _marker: PhantomData,
223 })
224 }
225
226 #[cfg(feature = "dirs")]
228 pub fn try_new_os_documents() -> Result<Self> {
229 let root = crate::PathBoundary::try_new_os_documents()?;
230 Ok(Self {
231 root,
232 #[cfg(feature = "tempfile")]
233 _temp_dir: None,
234 _marker: PhantomData,
235 })
236 }
237
238 #[cfg(feature = "dirs")]
240 pub fn try_new_os_downloads() -> Result<Self> {
241 let root = crate::PathBoundary::try_new_os_downloads()?;
242 Ok(Self {
243 root,
244 #[cfg(feature = "tempfile")]
245 _temp_dir: None,
246 _marker: PhantomData,
247 })
248 }
249
250 #[cfg(feature = "dirs")]
252 pub fn try_new_os_pictures() -> Result<Self> {
253 let root = crate::PathBoundary::try_new_os_pictures()?;
254 Ok(Self {
255 root,
256 #[cfg(feature = "tempfile")]
257 _temp_dir: None,
258 _marker: PhantomData,
259 })
260 }
261
262 #[cfg(feature = "dirs")]
264 pub fn try_new_os_audio() -> Result<Self> {
265 let root = crate::PathBoundary::try_new_os_audio()?;
266 Ok(Self {
267 root,
268 #[cfg(feature = "tempfile")]
269 _temp_dir: None,
270 _marker: PhantomData,
271 })
272 }
273
274 #[cfg(feature = "dirs")]
276 pub fn try_new_os_videos() -> Result<Self> {
277 let root = crate::PathBoundary::try_new_os_videos()?;
278 Ok(Self {
279 root,
280 #[cfg(feature = "tempfile")]
281 _temp_dir: None,
282 _marker: PhantomData,
283 })
284 }
285
286 #[cfg(feature = "dirs")]
288 pub fn try_new_os_executables() -> Result<Self> {
289 let root = crate::PathBoundary::try_new_os_executables()?;
290 Ok(Self {
291 root,
292 #[cfg(feature = "tempfile")]
293 _temp_dir: None,
294 _marker: PhantomData,
295 })
296 }
297
298 #[cfg(feature = "dirs")]
300 pub fn try_new_os_runtime() -> Result<Self> {
301 let root = crate::PathBoundary::try_new_os_runtime()?;
302 Ok(Self {
303 root,
304 #[cfg(feature = "tempfile")]
305 _temp_dir: None,
306 _marker: PhantomData,
307 })
308 }
309
310 #[cfg(feature = "dirs")]
312 pub fn try_new_os_state(app_name: &str) -> Result<Self> {
313 let root = crate::PathBoundary::try_new_os_state(app_name)?;
314 Ok(Self {
315 root,
316 #[cfg(feature = "tempfile")]
317 _temp_dir: None,
318 _marker: PhantomData,
319 })
320 }
321
322 #[cfg(feature = "app-path")]
327 pub fn try_new_app_path(subdir: &str, env_override: Option<&str>) -> Result<Self> {
328 let root = crate::PathBoundary::try_new_app_path(subdir, env_override)?;
329 Ok(Self {
330 root,
331 #[cfg(feature = "tempfile")]
332 _temp_dir: None,
333 _marker: PhantomData,
334 })
335 }
336}
337
338impl<Marker> std::fmt::Display for VirtualRoot<Marker> {
339 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340 write!(f, "{}", self.path().display())
341 }
342}
343
344impl<Marker> AsRef<Path> for VirtualRoot<Marker> {
345 fn as_ref(&self) -> &Path {
346 self.path()
347 }
348}
349
350impl<Marker> std::fmt::Debug for VirtualRoot<Marker> {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 f.debug_struct("VirtualRoot")
353 .field("root", &self.path())
354 .field("marker", &std::any::type_name::<Marker>())
355 .finish()
356 }
357}
358
359impl<Marker> PartialEq for VirtualRoot<Marker> {
360 #[inline]
361 fn eq(&self, other: &Self) -> bool {
362 self.path() == other.path()
363 }
364}
365
366impl<Marker> Eq for VirtualRoot<Marker> {}
367
368impl<Marker> std::hash::Hash for VirtualRoot<Marker> {
369 #[inline]
370 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
371 self.path().hash(state);
372 }
373}
374
375impl<Marker> PartialOrd for VirtualRoot<Marker> {
376 #[inline]
377 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
378 Some(self.cmp(other))
379 }
380}
381
382impl<Marker> Ord for VirtualRoot<Marker> {
383 #[inline]
384 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
385 self.path().cmp(other.path())
386 }
387}
388
389impl<Marker> PartialEq<crate::PathBoundary<Marker>> for VirtualRoot<Marker> {
390 #[inline]
391 fn eq(&self, other: &crate::PathBoundary<Marker>) -> bool {
392 self.path() == other.path()
393 }
394}
395
396impl<Marker> PartialEq<std::path::Path> for VirtualRoot<Marker> {
397 #[inline]
398 fn eq(&self, other: &std::path::Path) -> bool {
399 let other_str = other.to_string_lossy();
402
403 #[cfg(windows)]
404 let other_normalized = other_str.replace('\\', "/");
405 #[cfg(not(windows))]
406 let other_normalized = other_str.to_string();
407
408 let normalized_other = if other_normalized.starts_with('/') {
409 other_normalized
410 } else {
411 format!("/{}", other_normalized)
412 };
413
414 "/" == normalized_other
415 }
416}
417
418impl<Marker> PartialEq<std::path::PathBuf> for VirtualRoot<Marker> {
419 #[inline]
420 fn eq(&self, other: &std::path::PathBuf) -> bool {
421 self.eq(other.as_path())
422 }
423}
424
425impl<Marker> PartialEq<&std::path::Path> for VirtualRoot<Marker> {
426 #[inline]
427 fn eq(&self, other: &&std::path::Path) -> bool {
428 self.eq(*other)
429 }
430}
431
432impl<Marker> PartialOrd<std::path::Path> for VirtualRoot<Marker> {
433 #[inline]
434 fn partial_cmp(&self, other: &std::path::Path) -> Option<std::cmp::Ordering> {
435 let other_str = other.to_string_lossy();
437
438 if other_str.is_empty() {
440 return Some(std::cmp::Ordering::Greater);
441 }
442
443 #[cfg(windows)]
444 let other_normalized = other_str.replace('\\', "/");
445 #[cfg(not(windows))]
446 let other_normalized = other_str.to_string();
447
448 let normalized_other = if other_normalized.starts_with('/') {
449 other_normalized
450 } else {
451 format!("/{}", other_normalized)
452 };
453
454 Some("/".cmp(&normalized_other))
455 }
456}
457
458impl<Marker> PartialOrd<&std::path::Path> for VirtualRoot<Marker> {
459 #[inline]
460 fn partial_cmp(&self, other: &&std::path::Path) -> Option<std::cmp::Ordering> {
461 self.partial_cmp(*other)
462 }
463}
464
465impl<Marker> PartialOrd<std::path::PathBuf> for VirtualRoot<Marker> {
466 #[inline]
467 fn partial_cmp(&self, other: &std::path::PathBuf) -> Option<std::cmp::Ordering> {
468 self.partial_cmp(other.as_path())
469 }
470}
471
472impl<Marker: Default> std::str::FromStr for VirtualRoot<Marker> {
473 type Err = crate::StrictPathError;
474
475 #[inline]
490 fn from_str(path: &str) -> std::result::Result<Self, Self::Err> {
491 Self::try_new_create(path)
492 }
493}