strict_path/validator/virtual_root.rs
1// Content copied from original src/validator/virtual_root.rs
2use 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// keep feature-gated TempDir RAII field using Arc from std::sync
12#[cfg(feature = "tempfile")]
13use tempfile::TempDir;
14
15/// SUMMARY:
16/// Provide a user‑facing virtual root that produces `VirtualPath` values clamped to a boundary.
17#[derive(Clone)]
18pub struct VirtualRoot<Marker = ()> {
19 pub(crate) root: PathBoundary<Marker>,
20 // Held only to tie RAII of temp directories to the VirtualRoot lifetime
21 #[cfg(feature = "tempfile")]
22 pub(crate) _temp_dir: Option<Arc<TempDir>>, // mirrors RAII when constructed from temp
23 pub(crate) _marker: PhantomData<Marker>,
24}
25
26impl<Marker> VirtualRoot<Marker> {
27 // no extra constructors; use PathBoundary::virtualize() or VirtualRoot::try_new
28 /// SUMMARY:
29 /// Create a `VirtualRoot` from an existing directory.
30 ///
31 /// PARAMETERS:
32 /// - `root_path` (`AsRef<Path>`): Existing directory to anchor the virtual root.
33 ///
34 /// RETURNS:
35 /// - `Result<VirtualRoot<Marker>>`: New virtual root with clamped operations.
36 ///
37 /// ERRORS:
38 /// - `StrictPathError::InvalidRestriction`: Root invalid or cannot be canonicalized.
39 ///
40 /// EXAMPLE:
41 /// Uses `AsRef<Path>` for maximum ergonomics, including direct `TempDir` support for clean shadowing patterns:
42 /// ```rust
43 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
44 /// use strict_path::VirtualRoot;
45 /// let tmp_dir = tempfile::tempdir()?;
46 /// let tmp_dir = VirtualRoot::<()>::try_new(tmp_dir)?; // Clean variable shadowing
47 /// # Ok(())
48 /// # }
49 /// ```
50 #[inline]
51 pub fn try_new<P: AsRef<Path>>(root_path: P) -> Result<Self> {
52 let root = PathBoundary::try_new(root_path)?;
53 Ok(Self {
54 root,
55 #[cfg(feature = "tempfile")]
56 _temp_dir: None,
57 _marker: PhantomData,
58 })
59 }
60
61 /// SUMMARY:
62 /// Create a `VirtualRoot` backed by a unique temporary directory with RAII cleanup.
63 ///
64 /// # Example
65 /// ```
66 /// # #[cfg(feature = "tempfile")] {
67 /// use strict_path::VirtualRoot;
68 ///
69 /// let uploads_root = VirtualRoot::<()>::try_new_temp()?;
70 /// let tenant_file = uploads_root.virtual_join("tenant/document.pdf")?;
71 /// let display = tenant_file.virtualpath_display().to_string();
72 /// assert!(display.starts_with("/"));
73 /// # }
74 /// # Ok::<(), Box<dyn std::error::Error>>(())
75 /// ```
76 #[cfg(feature = "tempfile")]
77 #[inline]
78 pub fn try_new_temp() -> Result<Self> {
79 let root = PathBoundary::try_new_temp()?;
80 let temp_dir = root.temp_dir_arc();
81 Ok(Self {
82 root,
83 #[cfg(feature = "tempfile")]
84 _temp_dir: temp_dir,
85 _marker: PhantomData,
86 })
87 }
88
89 /// SUMMARY:
90 /// Create a `VirtualRoot` in a temporary directory with a custom prefix and RAII cleanup.
91 ///
92 /// # Example
93 /// ```
94 /// # #[cfg(feature = "tempfile")] {
95 /// use strict_path::VirtualRoot;
96 ///
97 /// let session_root = VirtualRoot::<()>::try_new_temp_with_prefix("session")?;
98 /// let export_path = session_root.virtual_join("exports/report.txt")?;
99 /// let display = export_path.virtualpath_display().to_string();
100 /// assert!(display.starts_with("/exports"));
101 /// # }
102 /// # Ok::<(), Box<dyn std::error::Error>>(())
103 /// ```
104 #[cfg(feature = "tempfile")]
105 #[inline]
106 pub fn try_new_temp_with_prefix(prefix: &str) -> Result<Self> {
107 let root = PathBoundary::try_new_temp_with_prefix(prefix)?;
108 let temp_dir = root.temp_dir_arc();
109 Ok(Self {
110 root,
111 #[cfg(feature = "tempfile")]
112 _temp_dir: temp_dir,
113 _marker: PhantomData,
114 })
115 }
116
117 /// SUMMARY:
118 /// Return filesystem metadata for the underlying root directory.
119 #[inline]
120 pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
121 self.root.metadata()
122 }
123
124 /// SUMMARY:
125 /// Create a symbolic link at `link_path` pointing to this root's underlying directory.
126 pub fn virtual_symlink(
127 &self,
128 link_path: &crate::path::virtual_path::VirtualPath<Marker>,
129 ) -> std::io::Result<()> {
130 let root = self
131 .virtual_join("")
132 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
133
134 root.as_unvirtual().strict_symlink(link_path.as_unvirtual())
135 }
136
137 /// SUMMARY:
138 /// Create a hard link at `link_path` pointing to this root's underlying directory.
139 pub fn virtual_hard_link(
140 &self,
141 link_path: &crate::path::virtual_path::VirtualPath<Marker>,
142 ) -> std::io::Result<()> {
143 let root = self
144 .virtual_join("")
145 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
146
147 root.as_unvirtual()
148 .strict_hard_link(link_path.as_unvirtual())
149 }
150
151 /// SUMMARY:
152 /// Read directory entries at the virtual root (discovery). Re‑join names through virtual/strict APIs before I/O.
153 #[inline]
154 pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
155 self.root.read_dir()
156 }
157
158 /// SUMMARY:
159 /// Remove the underlying root directory (non‑recursive); fails if not empty.
160 #[inline]
161 pub fn remove_dir(&self) -> std::io::Result<()> {
162 self.root.remove_dir()
163 }
164
165 /// SUMMARY:
166 /// Recursively remove the underlying root directory and all its contents.
167 #[inline]
168 pub fn remove_dir_all(&self) -> std::io::Result<()> {
169 self.root.remove_dir_all()
170 }
171
172 /// SUMMARY:
173 /// Ensure the directory exists (create if missing), then return a `VirtualRoot`.
174 ///
175 /// EXAMPLE:
176 /// Uses `AsRef<Path>` for maximum ergonomics, including direct `TempDir` support for clean shadowing patterns:
177 /// ```rust
178 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
179 /// use strict_path::VirtualRoot;
180 /// let tmp_dir = tempfile::tempdir()?;
181 /// let tmp_dir = VirtualRoot::<()>::try_new_create(tmp_dir)?; // Clean variable shadowing
182 /// # Ok(())
183 /// # }
184 /// ```
185 #[inline]
186 pub fn try_new_create<P: AsRef<Path>>(root_path: P) -> Result<Self> {
187 let root = PathBoundary::try_new_create(root_path)?;
188 Ok(Self {
189 root,
190 #[cfg(feature = "tempfile")]
191 _temp_dir: None,
192 _marker: PhantomData,
193 })
194 }
195
196 /// SUMMARY:
197 /// Join a candidate path to this virtual root, producing a clamped `VirtualPath`.
198 ///
199 /// PARAMETERS:
200 /// - `candidate_path` (`AsRef<Path>`): Virtual path to resolve and clamp.
201 ///
202 /// RETURNS:
203 /// - `Result<VirtualPath<Marker>>`: Clamped, validated path within the virtual root.
204 ///
205 /// ERRORS:
206 /// - `StrictPathError::PathResolutionError`, `StrictPathError::PathEscapesBoundary`.
207 #[inline]
208 pub fn virtual_join<P: AsRef<Path>>(&self, candidate_path: P) -> Result<VirtualPath<Marker>> {
209 // 1) Anchor in virtual space (clamps virtual root and resolves relative parts)
210 let user_candidate = candidate_path.as_ref().to_path_buf();
211 let anchored = PathHistory::new(user_candidate).canonicalize_anchored(&self.root)?;
212
213 // 2) Boundary-check once against the PathBoundary's canonicalized root (no re-canonicalization)
214 let validated = anchored.boundary_check(self.root.stated_path())?;
215
216 // 3) Construct a StrictPath directly and then virtualize
217 let jp = crate::path::strict_path::StrictPath::new(
218 std::sync::Arc::new(self.root.clone()),
219 validated,
220 );
221 Ok(jp.virtualize())
222 }
223
224 /// Returns the underlying path boundary root as a system path.
225 #[inline]
226 pub(crate) fn path(&self) -> &Path {
227 self.root.path()
228 }
229
230 /// SUMMARY:
231 /// Return the virtual root path as `&OsStr` for allocation‑free `AsRef<Path>` interop.
232 #[inline]
233 pub fn interop_path(&self) -> &std::ffi::OsStr {
234 self.root.interop_path()
235 }
236
237 /// Returns true if the underlying path boundary root exists.
238 #[inline]
239 pub fn exists(&self) -> bool {
240 self.root.exists()
241 }
242
243 /// SUMMARY:
244 /// Borrow the underlying `PathBoundary`.
245 #[inline]
246 pub fn as_unvirtual(&self) -> &PathBoundary<Marker> {
247 &self.root
248 }
249
250 /// SUMMARY:
251 /// Consume this `VirtualRoot` and return the underlying `PathBoundary` (symmetry with `virtualize`).
252 #[inline]
253 pub fn unvirtual(self) -> PathBoundary<Marker> {
254 self.root
255 }
256
257 // OS Standard Directory Constructors
258 //
259 // Creates virtual roots in OS standard directories following platform conventions.
260 // Applications see clean virtual paths ("/config.toml") while the system manages
261 // the actual location (e.g., "~/.config/myapp/config.toml").
262
263 /// Creates a virtual root in the OS standard config directory.
264 ///
265 /// **Cross-Platform Behavior:**
266 /// - **Linux**: `~/.config/{app_name}` (XDG Base Directory Specification)
267 /// - **Windows**: `%APPDATA%\{app_name}` (Known Folder API - Roaming AppData)
268 /// - **macOS**: `~/Library/Application Support/{app_name}` (Apple Standard Directories)
269 #[cfg(feature = "dirs")]
270 pub fn try_new_os_config(app_name: &str) -> Result<Self> {
271 let root = crate::PathBoundary::try_new_os_config(app_name)?;
272 Ok(Self {
273 root,
274 #[cfg(feature = "tempfile")]
275 _temp_dir: None,
276 _marker: PhantomData,
277 })
278 }
279
280 /// Creates a virtual root in the OS standard data directory.
281 #[cfg(feature = "dirs")]
282 pub fn try_new_os_data(app_name: &str) -> Result<Self> {
283 let root = crate::PathBoundary::try_new_os_data(app_name)?;
284 Ok(Self {
285 root,
286 #[cfg(feature = "tempfile")]
287 _temp_dir: None,
288 _marker: PhantomData,
289 })
290 }
291
292 /// Creates a virtual root in the OS standard cache directory.
293 #[cfg(feature = "dirs")]
294 pub fn try_new_os_cache(app_name: &str) -> Result<Self> {
295 let root = crate::PathBoundary::try_new_os_cache(app_name)?;
296 Ok(Self {
297 root,
298 #[cfg(feature = "tempfile")]
299 _temp_dir: None,
300 _marker: PhantomData,
301 })
302 }
303
304 /// Creates a virtual root in the OS local config directory.
305 #[cfg(feature = "dirs")]
306 pub fn try_new_os_config_local(app_name: &str) -> Result<Self> {
307 let root = crate::PathBoundary::try_new_os_config_local(app_name)?;
308 Ok(Self {
309 root,
310 #[cfg(feature = "tempfile")]
311 _temp_dir: None,
312 _marker: PhantomData,
313 })
314 }
315
316 /// Creates a virtual root in the OS local data directory.
317 #[cfg(feature = "dirs")]
318 pub fn try_new_os_data_local(app_name: &str) -> Result<Self> {
319 let root = crate::PathBoundary::try_new_os_data_local(app_name)?;
320 Ok(Self {
321 root,
322 #[cfg(feature = "tempfile")]
323 _temp_dir: None,
324 _marker: PhantomData,
325 })
326 }
327
328 /// Creates a virtual root in the user's home directory.
329 #[cfg(feature = "dirs")]
330 pub fn try_new_os_home() -> Result<Self> {
331 let root = crate::PathBoundary::try_new_os_home()?;
332 Ok(Self {
333 root,
334 #[cfg(feature = "tempfile")]
335 _temp_dir: None,
336 _marker: PhantomData,
337 })
338 }
339
340 /// Creates a virtual root in the user's desktop directory.
341 #[cfg(feature = "dirs")]
342 pub fn try_new_os_desktop() -> Result<Self> {
343 let root = crate::PathBoundary::try_new_os_desktop()?;
344 Ok(Self {
345 root,
346 #[cfg(feature = "tempfile")]
347 _temp_dir: None,
348 _marker: PhantomData,
349 })
350 }
351
352 /// Creates a virtual root in the user's documents directory.
353 #[cfg(feature = "dirs")]
354 pub fn try_new_os_documents() -> Result<Self> {
355 let root = crate::PathBoundary::try_new_os_documents()?;
356 Ok(Self {
357 root,
358 #[cfg(feature = "tempfile")]
359 _temp_dir: None,
360 _marker: PhantomData,
361 })
362 }
363
364 /// Creates a virtual root in the user's downloads directory.
365 #[cfg(feature = "dirs")]
366 pub fn try_new_os_downloads() -> Result<Self> {
367 let root = crate::PathBoundary::try_new_os_downloads()?;
368 Ok(Self {
369 root,
370 #[cfg(feature = "tempfile")]
371 _temp_dir: None,
372 _marker: PhantomData,
373 })
374 }
375
376 /// Creates a virtual root in the user's pictures directory.
377 #[cfg(feature = "dirs")]
378 pub fn try_new_os_pictures() -> Result<Self> {
379 let root = crate::PathBoundary::try_new_os_pictures()?;
380 Ok(Self {
381 root,
382 #[cfg(feature = "tempfile")]
383 _temp_dir: None,
384 _marker: PhantomData,
385 })
386 }
387
388 /// Creates a virtual root in the user's music/audio directory.
389 #[cfg(feature = "dirs")]
390 pub fn try_new_os_audio() -> Result<Self> {
391 let root = crate::PathBoundary::try_new_os_audio()?;
392 Ok(Self {
393 root,
394 #[cfg(feature = "tempfile")]
395 _temp_dir: None,
396 _marker: PhantomData,
397 })
398 }
399
400 /// Creates a virtual root in the user's videos directory.
401 #[cfg(feature = "dirs")]
402 pub fn try_new_os_videos() -> Result<Self> {
403 let root = crate::PathBoundary::try_new_os_videos()?;
404 Ok(Self {
405 root,
406 #[cfg(feature = "tempfile")]
407 _temp_dir: None,
408 _marker: PhantomData,
409 })
410 }
411
412 /// Creates a virtual root in the OS executable directory (Linux only).
413 #[cfg(feature = "dirs")]
414 pub fn try_new_os_executables() -> Result<Self> {
415 let root = crate::PathBoundary::try_new_os_executables()?;
416 Ok(Self {
417 root,
418 #[cfg(feature = "tempfile")]
419 _temp_dir: None,
420 _marker: PhantomData,
421 })
422 }
423
424 /// Creates a virtual root in the OS runtime directory (Linux only).
425 #[cfg(feature = "dirs")]
426 pub fn try_new_os_runtime() -> Result<Self> {
427 let root = crate::PathBoundary::try_new_os_runtime()?;
428 Ok(Self {
429 root,
430 #[cfg(feature = "tempfile")]
431 _temp_dir: None,
432 _marker: PhantomData,
433 })
434 }
435
436 /// Creates a virtual root in the OS state directory (Linux only).
437 #[cfg(feature = "dirs")]
438 pub fn try_new_os_state(app_name: &str) -> Result<Self> {
439 let root = crate::PathBoundary::try_new_os_state(app_name)?;
440 Ok(Self {
441 root,
442 #[cfg(feature = "tempfile")]
443 _temp_dir: None,
444 _marker: PhantomData,
445 })
446 }
447
448 /// SUMMARY:
449 /// Create a virtual root using the `app-path` strategy (portable app‑relative directory),
450 /// optionally honoring an environment variable override.
451 ///
452 /// PARAMETERS:
453 /// - `subdir` (`AsRef<Path>`): Subdirectory path relative to the executable location (or to the
454 /// directory specified by the environment override). Accepts any path‑like value via `AsRef<Path>`.
455 /// - `env_override` (Option<&str>): Optional environment variable name to check first; when set
456 /// and the variable is present, its value is used as the root base instead of the executable directory.
457 ///
458 /// RETURNS:
459 /// - `Result<VirtualRoot<Marker>>`: Virtual root whose underlying `PathBoundary` is created if missing
460 /// and proven safe; all subsequent `virtual_join` operations are clamped to this root.
461 ///
462 /// ERRORS:
463 /// - `StrictPathError::InvalidRestriction`: If `app-path` resolution fails or the directory cannot be created/validated.
464 ///
465 /// EXAMPLE:
466 /// ```rust
467 /// # #[cfg(feature = "app-path")] {
468 /// use strict_path::VirtualRoot;
469 ///
470 /// // Create ./data relative to the executable (portable layout)
471 /// let vroot = VirtualRoot::<()>::try_new_app_path("data", None)?;
472 /// let vp = vroot.virtual_join("docs/report.txt")?;
473 /// assert_eq!(vp.virtualpath_display().to_string(), "/docs/report.txt");
474 ///
475 /// // With environment override: respects MYAPP_DATA_DIR when set
476 /// let _vroot = VirtualRoot::<()>::try_new_app_path("data", Some("MYAPP_DATA_DIR"))?;
477 /// # }
478 /// # Ok::<(), Box<dyn std::error::Error>>(())
479 /// ```
480 #[cfg(feature = "app-path")]
481 pub fn try_new_app_path<P: AsRef<Path>>(subdir: P, env_override: Option<&str>) -> Result<Self> {
482 let root = crate::PathBoundary::try_new_app_path(subdir, env_override)?;
483 Ok(Self {
484 root,
485 #[cfg(feature = "tempfile")]
486 _temp_dir: None,
487 _marker: PhantomData,
488 })
489 }
490
491 /// SUMMARY:
492 /// Create a virtual root via `app-path`, always consulting a specific environment variable
493 /// before falling back to the executable‑relative directory.
494 ///
495 /// PARAMETERS:
496 /// - `subdir` (`AsRef<Path>`): Subdirectory path used with `app-path` resolution.
497 /// - `env_override` (&str): Environment variable name to check first for the root base.
498 ///
499 /// RETURNS:
500 /// - `Result<VirtualRoot<Marker>>`: New virtual root anchored using `app-path` semantics.
501 ///
502 /// ERRORS:
503 /// - `StrictPathError::InvalidRestriction`: If resolution fails or the directory can't be created/validated.
504 ///
505 /// EXAMPLE:
506 /// ```rust
507 /// # #[cfg(feature = "app-path")] {
508 /// use strict_path::VirtualRoot;
509 /// let _vroot = VirtualRoot::<()>::try_new_app_path_with_env("cache", "MYAPP_CACHE_DIR")?;
510 /// # }
511 /// # Ok::<(), Box<dyn std::error::Error>>(())
512 /// ```
513 #[cfg(feature = "app-path")]
514 pub fn try_new_app_path_with_env<P: AsRef<Path>>(
515 subdir: P,
516 env_override: &str,
517 ) -> Result<Self> {
518 Self::try_new_app_path(subdir, Some(env_override))
519 }
520}
521
522impl<Marker> std::fmt::Display for VirtualRoot<Marker> {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 write!(f, "{}", self.path().display())
525 }
526}
527
528impl<Marker> AsRef<Path> for VirtualRoot<Marker> {
529 fn as_ref(&self) -> &Path {
530 self.path()
531 }
532}
533
534impl<Marker> std::fmt::Debug for VirtualRoot<Marker> {
535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
536 f.debug_struct("VirtualRoot")
537 .field("root", &self.path())
538 .field("marker", &std::any::type_name::<Marker>())
539 .finish()
540 }
541}
542
543impl<Marker> PartialEq for VirtualRoot<Marker> {
544 #[inline]
545 fn eq(&self, other: &Self) -> bool {
546 self.path() == other.path()
547 }
548}
549
550impl<Marker> Eq for VirtualRoot<Marker> {}
551
552impl<Marker> std::hash::Hash for VirtualRoot<Marker> {
553 #[inline]
554 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
555 self.path().hash(state);
556 }
557}
558
559impl<Marker> PartialOrd for VirtualRoot<Marker> {
560 #[inline]
561 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
562 Some(self.cmp(other))
563 }
564}
565
566impl<Marker> Ord for VirtualRoot<Marker> {
567 #[inline]
568 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
569 self.path().cmp(other.path())
570 }
571}
572
573impl<Marker> PartialEq<crate::PathBoundary<Marker>> for VirtualRoot<Marker> {
574 #[inline]
575 fn eq(&self, other: &crate::PathBoundary<Marker>) -> bool {
576 self.path() == other.path()
577 }
578}
579
580impl<Marker> PartialEq<std::path::Path> for VirtualRoot<Marker> {
581 #[inline]
582 fn eq(&self, other: &std::path::Path) -> bool {
583 // Compare as virtual root path (always "/")
584 // VirtualRoot represents the virtual "/" regardless of underlying system path
585 let other_str = other.to_string_lossy();
586
587 #[cfg(windows)]
588 let other_normalized = other_str.replace('\\', "/");
589 #[cfg(not(windows))]
590 let other_normalized = other_str.to_string();
591
592 let normalized_other = if other_normalized.starts_with('/') {
593 other_normalized
594 } else {
595 format!("/{}", other_normalized)
596 };
597
598 "/" == normalized_other
599 }
600}
601
602impl<Marker> PartialEq<std::path::PathBuf> for VirtualRoot<Marker> {
603 #[inline]
604 fn eq(&self, other: &std::path::PathBuf) -> bool {
605 self.eq(other.as_path())
606 }
607}
608
609impl<Marker> PartialEq<&std::path::Path> for VirtualRoot<Marker> {
610 #[inline]
611 fn eq(&self, other: &&std::path::Path) -> bool {
612 self.eq(*other)
613 }
614}
615
616impl<Marker> PartialOrd<std::path::Path> for VirtualRoot<Marker> {
617 #[inline]
618 fn partial_cmp(&self, other: &std::path::Path) -> Option<std::cmp::Ordering> {
619 // Compare as virtual root path (always "/")
620 let other_str = other.to_string_lossy();
621
622 // Handle empty path specially - "/" is greater than ""
623 if other_str.is_empty() {
624 return Some(std::cmp::Ordering::Greater);
625 }
626
627 #[cfg(windows)]
628 let other_normalized = other_str.replace('\\', "/");
629 #[cfg(not(windows))]
630 let other_normalized = other_str.to_string();
631
632 let normalized_other = if other_normalized.starts_with('/') {
633 other_normalized
634 } else {
635 format!("/{}", other_normalized)
636 };
637
638 Some("/".cmp(&normalized_other))
639 }
640}
641
642impl<Marker> PartialOrd<&std::path::Path> for VirtualRoot<Marker> {
643 #[inline]
644 fn partial_cmp(&self, other: &&std::path::Path) -> Option<std::cmp::Ordering> {
645 self.partial_cmp(*other)
646 }
647}
648
649impl<Marker> PartialOrd<std::path::PathBuf> for VirtualRoot<Marker> {
650 #[inline]
651 fn partial_cmp(&self, other: &std::path::PathBuf) -> Option<std::cmp::Ordering> {
652 self.partial_cmp(other.as_path())
653 }
654}
655
656impl<Marker: Default> std::str::FromStr for VirtualRoot<Marker> {
657 type Err = crate::StrictPathError;
658
659 /// Parse a VirtualRoot from a string path for universal ergonomics.
660 ///
661 /// Creates the directory if it doesn't exist, enabling seamless integration
662 /// with any string-parsing context (clap, config files, environment variables, etc.):
663 /// ```rust
664 /// # use strict_path::VirtualRoot;
665 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
666 /// let temp_dir = tempfile::tempdir()?;
667 /// let virtual_path = temp_dir.path().join("virtual_dir");
668 /// let vroot: VirtualRoot<()> = virtual_path.to_string_lossy().parse()?;
669 /// assert!(virtual_path.exists());
670 /// # Ok(())
671 /// # }
672 /// ```
673 #[inline]
674 fn from_str(path: &str) -> std::result::Result<Self, Self::Err> {
675 Self::try_new_create(path)
676 }
677}