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