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}