strict_path/path/
strict_path.rs

1use crate::validator::path_history::{BoundaryChecked, Canonicalized, PathHistory, Raw};
2use crate::{Result, StrictPathError};
3use std::cmp::Ordering;
4use std::ffi::OsStr;
5use std::fmt;
6use std::hash::{Hash, Hasher};
7use std::marker::PhantomData;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11/// SUMMARY:
12/// Hold a validated, system-facing filesystem path guaranteed to be within a `PathBoundary`.
13///
14/// DETAILS:
15/// Use when you need system-facing I/O with safety proofs. For user-facing display and rooted
16/// virtual operations prefer `VirtualPath`. Operations like `strict_join` and
17/// `strictpath_parent` preserve guarantees. `Display` shows the real system path. String
18/// accessors are prefixed with `strictpath_` to avoid confusion.
19#[derive(Clone)]
20pub struct StrictPath<Marker = ()> {
21    path: PathHistory<((Raw, Canonicalized), BoundaryChecked)>,
22    boundary: Arc<crate::PathBoundary<Marker>>,
23    _marker: PhantomData<Marker>,
24}
25
26impl<Marker> StrictPath<Marker> {
27    /// SUMMARY:
28    /// Create the base `StrictPath` anchored at the provided boundary directory.
29    ///
30    /// PARAMETERS:
31    /// - `dir_path` (`AsRef<Path>`): Boundary directory (must exist).
32    ///
33    /// RETURNS:
34    /// - `Result<StrictPath<Marker>>`: Base path ("" join) within the boundary.
35    ///
36    /// ERRORS:
37    /// - `StrictPathError::InvalidRestriction`: If the boundary cannot be created/validated.
38    ///
39    /// NOTE: Prefer passing `PathBoundary` in reusable flows.
40    pub fn with_boundary<P: AsRef<Path>>(dir_path: P) -> Result<Self> {
41        let boundary = crate::PathBoundary::try_new(dir_path)?;
42        boundary.into_strictpath()
43    }
44
45    /// SUMMARY:
46    /// Create the base `StrictPath`, creating the boundary directory if missing.
47    pub fn with_boundary_create<P: AsRef<Path>>(dir_path: P) -> Result<Self> {
48        let boundary = crate::PathBoundary::try_new_create(dir_path)?;
49        boundary.into_strictpath()
50    }
51    pub(crate) fn new(
52        boundary: Arc<crate::PathBoundary<Marker>>,
53        validated_path: PathHistory<((Raw, Canonicalized), BoundaryChecked)>,
54    ) -> Self {
55        Self {
56            path: validated_path,
57            boundary,
58            _marker: PhantomData,
59        }
60    }
61
62    #[cfg(feature = "virtual-path")]
63    #[inline]
64    pub(crate) fn boundary(&self) -> &crate::PathBoundary<Marker> {
65        &self.boundary
66    }
67
68    #[inline]
69    pub(crate) fn path(&self) -> &Path {
70        &self.path
71    }
72
73    /// SUMMARY:
74    /// Return a lossy `String` view of the system path. Prefer `.interop_path()` only for unavoidable third-party interop.
75    #[inline]
76    pub fn strictpath_to_string_lossy(&self) -> std::borrow::Cow<'_, str> {
77        self.path.to_string_lossy()
78    }
79
80    /// SUMMARY:
81    /// Return the underlying system path as `&str` if valid UTF‑8; otherwise `None`.
82    #[inline]
83    pub fn strictpath_to_str(&self) -> Option<&str> {
84        self.path.to_str()
85    }
86
87    /// SUMMARY:
88    /// Return the underlying system path as `&OsStr` for unavoidable third-party `AsRef<Path>` interop.
89    #[inline]
90    pub fn interop_path(&self) -> &OsStr {
91        self.path.as_os_str()
92    }
93
94    /// SUMMARY:
95    /// Return a `Display` wrapper that shows the real system path.
96    #[inline]
97    pub fn strictpath_display(&self) -> std::path::Display<'_> {
98        self.path.display()
99    }
100
101    /// SUMMARY:
102    /// Consume and return the inner `PathBuf` (escape hatch). Prefer `.interop_path()` (third-party adapters only) to borrow.
103    #[inline]
104    pub fn unstrict(self) -> PathBuf {
105        self.path.into_inner()
106    }
107
108    /// SUMMARY:
109    /// Convert this `StrictPath` into a user‑facing `VirtualPath`.
110    #[cfg(feature = "virtual-path")]
111    #[inline]
112    pub fn virtualize(self) -> crate::path::virtual_path::VirtualPath<Marker> {
113        crate::path::virtual_path::VirtualPath::new(self)
114    }
115
116    /// SUMMARY:
117    /// Change the compile-time marker while reusing the validated strict path.
118    ///
119    /// WHEN TO USE:
120    /// - After authenticating/authorizing a user and granting them access to a path
121    /// - When escalating or downgrading permissions (e.g., ReadOnly → ReadWrite)
122    /// - When reinterpreting a path's domain (e.g., TempStorage → UserUploads)
123    ///
124    /// WHEN NOT TO USE:
125    /// - When converting between path types - conversions preserve markers automatically
126    /// - When the current marker already matches your needs - no transformation needed
127    /// - When you haven't verified authorization - NEVER change markers without checking permissions
128    ///
129    /// PARAMETERS:
130    /// - `_none_`
131    ///
132    /// RETURNS:
133    /// - `StrictPath<NewMarker>`: Same boundary-checked system path encoded with the new marker.
134    ///
135    /// ERRORS:
136    /// - `_none_`
137    ///
138    /// SECURITY:
139    /// The caller MUST ensure the new marker reflects real-world permissions. This method does not
140    /// perform any authorization checks.
141    ///
142    /// EXAMPLE:
143    /// ```rust
144    /// # use strict_path::{PathBoundary, StrictPath};
145    /// # use std::io;
146    /// # struct UserFiles;
147    /// # struct ReadOnly;
148    /// # struct ReadWrite;
149    /// # let boundary_dir = std::env::temp_dir().join("strict-path-change-marker-example");
150    /// # std::fs::create_dir_all(&boundary_dir.join("logs"))?;
151    /// # let boundary: PathBoundary = PathBoundary::try_new(&boundary_dir)?;
152    /// #
153    /// // Verify user can write before granting write access
154    /// fn authorize_write_access(
155    ///     user_id: &str,
156    ///     path: StrictPath<(UserFiles, ReadOnly)>
157    /// ) -> Result<StrictPath<(UserFiles, ReadWrite)>, &'static str> {
158    ///     if user_id == "admin" {
159    ///         Ok(path.change_marker())  // ✅ Transform after authorization check
160    ///     } else {
161    ///         Err("insufficient permissions")  // ❌ User lacks write permission
162    ///     }
163    /// }
164    ///
165    /// // Function requiring write permission - enforces type safety at compile time
166    /// fn write_log_entry(path: StrictPath<(UserFiles, ReadWrite)>, content: &str) -> io::Result<()> {
167    ///     path.write(content.as_bytes())
168    /// }
169    ///
170    /// // Start with read-only access
171    /// let read_only_path: StrictPath<(UserFiles, ReadOnly)> =
172    ///     boundary.strict_join("logs/app.log")?.change_marker();
173    ///
174    /// // Elevate permissions after authorization
175    /// let read_write_path = authorize_write_access("admin", read_only_path)
176    ///     .expect("user must have sufficient permissions");
177    ///
178    /// // Now we can call functions requiring write access
179    /// write_log_entry(read_write_path, "Application started")?;
180    /// #
181    /// # std::fs::remove_dir_all(&boundary_dir)?;
182    /// # Ok::<_, Box<dyn std::error::Error>>(())
183    /// ```
184    ///
185    /// **Type Safety Guarantee:**
186    ///
187    /// The following code **fails to compile** because you cannot pass a path with one marker
188    /// type to a function expecting a different marker type. This compile-time check enforces
189    /// that permission changes are explicit and cannot be bypassed accidentally.
190    ///
191    /// ```compile_fail
192    /// # use strict_path::{PathBoundary, StrictPath};
193    /// # struct ReadOnly;
194    /// # struct WritePermission;
195    /// # let boundary_dir = std::env::temp_dir().join("strict-path-change-marker-deny");
196    /// # std::fs::create_dir_all(&boundary_dir).unwrap();
197    /// # let boundary: PathBoundary<ReadOnly> = PathBoundary::try_new(&boundary_dir).unwrap();
198    /// let read_only_path: StrictPath<ReadOnly> = boundary.strict_join("logs/app.log").unwrap();
199    /// fn require_write(_: StrictPath<WritePermission>) {}
200    /// // ❌ Compile error: expected `StrictPath<WritePermission>`, found `StrictPath<ReadOnly>`
201    /// require_write(read_only_path);
202    /// ```
203    #[inline]
204    pub fn change_marker<NewMarker>(self) -> StrictPath<NewMarker> {
205        let StrictPath { path, boundary, .. } = self;
206
207        // Try to unwrap the Arc (zero-cost if this is the only reference).
208        // If other references exist, clone the boundary (allocation needed).
209        let boundary_owned = Arc::try_unwrap(boundary).unwrap_or_else(|arc| (*arc).clone());
210        let new_boundary = Arc::new(boundary_owned.change_marker::<NewMarker>());
211
212        StrictPath {
213            path,
214            boundary: new_boundary,
215            _marker: PhantomData,
216        }
217    }
218
219    /// SUMMARY:
220    /// Consume and return a new `PathBoundary` anchored at this strict path.
221    ///
222    /// RETURNS:
223    /// - `Result<PathBoundary<Marker>>`: Boundary anchored at the strict path's
224    ///   system location (must already exist and be a directory).
225    ///
226    /// ERRORS:
227    /// - `StrictPathError::InvalidRestriction`: If the strict path does not exist
228    ///   or is not a directory.
229    #[inline]
230    pub fn try_into_boundary(self) -> Result<crate::PathBoundary<Marker>> {
231        let StrictPath { path, .. } = self;
232        crate::PathBoundary::try_new(path.into_inner())
233    }
234
235    /// SUMMARY:
236    /// Consume and return a `PathBoundary`, creating the directory if missing.
237    ///
238    /// RETURNS:
239    /// - `Result<PathBoundary<Marker>>`: Boundary anchored at the strict path's
240    ///   system location (created if necessary).
241    ///
242    /// ERRORS:
243    /// - `StrictPathError::InvalidRestriction`: If creation or canonicalization fails.
244    #[inline]
245    pub fn try_into_boundary_create(self) -> Result<crate::PathBoundary<Marker>> {
246        let StrictPath { path, .. } = self;
247        crate::PathBoundary::try_new_create(path.into_inner())
248    }
249
250    /// SUMMARY:
251    /// Join a path segment and re-validate against the boundary.
252    ///
253    /// NOTE:
254    /// Never call `Path::join` on a leaked system path (e.g., from `interop_path()` or `unstrict()`); always re-validate through this method.
255    ///
256    /// PARAMETERS:
257    /// - `path` (`AsRef<Path>`): Segment or absolute path to validate.
258    ///
259    /// RETURNS:
260    /// - `Result<StrictPath<Marker>>`: Validated path inside the boundary.
261    ///
262    /// ERRORS:
263    /// - `StrictPathError::PathResolutionError`, `StrictPathError::PathEscapesBoundary`.
264    #[inline]
265    pub fn strict_join<P: AsRef<Path>>(&self, path: P) -> Result<Self> {
266        let new_systempath = self.path.join(path);
267        self.boundary.strict_join(new_systempath)
268    }
269
270    /// SUMMARY:
271    /// Return the parent as a new `StrictPath`, or `None` at the boundary root.
272    pub fn strictpath_parent(&self) -> Result<Option<Self>> {
273        match self.path.parent() {
274            Some(p) => match self.boundary.strict_join(p) {
275                Ok(p) => Ok(Some(p)),
276                Err(e) => Err(e),
277            },
278            None => Ok(None),
279        }
280    }
281
282    /// SUMMARY:
283    /// Return a new path with file name changed, re‑validating against the boundary.
284    #[inline]
285    pub fn strictpath_with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<Self> {
286        let new_systempath = self.path.with_file_name(file_name);
287        self.boundary.strict_join(new_systempath)
288    }
289
290    /// SUMMARY:
291    /// Return a new path with extension changed; error at the boundary root.
292    pub fn strictpath_with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<Self> {
293        let system_path = &self.path;
294        if system_path.file_name().is_none() {
295            return Err(StrictPathError::path_escapes_boundary(
296                self.path.to_path_buf(),
297                self.boundary.path().to_path_buf(),
298            ));
299        }
300        let new_systempath = system_path.with_extension(extension);
301        self.boundary.strict_join(new_systempath)
302    }
303
304    /// Returns the file name component of the system path, if any.
305    #[inline]
306    pub fn strictpath_file_name(&self) -> Option<&OsStr> {
307        self.path.file_name()
308    }
309
310    /// Returns the file stem of the system path, if any.
311    #[inline]
312    pub fn strictpath_file_stem(&self) -> Option<&OsStr> {
313        self.path.file_stem()
314    }
315
316    /// Returns the extension of the system path, if any.
317    #[inline]
318    pub fn strictpath_extension(&self) -> Option<&OsStr> {
319        self.path.extension()
320    }
321
322    /// Returns `true` if the system path starts with the given prefix.
323    #[inline]
324    pub fn strictpath_starts_with<P: AsRef<Path>>(&self, p: P) -> bool {
325        self.path.starts_with(p.as_ref())
326    }
327
328    /// Returns `true` if the system path ends with the given suffix.
329    #[inline]
330    pub fn strictpath_ends_with<P: AsRef<Path>>(&self, p: P) -> bool {
331        self.path.ends_with(p.as_ref())
332    }
333
334    /// Returns `true` if the system path exists.
335    pub fn exists(&self) -> bool {
336        self.path.exists()
337    }
338
339    /// Returns `true` if the system path is a file.
340    pub fn is_file(&self) -> bool {
341        self.path.is_file()
342    }
343
344    /// Returns `true` if the system path is a directory.
345    pub fn is_dir(&self) -> bool {
346        self.path.is_dir()
347    }
348
349    /// Returns the metadata for the system path.
350    pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
351        std::fs::metadata(&self.path)
352    }
353
354    /// SUMMARY:
355    /// Read directory entries at this path (discovery). Re‑join names through strict/virtual APIs before I/O.
356    pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
357        std::fs::read_dir(&self.path)
358    }
359
360    /// Reads the file contents as `String`.
361    pub fn read_to_string(&self) -> std::io::Result<String> {
362        std::fs::read_to_string(&self.path)
363    }
364
365    /// Reads the file contents as raw bytes.
366    #[deprecated(since = "0.1.0-alpha.5", note = "Use read() instead")]
367    pub fn read_bytes(&self) -> std::io::Result<Vec<u8>> {
368        std::fs::read(&self.path)
369    }
370
371    /// Writes raw bytes to the file, creating it if it does not exist.
372    #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
373    pub fn write_bytes(&self, data: &[u8]) -> std::io::Result<()> {
374        std::fs::write(&self.path, data)
375    }
376
377    /// Writes a UTF-8 string to the file, creating it if it does not exist.
378    #[deprecated(since = "0.1.0-alpha.5", note = "Use write(...) instead")]
379    pub fn write_string(&self, data: &str) -> std::io::Result<()> {
380        std::fs::write(&self.path, data)
381    }
382
383    /// Reads the file contents as raw bytes (replacement for `read_bytes`).
384    #[inline]
385    pub fn read(&self) -> std::io::Result<Vec<u8>> {
386        std::fs::read(&self.path)
387    }
388
389    /// SUMMARY:
390    /// Write bytes to the file (create if missing). Accepts any `AsRef<[u8]>` (e.g., `&str`, `&[u8]`).
391    #[inline]
392    pub fn write<C: AsRef<[u8]>>(&self, contents: C) -> std::io::Result<()> {
393        std::fs::write(&self.path, contents)
394    }
395
396    /// SUMMARY:
397    /// Create or truncate the file at this strict path and return a writable handle.
398    ///
399    /// PARAMETERS:
400    /// - _none_
401    ///
402    /// RETURNS:
403    /// - `std::fs::File`: Writable handle scoped to this boundary.
404    ///
405    /// ERRORS:
406    /// - `std::io::Error`: Propagates OS errors when the parent directory is missing or file creation fails.
407    ///
408    /// EXAMPLE:
409    /// ```rust
410    /// # use strict_path::{PathBoundary, StrictPath};
411    /// # use std::io::Write;
412    /// # let boundary_dir = std::env::temp_dir().join("strict-path-create-file-example");
413    /// # std::fs::create_dir_all(&boundary_dir)?;
414    /// # let boundary: PathBoundary = PathBoundary::try_new(&boundary_dir)?;
415    /// let log_path: StrictPath = boundary.strict_join("logs/app.log")?;
416    /// log_path.create_parent_dir_all()?;
417    /// let mut file = log_path.create_file()?;
418    /// file.write_all(b"session started")?;
419    /// # std::fs::remove_dir_all(&boundary_dir)?;
420    /// # Ok::<_, Box<dyn std::error::Error>>(())
421    /// ```
422    #[inline]
423    pub fn create_file(&self) -> std::io::Result<std::fs::File> {
424        std::fs::File::create(&self.path)
425    }
426
427    /// SUMMARY:
428    /// Open the file at this strict path in read-only mode.
429    ///
430    /// PARAMETERS:
431    /// - _none_
432    ///
433    /// RETURNS:
434    /// - `std::fs::File`: Read-only handle scoped to this boundary.
435    ///
436    /// ERRORS:
437    /// - `std::io::Error`: Propagates OS errors when the file is missing or inaccessible.
438    ///
439    /// EXAMPLE:
440    /// ```rust
441    /// # use strict_path::{PathBoundary, StrictPath};
442    /// # use std::io::{Read, Write};
443    /// # let boundary_dir = std::env::temp_dir().join("strict-path-open-file-example");
444    /// # std::fs::create_dir_all(&boundary_dir)?;
445    /// # let boundary: PathBoundary = PathBoundary::try_new(&boundary_dir)?;
446    /// let transcript: StrictPath = boundary.strict_join("logs/session.log")?;
447    /// transcript.create_parent_dir_all()?;
448    /// transcript.write("session start")?;
449    /// let mut file = transcript.open_file()?;
450    /// let mut contents = String::new();
451    /// file.read_to_string(&mut contents)?;
452    /// assert_eq!(contents, "session start");
453    /// # std::fs::remove_dir_all(&boundary_dir)?;
454    /// # Ok::<_, Box<dyn std::error::Error>>(())
455    /// ```
456    #[inline]
457    pub fn open_file(&self) -> std::io::Result<std::fs::File> {
458        std::fs::File::open(&self.path)
459    }
460
461    /// Creates all directories in the system path if missing (like `std::fs::create_dir_all`).
462    pub fn create_dir_all(&self) -> std::io::Result<()> {
463        std::fs::create_dir_all(&self.path)
464    }
465
466    /// Creates the directory at the system path (non-recursive, like `std::fs::create_dir`).
467    ///
468    /// Fails if the parent directory does not exist. Use `create_dir_all` to
469    /// create missing parent directories recursively.
470    pub fn create_dir(&self) -> std::io::Result<()> {
471        std::fs::create_dir(&self.path)
472    }
473
474    /// SUMMARY:
475    /// Create only the immediate parent directory (non‑recursive). `Ok(())` at the boundary root.
476    pub fn create_parent_dir(&self) -> std::io::Result<()> {
477        match self.strictpath_parent() {
478            Ok(Some(parent)) => parent.create_dir(),
479            Ok(None) => Ok(()),
480            Err(StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
481            Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
482        }
483    }
484
485    /// SUMMARY:
486    /// Recursively create all missing directories up to the immediate parent. `Ok(())` at boundary.
487    pub fn create_parent_dir_all(&self) -> std::io::Result<()> {
488        match self.strictpath_parent() {
489            Ok(Some(parent)) => parent.create_dir_all(),
490            Ok(None) => Ok(()),
491            Err(StrictPathError::PathEscapesBoundary { .. }) => Ok(()),
492            Err(e) => Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
493        }
494    }
495
496    /// SUMMARY:
497    /// Create a symbolic link at this location pointing to `target` (same boundary required).
498    /// On Windows, file vs directory symlink is selected by target metadata (or best‑effort when missing).
499    pub fn strict_symlink(&self, link_path: &Self) -> std::io::Result<()> {
500        if self.boundary.path() != link_path.boundary.path() {
501            let err = StrictPathError::path_escapes_boundary(
502                link_path.path().to_path_buf(),
503                self.boundary.path().to_path_buf(),
504            );
505            return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
506        }
507
508        #[cfg(unix)]
509        {
510            std::os::unix::fs::symlink(self.path(), link_path.path())?;
511        }
512
513        #[cfg(windows)]
514        {
515            create_windows_symlink(self.path(), link_path.path())?;
516        }
517
518        Ok(())
519    }
520
521    /// SUMMARY:
522    /// Create a hard link at `link_path` pointing to this path (same boundary; caller creates parents).
523    pub fn strict_hard_link(&self, link_path: &Self) -> std::io::Result<()> {
524        if self.boundary.path() != link_path.boundary.path() {
525            let err = StrictPathError::path_escapes_boundary(
526                link_path.path().to_path_buf(),
527                self.boundary.path().to_path_buf(),
528            );
529            return Err(std::io::Error::new(std::io::ErrorKind::Other, err));
530        }
531
532        std::fs::hard_link(self.path(), link_path.path())?;
533
534        Ok(())
535    }
536
537    /// SUMMARY:
538    /// Rename/move within the same boundary. Relative destinations are siblings; absolute are validated.
539    /// Parents are not created automatically.
540    pub fn strict_rename<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<()> {
541        let dest_ref = dest.as_ref();
542
543        // Compute destination under the parent directory for relative paths; allow absolute too
544        let dest_path = if dest_ref.is_absolute() {
545            match self.boundary.strict_join(dest_ref) {
546                Ok(p) => p,
547                Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
548            }
549        } else {
550            let parent = match self.strictpath_parent() {
551                Ok(Some(p)) => p,
552                Ok(None) => match self.boundary.as_ref().clone().into_strictpath() {
553                    Ok(root) => root,
554                    Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
555                },
556                Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
557            };
558            match parent.strict_join(dest_ref) {
559                Ok(p) => p,
560                Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
561            }
562        };
563
564        std::fs::rename(self.path(), dest_path.path())
565    }
566
567    /// SUMMARY:
568    /// Copy within the same boundary. Relative destinations are siblings; absolute are validated.
569    /// Parents are not created automatically. Returns bytes copied.
570    pub fn strict_copy<P: AsRef<Path>>(&self, dest: P) -> std::io::Result<u64> {
571        let dest_ref = dest.as_ref();
572
573        // Compute destination under the parent directory for relative paths; allow absolute too
574        let dest_path = if dest_ref.is_absolute() {
575            match self.boundary.strict_join(dest_ref) {
576                Ok(p) => p,
577                Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
578            }
579        } else {
580            let parent = match self.strictpath_parent() {
581                Ok(Some(p)) => p,
582                Ok(None) => match self.boundary.as_ref().clone().into_strictpath() {
583                    Ok(root) => root,
584                    Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
585                },
586                Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
587            };
588            match parent.strict_join(dest_ref) {
589                Ok(p) => p,
590                Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
591            }
592        };
593
594        std::fs::copy(self.path(), dest_path.path())
595    }
596
597    /// SUMMARY:
598    /// Remove the file at this path.
599    pub fn remove_file(&self) -> std::io::Result<()> {
600        std::fs::remove_file(&self.path)
601    }
602
603    /// SUMMARY:
604    /// Remove the directory at this path.
605    pub fn remove_dir(&self) -> std::io::Result<()> {
606        std::fs::remove_dir(&self.path)
607    }
608
609    /// SUMMARY:
610    /// Recursively remove the directory and its contents.
611    pub fn remove_dir_all(&self) -> std::io::Result<()> {
612        std::fs::remove_dir_all(&self.path)
613    }
614}
615
616#[cfg(feature = "serde")]
617impl<Marker> serde::Serialize for StrictPath<Marker> {
618    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
619    where
620        S: serde::Serializer,
621    {
622        serializer.serialize_str(self.strictpath_to_string_lossy().as_ref())
623    }
624}
625
626impl<Marker> fmt::Debug for StrictPath<Marker> {
627    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
628        f.debug_struct("StrictPath")
629            .field("path", &self.path)
630            .field("boundary", &self.boundary.path())
631            .field("marker", &std::any::type_name::<Marker>())
632            .finish()
633    }
634}
635
636#[cfg(windows)]
637fn create_windows_symlink(src: &Path, link: &Path) -> std::io::Result<()> {
638    use std::os::windows::fs::{symlink_dir, symlink_file};
639
640    match std::fs::metadata(src) {
641        Ok(metadata) => {
642            if metadata.is_dir() {
643                symlink_dir(src, link)
644            } else {
645                symlink_file(src, link)
646            }
647        }
648        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
649            match symlink_file(src, link) {
650                Ok(()) => Ok(()),
651                Err(file_err) => {
652                    if let Some(code) = file_err.raw_os_error() {
653                        const ERROR_DIRECTORY: i32 = 267; // target resolved as directory
654                        if code == ERROR_DIRECTORY {
655                            return symlink_dir(src, link);
656                        }
657                    }
658                    Err(file_err)
659                }
660            }
661        }
662        Err(err) => Err(err),
663    }
664}
665
666impl<Marker> PartialEq for StrictPath<Marker> {
667    #[inline]
668    fn eq(&self, other: &Self) -> bool {
669        self.path.as_ref() == other.path.as_ref()
670    }
671}
672
673impl<Marker> Eq for StrictPath<Marker> {}
674
675impl<Marker> Hash for StrictPath<Marker> {
676    #[inline]
677    fn hash<H: Hasher>(&self, state: &mut H) {
678        self.path.hash(state);
679    }
680}
681
682impl<Marker> PartialOrd for StrictPath<Marker> {
683    #[inline]
684    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
685        Some(self.cmp(other))
686    }
687}
688
689impl<Marker> Ord for StrictPath<Marker> {
690    #[inline]
691    fn cmp(&self, other: &Self) -> Ordering {
692        self.path.cmp(&other.path)
693    }
694}
695
696impl<T: AsRef<Path>, Marker> PartialEq<T> for StrictPath<Marker> {
697    fn eq(&self, other: &T) -> bool {
698        self.path.as_ref() == other.as_ref()
699    }
700}
701
702impl<T: AsRef<Path>, Marker> PartialOrd<T> for StrictPath<Marker> {
703    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
704        Some(self.path.as_ref().cmp(other.as_ref()))
705    }
706}
707
708#[cfg(feature = "virtual-path")]
709impl<Marker> PartialEq<crate::path::virtual_path::VirtualPath<Marker>> for StrictPath<Marker> {
710    #[inline]
711    fn eq(&self, other: &crate::path::virtual_path::VirtualPath<Marker>) -> bool {
712        self.path.as_ref() == other.interop_path()
713    }
714}