Skip to main content

typed_path/typed/utf8/
pathbuf.rs

1use alloc::collections::TryReserveError;
2use core::convert::TryFrom;
3use core::fmt;
4
5#[cfg(feature = "serde")]
6use serde_core::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
7
8use crate::common::{CheckedPathError, StripPrefixError};
9use crate::no_std_compat::*;
10use crate::typed::{
11    PathType, Utf8TypedAncestors, Utf8TypedComponents, Utf8TypedIter, Utf8TypedPath,
12};
13use crate::unix::{Utf8UnixPath, Utf8UnixPathBuf};
14use crate::windows::{Utf8WindowsPath, Utf8WindowsPathBuf};
15
16/// Represents a pathbuf with a known type that can be one of:
17///
18/// * [`Utf8UnixPathBuf`]
19/// * [`Utf8WindowsPathBuf`]
20#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
21pub enum Utf8TypedPathBuf {
22    Unix(Utf8UnixPathBuf),
23    Windows(Utf8WindowsPathBuf),
24}
25
26impl Utf8TypedPathBuf {
27    /// Returns true if this path represents a Unix path.
28    #[inline]
29    pub fn is_unix(&self) -> bool {
30        matches!(self, Self::Unix(_))
31    }
32
33    /// Returns true if this path represents a Windows path.
34    #[inline]
35    pub fn is_windows(&self) -> bool {
36        matches!(self, Self::Windows(_))
37    }
38
39    /// Converts this [`Utf8TypedPathBuf`] into the Unix variant.
40    pub fn with_unix_encoding(&self) -> Utf8TypedPathBuf {
41        match self {
42            Self::Windows(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding()),
43            _ => self.clone(),
44        }
45    }
46
47    /// Converts this [`Utf8TypedPathBuf`] into the Unix variant, ensuring it is valid as a Unix
48    /// path.
49    pub fn with_unix_encoding_checked(&self) -> Result<Utf8TypedPathBuf, CheckedPathError> {
50        Ok(match self {
51            Self::Unix(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding_checked()?),
52            Self::Windows(p) => Utf8TypedPathBuf::Unix(p.with_unix_encoding_checked()?),
53        })
54    }
55
56    /// Converts this [`Utf8TypedPathBuf`] into the Windows variant.
57    pub fn with_windows_encoding(&self) -> Utf8TypedPathBuf {
58        match self {
59            Self::Unix(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding()),
60            _ => self.clone(),
61        }
62    }
63
64    /// Converts this [`Utf8TypedPathBuf`] into the Windows variant, ensuring it is valid as a
65    /// Windows path.
66    pub fn with_windows_encoding_checked(&self) -> Result<Utf8TypedPathBuf, CheckedPathError> {
67        Ok(match self {
68            Self::Unix(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding_checked()?),
69            Self::Windows(p) => Utf8TypedPathBuf::Windows(p.with_windows_encoding_checked()?),
70        })
71    }
72
73    /// Allocates an empty [`Utf8TypedPathBuf`] for the specified path type.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use typed_path::{PathType, Utf8TypedPathBuf};
79    /// let _unix_path = Utf8TypedPathBuf::new(PathType::Unix);
80    /// let _windows_path = Utf8TypedPathBuf::new(PathType::Windows);
81    /// ```
82    #[inline]
83    pub fn new(r#type: PathType) -> Self {
84        match r#type {
85            PathType::Unix => Self::unix(),
86            PathType::Windows => Self::windows(),
87        }
88    }
89
90    /// Allocates an empty [`Utf8TypedPathBuf`] as a Unix path.
91    #[inline]
92    pub fn unix() -> Self {
93        Self::Unix(Utf8UnixPathBuf::new())
94    }
95
96    /// Allocates an empty [`Utf8TypedPathBuf`] as a Windows path.
97    #[inline]
98    pub fn windows() -> Self {
99        Self::Windows(Utf8WindowsPathBuf::new())
100    }
101
102    /// Creates a new [`Utf8TypedPathBuf`] from the bytes representing a Unix path.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use typed_path::Utf8TypedPathBuf;
108    /// let path = Utf8TypedPathBuf::from_unix("/tmp");
109    /// ```
110    pub fn from_unix(s: impl AsRef<str>) -> Self {
111        Self::Unix(Utf8UnixPathBuf::from(s.as_ref()))
112    }
113
114    /// Creates a new [`Utf8TypedPathBuf`] from the bytes representing a Windows path.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use typed_path::Utf8TypedPathBuf;
120    /// let path = Utf8TypedPathBuf::from_windows(r"C:\tmp");
121    /// ```
122    pub fn from_windows(s: impl AsRef<str>) -> Self {
123        Self::Windows(Utf8WindowsPathBuf::from(s.as_ref()))
124    }
125
126    /// Converts into a [`Utf8TypedPath`].
127    pub fn to_path(&self) -> Utf8TypedPath<'_> {
128        match self {
129            Self::Unix(path) => Utf8TypedPath::Unix(path.as_path()),
130            Self::Windows(path) => Utf8TypedPath::Windows(path.as_path()),
131        }
132    }
133
134    /// Extends `self` with `path`.
135    ///
136    /// If `path` is absolute, it replaces the current path.
137    ///
138    /// With [`Utf8WindowsPathBuf`]:
139    ///
140    /// * if `path` has a root but no prefix (e.g., `\windows`), it
141    ///   replaces everything except for the prefix (if any) of `self`.
142    /// * if `path` has a prefix but no root, it replaces `self`.
143    /// * if `self` has a verbatim prefix (e.g. `\\?\C:\windows`)
144    ///   and `path` is not empty, the new path is normalized: all references
145    ///   to `.` and `..` are removed.
146    ///
147    /// [`Utf8WindowsPathBuf`]: crate::Utf8WindowsPathBuf
148    ///
149    /// # Difference from PathBuf
150    ///
151    /// Unlike [`Utf8PathBuf::push`], this implementation only supports types that implement
152    /// `AsRef<str>` instead of `AsRef<Path>`.
153    ///
154    /// [`Utf8PathBuf::push`]: crate::Utf8PathBuf::push
155    ///
156    /// # Examples
157    ///
158    /// Pushing a relative path extends the existing path:
159    ///
160    /// ```
161    /// use typed_path::Utf8TypedPathBuf;
162    ///
163    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
164    /// path.push("file.bk");
165    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/tmp/file.bk"));
166    /// ```
167    ///
168    /// Pushing an absolute path replaces the existing path:
169    ///
170    /// ```
171    /// use typed_path::Utf8TypedPathBuf;
172    ///
173    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
174    /// path.push("/etc");
175    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/etc"));
176    /// ```
177    pub fn push(&mut self, path: impl AsRef<str>) {
178        match self {
179            Self::Unix(a) => a.push(Utf8UnixPath::new(&path)),
180            Self::Windows(a) => a.push(Utf8WindowsPath::new(&path)),
181        }
182    }
183
184    /// Like [`Utf8TypedPathBuf::push`], extends `self` with `path`, but also checks to ensure that
185    /// `path` abides by a set of rules.
186    ///
187    /// # Rules
188    ///
189    /// 1. `path` cannot contain a prefix component.
190    /// 2. `path` cannot contain a root component.
191    /// 3. `path` cannot contain invalid filename bytes.
192    /// 4. `path` cannot contain parent components such that the current path would be escaped.
193    ///
194    /// # Difference from PathBuf
195    ///
196    /// Unlike [`PathBuf::push_checked`], this implementation only supports types that implement
197    /// `AsRef<str>` instead of `AsRef<Path>`.
198    ///
199    /// [`PathBuf::push_checked`]: crate::PathBuf::push_checked
200    ///
201    /// # Examples
202    ///
203    /// Pushing a relative path extends the existing path:
204    ///
205    /// ```
206    /// use typed_path::Utf8TypedPathBuf;
207    ///
208    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
209    /// assert!(path.push_checked("file.bk").is_ok());
210    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/tmp/file.bk"));
211    /// ```
212    ///
213    /// Pushing a relative path that contains unresolved parent directory references fails
214    /// with an error:
215    ///
216    /// ```
217    /// use typed_path::{CheckedPathError, Utf8TypedPathBuf};
218    ///
219    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
220    ///
221    /// // Pushing a relative path that contains parent directory references that cannot be
222    /// // resolved within the path is considered an error as this is considered a path
223    /// // traversal attack!
224    /// assert_eq!(path.push_checked(".."), Err(CheckedPathError::PathTraversalAttack));
225    /// assert_eq!(path, Utf8TypedPathBuf::from("/tmp"));
226    /// ```
227    ///
228    /// Pushing an absolute path fails with an error:
229    ///
230    /// ```
231    /// use typed_path::{CheckedPathError, Utf8TypedPathBuf};
232    ///
233    /// let mut path = Utf8TypedPathBuf::from_unix("/tmp");
234    ///
235    /// // Pushing an absolute path will fail with an error
236    /// assert_eq!(path.push_checked("/etc"), Err(CheckedPathError::UnexpectedRoot));
237    /// assert_eq!(path, Utf8TypedPathBuf::from_unix("/tmp"));
238    /// ```
239    pub fn push_checked(&mut self, path: impl AsRef<str>) -> Result<(), CheckedPathError> {
240        match self {
241            Self::Unix(a) => a.push_checked(Utf8UnixPath::new(&path)),
242            Self::Windows(a) => a.push_checked(Utf8WindowsPath::new(&path)),
243        }
244    }
245
246    /// Truncates `self` to [`self.parent`].
247    ///
248    /// Returns `false` and does nothing if [`self.parent`] is [`None`].
249    /// Otherwise, returns `true`.
250    ///
251    /// [`self.parent`]: Utf8TypedPath::parent
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
257    ///
258    /// let mut p = Utf8TypedPathBuf::from_unix("/spirited/away.rs");
259    ///
260    /// p.pop();
261    /// assert_eq!(Utf8TypedPath::derive("/spirited"), p);
262    /// p.pop();
263    /// assert_eq!(Utf8TypedPath::derive("/"), p);
264    /// ```
265    pub fn pop(&mut self) -> bool {
266        impl_typed_fn!(self, pop)
267    }
268
269    /// Updates [`self.file_name`] to `file_name`.
270    ///
271    /// If [`self.file_name`] was [`None`], this is equivalent to pushing
272    /// `file_name`.
273    ///
274    /// Otherwise it is equivalent to calling [`pop`] and then pushing
275    /// `file_name`. The new path will be a sibling of the original path.
276    /// (That is, it will have the same parent.)
277    ///
278    /// [`self.file_name`]: Utf8TypedPath::file_name
279    /// [`pop`]: Utf8TypedPathBuf::pop
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// use typed_path::Utf8TypedPathBuf;
285    ///
286    /// let mut buf = Utf8TypedPathBuf::from_unix("/");
287    /// assert!(buf.file_name() == None);
288    /// buf.set_file_name("bar");
289    /// assert!(buf == Utf8TypedPathBuf::from_unix("/bar"));
290    /// assert!(buf.file_name().is_some());
291    /// buf.set_file_name("baz.txt");
292    /// assert!(buf == Utf8TypedPathBuf::from_unix("/baz.txt"));
293    /// ```
294    pub fn set_file_name<S: AsRef<str>>(&mut self, file_name: S) {
295        impl_typed_fn!(self, set_file_name, file_name)
296    }
297
298    /// Updates [`self.extension`] to `extension`.
299    ///
300    /// Returns `false` and does nothing if [`self.file_name`] is [`None`],
301    /// returns `true` and updates the extension otherwise.
302    ///
303    /// If [`self.extension`] is [`None`], the extension is added; otherwise
304    /// it is replaced.
305    ///
306    /// [`self.file_name`]: Utf8TypedPath::file_name
307    /// [`self.extension`]: Utf8TypedPath::extension
308    ///
309    /// # Examples
310    ///
311    /// ```
312    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
313    ///
314    /// let mut p = Utf8TypedPathBuf::from_unix("/feel/the");
315    ///
316    /// p.set_extension("force");
317    /// assert_eq!(Utf8TypedPath::derive("/feel/the.force"), p.to_path());
318    ///
319    /// p.set_extension("dark_side");
320    /// assert_eq!(Utf8TypedPath::derive("/feel/the.dark_side"), p.to_path());
321    /// ```
322    pub fn set_extension<S: AsRef<str>>(&mut self, extension: S) -> bool {
323        impl_typed_fn!(self, set_extension, extension)
324    }
325
326    /// Consumes the [`Utf8TypedPathBuf`], yielding its internal [`String`] storage.
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use typed_path::Utf8TypedPathBuf;
332    ///
333    /// let p = Utf8TypedPathBuf::from_unix("/the/head");
334    /// let string = p.into_string();
335    /// assert_eq!(string, "/the/head");
336    /// ```
337    #[inline]
338    pub fn into_string(self) -> String {
339        impl_typed_fn!(self, into_string)
340    }
341
342    /// Invokes [`capacity`] on the underlying instance of [`Vec`].
343    ///
344    /// [`capacity`]: Vec::capacity
345    #[inline]
346    pub fn capacity(&self) -> usize {
347        impl_typed_fn!(self, capacity)
348    }
349
350    /// Invokes [`clear`] on the underlying instance of [`Vec`].
351    ///
352    /// [`clear`]: Vec::clear
353    #[inline]
354    pub fn clear(&mut self) {
355        impl_typed_fn!(self, clear)
356    }
357
358    /// Invokes [`reserve`] on the underlying instance of [`Vec`].
359    ///
360    /// [`reserve`]: Vec::reserve
361    #[inline]
362    pub fn reserve(&mut self, additional: usize) {
363        impl_typed_fn!(self, reserve, additional)
364    }
365
366    /// Invokes [`try_reserve`] on the underlying instance of [`Vec`].
367    ///
368    /// [`try_reserve`]: Vec::try_reserve
369    #[inline]
370    pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
371        impl_typed_fn!(self, try_reserve, additional)
372    }
373
374    /// Invokes [`reserve_exact`] on the underlying instance of [`Vec`].
375    ///
376    /// [`reserve_exact`]: Vec::reserve_exact
377    #[inline]
378    pub fn reserve_exact(&mut self, additional: usize) {
379        impl_typed_fn!(self, reserve_exact, additional)
380    }
381
382    /// Invokes [`try_reserve_exact`] on the underlying instance of [`Vec`].
383    ///
384    /// [`try_reserve_exact`]: Vec::try_reserve_exact
385    #[inline]
386    pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
387        impl_typed_fn!(self, try_reserve_exact, additional)
388    }
389
390    /// Invokes [`shrink_to_fit`] on the underlying instance of [`Vec`].
391    ///
392    /// [`shrink_to_fit`]: Vec::shrink_to_fit
393    #[inline]
394    pub fn shrink_to_fit(&mut self) {
395        impl_typed_fn!(self, shrink_to_fit)
396    }
397
398    /// Invokes [`shrink_to`] on the underlying instance of [`Vec`].
399    ///
400    /// [`shrink_to`]: Vec::shrink_to
401    #[inline]
402    pub fn shrink_to(&mut self, min_capacity: usize) {
403        impl_typed_fn!(self, shrink_to, min_capacity)
404    }
405}
406
407/// Reimplementation of [`Utf8TypedPath`] methods as we cannot implement [`std::ops::Deref`]
408/// directly.
409impl Utf8TypedPathBuf {
410    /// Yields the underlying [`str`] slice.
411    ///
412    /// # Examples
413    ///
414    /// ```
415    /// use typed_path::Utf8TypedPathBuf;
416    ///
417    /// let string = Utf8TypedPathBuf::from("foo.txt").as_str().to_string();
418    /// assert_eq!(string, "foo.txt");
419    /// ```
420    pub fn as_str(&self) -> &str {
421        impl_typed_fn!(self, as_str)
422    }
423
424    /// Returns `true` if the [`Utf8TypedPathBuf`] is absolute, i.e., if it is independent of
425    /// the current directory.
426    ///
427    /// * On Unix ([`Utf8UnixPathBuf`]]), a path is absolute if it starts with the root, so
428    ///   `is_absolute` and [`has_root`] are equivalent.
429    ///
430    /// * On Windows ([`Utf8WindowsPathBuf`]), a path is absolute if it has a prefix and starts with
431    ///   the root: `c:\windows` is absolute, while `c:temp` and `\temp` are not.
432    ///
433    /// [`Utf8UnixPathBuf`]: crate::Utf8UnixPathBuf
434    /// [`Utf8WindowsPathBuf`]: crate::Utf8WindowsPathBuf
435    ///
436    /// # Examples
437    ///
438    /// ```
439    /// use typed_path::Utf8TypedPathBuf;
440    ///
441    /// assert!(!Utf8TypedPathBuf::from("foo.txt").is_absolute());
442    /// ```
443    ///
444    /// [`has_root`]: Utf8TypedPathBuf::has_root
445    pub fn is_absolute(&self) -> bool {
446        impl_typed_fn!(self, is_absolute)
447    }
448
449    /// Returns `true` if the [`Utf8TypedPathBuf`] is relative, i.e., not absolute.
450    ///
451    /// See [`is_absolute`]'s documentation for more details.
452    ///
453    /// # Examples
454    ///
455    /// ```
456    /// use typed_path::Utf8TypedPathBuf;
457    ///
458    /// assert!(Utf8TypedPathBuf::from("foo.txt").is_relative());
459    /// ```
460    ///
461    /// [`is_absolute`]: Utf8TypedPathBuf::is_absolute
462    #[inline]
463    pub fn is_relative(&self) -> bool {
464        impl_typed_fn!(self, is_relative)
465    }
466
467    /// Returns `true` if the [`Utf8TypedPathBuf`] has a root.
468    ///
469    /// * On Unix ([`Utf8UnixPathBuf`]), a path has a root if it begins with `/`.
470    ///
471    /// * On Windows ([`Utf8WindowsPathBuf`]), a path has a root if it:
472    ///     * has no prefix and begins with a separator, e.g., `\windows`
473    ///     * has a prefix followed by a separator, e.g., `c:\windows` but not `c:windows`
474    ///     * has any non-disk prefix, e.g., `\\server\share`
475    ///
476    /// [`Utf8UnixPathBuf`]: crate::Utf8UnixPathBuf
477    /// [`Utf8WindowsPathBuf`]: crate::Utf8WindowsPathBuf
478    ///
479    /// # Examples
480    ///
481    /// ```
482    /// use typed_path::Utf8TypedPathBuf;
483    ///
484    /// assert!(Utf8TypedPathBuf::from("/etc/passwd").has_root());
485    /// ```
486    #[inline]
487    pub fn has_root(&self) -> bool {
488        impl_typed_fn!(self, has_root)
489    }
490
491    /// Returns a reference to the path without its final component, if there is one.
492    ///
493    /// Returns [`None`] if the path terminates in a root or prefix.
494    ///
495    /// # Examples
496    ///
497    /// ```
498    /// use typed_path::Utf8TypedPathBuf;
499    ///
500    /// let path = Utf8TypedPathBuf::from("/foo/bar");
501    /// let parent = path.parent().unwrap();
502    /// assert_eq!(parent, Utf8TypedPathBuf::from("/foo"));
503    ///
504    /// let grand_parent = parent.parent().unwrap();
505    /// assert_eq!(grand_parent, Utf8TypedPathBuf::from("/"));
506    /// assert_eq!(grand_parent.parent(), None);
507    /// ```
508    pub fn parent(&self) -> Option<Utf8TypedPath<'_>> {
509        self.to_path().parent()
510    }
511
512    /// Produces an iterator over [`Utf8TypedPathBuf`] and its ancestors.
513    ///
514    /// The iterator will yield the [`Utf8TypedPathBuf`] that is returned if the [`parent`] method
515    /// is used zero or more times. That means, the iterator will yield `&self`,
516    /// `&self.parent().unwrap()`, `&self.parent().unwrap().parent().unwrap()` and so on. If the
517    /// [`parent`] method returns [`None`], the iterator will do likewise. The iterator will always
518    /// yield at least one value, namely `&self`.
519    ///
520    /// # Examples
521    ///
522    /// ```
523    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
524    ///
525    /// let path = Utf8TypedPathBuf::from("/foo/bar");
526    /// let mut ancestors = path.ancestors();
527    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/foo/bar")));
528    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/foo")));
529    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("/")));
530    /// assert_eq!(ancestors.next(), None);
531    ///
532    /// let path = Utf8TypedPathBuf::from("../foo/bar");
533    /// let mut ancestors = path.ancestors();
534    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("../foo/bar")));
535    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("../foo")));
536    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("..")));
537    /// assert_eq!(ancestors.next(), Some(Utf8TypedPath::derive("")));
538    /// assert_eq!(ancestors.next(), None);
539    /// ```
540    ///
541    /// [`parent`]: Utf8TypedPathBuf::parent
542    #[inline]
543    pub fn ancestors(&self) -> Utf8TypedAncestors<'_> {
544        self.to_path().ancestors()
545    }
546
547    /// Returns the final component of the [`Utf8TypedPathBuf`], if there is one.
548    ///
549    /// If the path is a normal file, this is the file name. If it's the path of a directory, this
550    /// is the directory name.
551    ///
552    /// Returns [`None`] if the path terminates in `..`.
553    ///
554    /// # Examples
555    ///
556    /// ```
557    /// use typed_path::Utf8TypedPathBuf;
558    ///
559    /// assert_eq!(Some("bin"), Utf8TypedPathBuf::from("/usr/bin/").file_name());
560    /// assert_eq!(Some("foo.txt"), Utf8TypedPathBuf::from("tmp/foo.txt").file_name());
561    /// assert_eq!(Some("foo.txt"), Utf8TypedPathBuf::from("foo.txt/.").file_name());
562    /// assert_eq!(Some("foo.txt"), Utf8TypedPathBuf::from("foo.txt/.//").file_name());
563    /// assert_eq!(None, Utf8TypedPathBuf::from("foo.txt/..").file_name());
564    /// assert_eq!(None, Utf8TypedPathBuf::from("/").file_name());
565    /// ```
566    pub fn file_name(&self) -> Option<&str> {
567        impl_typed_fn!(self, file_name)
568    }
569
570    /// Returns a path that, when joined onto `base`, yields `self`.
571    ///
572    /// # Errors
573    ///
574    /// If `base` is not a prefix of `self` (i.e., [`starts_with`]
575    /// returns `false`), returns [`Err`].
576    ///
577    /// [`starts_with`]: Utf8TypedPathBuf::starts_with
578    ///
579    /// # Examples
580    ///
581    /// ```
582    /// use typed_path::{Utf8TypedPath, Utf8TypedPathBuf};
583    ///
584    /// let path = Utf8TypedPathBuf::from("/test/haha/foo.txt");
585    ///
586    /// assert_eq!(path.strip_prefix("/"), Ok(Utf8TypedPath::derive("test/haha/foo.txt")));
587    /// assert_eq!(path.strip_prefix("/test"), Ok(Utf8TypedPath::derive("haha/foo.txt")));
588    /// assert_eq!(path.strip_prefix("/test/"), Ok(Utf8TypedPath::derive("haha/foo.txt")));
589    /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Utf8TypedPath::derive("")));
590    /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Utf8TypedPath::derive("")));
591    ///
592    /// assert!(path.strip_prefix("test").is_err());
593    /// assert!(path.strip_prefix("/haha").is_err());
594    ///
595    /// let prefix = Utf8TypedPathBuf::from("/test/");
596    /// assert_eq!(path.strip_prefix(prefix), Ok(Utf8TypedPath::derive("haha/foo.txt")));
597    /// ```
598    pub fn strip_prefix(
599        &self,
600        base: impl AsRef<str>,
601    ) -> Result<Utf8TypedPath<'_>, StripPrefixError> {
602        match self {
603            Self::Unix(p) => p
604                .strip_prefix(Utf8UnixPath::new(&base))
605                .map(Utf8TypedPath::Unix),
606            Self::Windows(p) => p
607                .strip_prefix(Utf8WindowsPath::new(&base))
608                .map(Utf8TypedPath::Windows),
609        }
610    }
611
612    /// Determines whether `base` is a prefix of `self`.
613    ///
614    /// Only considers whole path components to match.
615    ///
616    /// # Difference from Path
617    ///
618    /// Unlike [`Utf8Path::starts_with`], this implementation only supports types that implement
619    /// `AsRef<str>` instead of `AsRef<Path>`.
620    ///
621    /// [`Utf8Path::starts_with`]: crate::Utf8Path::starts_with
622    ///
623    /// # Examples
624    ///
625    /// ```
626    /// use typed_path::Utf8TypedPathBuf;
627    ///
628    /// let path = Utf8TypedPathBuf::from("/etc/passwd");
629    ///
630    /// assert!(path.starts_with("/etc"));
631    /// assert!(path.starts_with("/etc/"));
632    /// assert!(path.starts_with("/etc/passwd"));
633    /// assert!(path.starts_with("/etc/passwd/")); // extra slash is okay
634    /// assert!(path.starts_with("/etc/passwd///")); // multiple extra slashes are okay
635    ///
636    /// assert!(!path.starts_with("/e"));
637    /// assert!(!path.starts_with("/etc/passwd.txt"));
638    ///
639    /// assert!(!Utf8TypedPathBuf::from("/etc/foo.rs").starts_with("/etc/foo"));
640    /// ```
641    pub fn starts_with(&self, base: impl AsRef<str>) -> bool {
642        self.to_path().starts_with(base)
643    }
644
645    /// Determines whether `child` is a suffix of `self`.
646    ///
647    /// Only considers whole path components to match.
648    ///
649    /// # Difference from Path
650    ///
651    /// Unlike [`Utf8Path::ends_with`], this implementation only supports types that implement
652    /// `AsRef<str>` instead of `AsRef<Path>`.
653    ///
654    /// [`Utf8Path::ends_with`]: crate::Utf8Path::ends_with
655    ///
656    /// # Examples
657    ///
658    /// ```
659    /// use typed_path::Utf8TypedPathBuf;
660    ///
661    /// let path = Utf8TypedPathBuf::from("/etc/resolv.conf");
662    ///
663    /// assert!(path.ends_with("resolv.conf"));
664    /// assert!(path.ends_with("etc/resolv.conf"));
665    /// assert!(path.ends_with("/etc/resolv.conf"));
666    ///
667    /// assert!(!path.ends_with("/resolv.conf"));
668    /// assert!(!path.ends_with("conf")); // use .extension() instead
669    /// ```
670    pub fn ends_with(&self, child: impl AsRef<str>) -> bool {
671        self.to_path().ends_with(child)
672    }
673
674    /// Extracts the stem (non-extension) portion of [`self.file_name`].
675    ///
676    /// [`self.file_name`]: Utf8TypedPathBuf::file_name
677    ///
678    /// The stem is:
679    ///
680    /// * [`None`], if there is no file name;
681    /// * The entire file name if there is no embedded `.`;
682    /// * The entire file name if the file name begins with `.` and has no other `.`s within;
683    /// * Otherwise, the portion of the file name before the final `.`
684    ///
685    /// # Examples
686    ///
687    /// ```
688    /// use typed_path::Utf8TypedPathBuf;
689    ///
690    /// assert_eq!("foo", Utf8TypedPathBuf::from("foo.rs").file_stem().unwrap());
691    /// assert_eq!("foo.tar", Utf8TypedPathBuf::from("foo.tar.gz").file_stem().unwrap());
692    /// ```
693    ///
694    pub fn file_stem(&self) -> Option<&str> {
695        impl_typed_fn!(self, file_stem)
696    }
697
698    /// Extracts the extension of [`self.file_name`], if possible.
699    ///
700    /// The extension is:
701    ///
702    /// * [`None`], if there is no file name;
703    /// * [`None`], if there is no embedded `.`;
704    /// * [`None`], if the file name begins with `.` and has no other `.`s within;
705    /// * Otherwise, the portion of the file name after the final `.`
706    ///
707    /// [`self.file_name`]: Utf8TypedPathBuf::file_name
708    ///
709    /// # Examples
710    ///
711    /// ```
712    /// use typed_path::Utf8TypedPathBuf;
713    ///
714    /// assert_eq!("rs", Utf8TypedPathBuf::from("foo.rs").extension().unwrap());
715    /// assert_eq!("gz", Utf8TypedPathBuf::from("foo.tar.gz").extension().unwrap());
716    /// ```
717    pub fn extension(&self) -> Option<&str> {
718        impl_typed_fn!(self, extension)
719    }
720
721    /// Returns an owned [`Utf8TypedPathBuf`] by resolving `..` and `.` segments.
722    ///
723    /// When multiple, sequential path segment separation characters are found (e.g. `/` for Unix
724    /// and either `\` or `/` on Windows), they are replaced by a single instance of the
725    /// platform-specific path segment separator (`/` on Unix and `\` on Windows).
726    ///
727    /// # Examples
728    ///
729    /// ```
730    /// use typed_path::Utf8TypedPathBuf;
731    ///
732    /// assert_eq!(
733    ///     Utf8TypedPathBuf::from("foo/bar//baz/./asdf/quux/..").normalize(),
734    ///     Utf8TypedPathBuf::from("foo/bar/baz/asdf"),
735    /// );
736    /// ```
737    ///
738    /// When starting with a root directory, any `..` segment whose parent is the root directory
739    /// will be filtered out:
740    ///
741    /// ```
742    /// use typed_path::Utf8TypedPathBuf;
743    ///
744    /// assert_eq!(
745    ///     Utf8TypedPathBuf::from("/../foo").normalize(),
746    ///     Utf8TypedPathBuf::from("/foo"),
747    /// );
748    /// ```
749    ///
750    /// If any `..` is left unresolved as the path is relative and no parent is found, it is
751    /// discarded:
752    ///
753    /// ```
754    /// use typed_path::Utf8TypedPathBuf;
755    ///
756    /// assert_eq!(
757    ///     Utf8TypedPathBuf::from("../foo/..").normalize(),
758    ///     Utf8TypedPathBuf::from(""),
759    /// );
760    ///
761    /// // Windows prefixes also count this way, but the prefix remains
762    /// assert_eq!(
763    ///     Utf8TypedPathBuf::from(r"C:..\foo\..").normalize(),
764    ///     Utf8TypedPathBuf::from(r"C:"),
765    /// );
766    /// ```
767    pub fn normalize(&self) -> Utf8TypedPathBuf {
768        self.to_path().normalize()
769    }
770
771    /// Converts a path to an absolute form by [`normalizing`] the path, returning a
772    /// [`Utf8TypedPathBuf`].
773    ///
774    /// In the case that the path is relative, the current working directory is prepended prior to
775    /// normalizing.
776    ///
777    /// [`normalizing`]: Utf8TypedPathBuf::normalize
778    ///
779    /// # Examples
780    ///
781    /// ```
782    /// use typed_path::{utils, Utf8TypedPathBuf, Utf8UnixEncoding};
783    ///
784    /// // With an absolute path, it is just normalized
785    /// let path = Utf8TypedPathBuf::from("/a/b/../c/./d");
786    /// assert_eq!(path.absolutize().unwrap(), Utf8TypedPathBuf::from("/a/c/d"));
787    ///
788    /// // With a relative path, it is first joined with the current working directory
789    /// // and then normalized
790    /// let cwd = utils::utf8_current_dir().unwrap()
791    ///     .with_encoding::<Utf8UnixEncoding>().to_typed_path_buf();
792    ///
793    /// let path = cwd.join("a/b/../c/./d");
794    /// assert_eq!(path.absolutize().unwrap(), cwd.join("a/c/d"));
795    /// ```
796    #[cfg(all(feature = "std", not(target_family = "wasm")))]
797    pub fn absolutize(&self) -> std::io::Result<Utf8TypedPathBuf> {
798        self.to_path().absolutize()
799    }
800
801    /// Creates an owned [`Utf8TypedPathBuf`] with `path` adjoined to `self`.
802    ///
803    /// See [`Utf8TypedPathBuf::push`] for more details on what it means to adjoin a path.
804    ///
805    /// # Difference from Path
806    ///
807    /// Unlike [`Utf8Path::join`], this implementation only supports types that implement
808    /// `AsRef<str>` instead of `AsRef<Path>`.
809    ///
810    /// [`Utf8Path::join`]: crate::Utf8Path::join
811    ///
812    /// # Examples
813    ///
814    /// ```
815    /// use typed_path::Utf8TypedPathBuf;
816    ///
817    /// assert_eq!(
818    ///     Utf8TypedPathBuf::from("/etc").join("passwd"),
819    ///     Utf8TypedPathBuf::from("/etc/passwd"),
820    /// );
821    /// ```
822    pub fn join(&self, path: impl AsRef<str>) -> Utf8TypedPathBuf {
823        self.to_path().join(path)
824    }
825
826    /// Creates an owned [`Utf8TypedPathBuf`] with `path` adjoined to `self`, checking the `path`
827    /// to ensure it is safe to join. _When dealing with user-provided paths, this is the preferred
828    /// method._
829    ///
830    /// See [`Utf8TypedPathBuf::push_checked`] for more details on what it means to adjoin a path
831    /// safely.
832    ///
833    /// # Difference from Path
834    ///
835    /// Unlike [`Utf8Path::join_checked`], this implementation only supports types that implement
836    /// `AsRef<str>` instead of `AsRef<Path>`.
837    ///
838    /// [`Utf8Path::join_checked`]: crate::Utf8Path::join_checked
839    ///
840    /// # Examples
841    ///
842    /// ```
843    /// use typed_path::{CheckedPathError, Utf8TypedPathBuf};
844    ///
845    /// // Valid path will join successfully
846    /// assert_eq!(
847    ///     Utf8TypedPathBuf::from("/etc").join_checked("passwd"),
848    ///     Ok(Utf8TypedPathBuf::from("/etc/passwd")),
849    /// );
850    ///
851    /// // Invalid path will fail to join
852    /// assert_eq!(
853    ///     Utf8TypedPathBuf::from("/etc").join_checked("/sneaky/path"),
854    ///     Err(CheckedPathError::UnexpectedRoot),
855    /// );
856    /// ```
857    pub fn join_checked(
858        &self,
859        path: impl AsRef<str>,
860    ) -> Result<Utf8TypedPathBuf, CheckedPathError> {
861        self.to_path().join_checked(path)
862    }
863
864    /// Creates an owned [`Utf8TypedPathBuf`] like `self` but with the given file name.
865    ///
866    /// See [`Utf8TypedPathBuf::set_file_name`] for more details.
867    ///
868    /// # Examples
869    ///
870    /// ```
871    /// use typed_path::Utf8TypedPathBuf;
872    ///
873    /// let path = Utf8TypedPathBuf::from("/tmp/foo.txt");
874    /// assert_eq!(path.with_file_name("bar.txt"), Utf8TypedPathBuf::from("/tmp/bar.txt"));
875    ///
876    /// let path = Utf8TypedPathBuf::from("/tmp");
877    /// assert_eq!(path.with_file_name("var"), Utf8TypedPathBuf::from("/var"));
878    /// ```
879    pub fn with_file_name<S: AsRef<str>>(&self, file_name: S) -> Utf8TypedPathBuf {
880        self.to_path().with_file_name(file_name)
881    }
882
883    /// Creates an owned [`Utf8TypedPathBuf`] like `self` but with the given extension.
884    ///
885    /// See [`Utf8TypedPathBuf::set_extension`] for more details.
886    ///
887    /// # Examples
888    ///
889    /// ```
890    /// use typed_path::Utf8TypedPathBuf;
891    ///
892    /// let path = Utf8TypedPathBuf::from("foo.rs");
893    /// assert_eq!(path.with_extension("txt"), Utf8TypedPathBuf::from("foo.txt"));
894    ///
895    /// let path = Utf8TypedPathBuf::from("foo.tar.gz");
896    /// assert_eq!(path.with_extension(""), Utf8TypedPathBuf::from("foo.tar"));
897    /// assert_eq!(path.with_extension("xz"), Utf8TypedPathBuf::from("foo.tar.xz"));
898    /// assert_eq!(path.with_extension("").with_extension("txt"), Utf8TypedPathBuf::from("foo.txt"));
899    /// ```
900    pub fn with_extension<S: AsRef<str>>(&self, extension: S) -> Utf8TypedPathBuf {
901        self.to_path().with_extension(extension)
902    }
903
904    /// Produces an iterator over the [`Utf8TypedComponent`]s of the path.
905    ///
906    /// When parsing the path, there is a small amount of normalization:
907    ///
908    /// * Repeated separators are ignored, so `a/b` and `a//b` both have
909    ///   `a` and `b` as components.
910    ///
911    /// * Occurrences of `.` are normalized away, except if they are at the
912    ///   beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and
913    ///   `a/b` all have `a` and `b` as components, but `./a/b` starts with
914    ///   an additional CurDir component.
915    ///
916    /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
917    ///
918    /// Note that no other normalization takes place; in particular, `a/c`
919    /// and `a/b/../c` are distinct, to account for the possibility that `b`
920    /// is a symbolic link (so its parent isn't `a`).
921    ///
922    /// # Examples
923    ///
924    /// ```
925    /// use typed_path::{Utf8TypedPathBuf, Utf8TypedComponent};
926    ///
927    /// let path = Utf8TypedPathBuf::from("/tmp/foo.txt");
928    /// let mut components = path.components();
929    ///
930    /// assert!(components.next().unwrap().is_root());
931    /// assert_eq!(components.next().unwrap().as_normal_str(), Some("tmp"));
932    /// assert_eq!(components.next().unwrap().as_normal_str(), Some("foo.txt"));
933    /// assert_eq!(components.next(), None)
934    /// ```
935    ///
936    /// [`Utf8TypedComponent`]: crate::Utf8TypedComponent
937    pub fn components(&self) -> Utf8TypedComponents<'_> {
938        self.to_path().components()
939    }
940
941    /// Produces an iterator over the path's components viewed as [`str`] slices.
942    ///
943    /// For more information about the particulars of how the path is separated
944    /// into components, see [`components`].
945    ///
946    /// [`components`]: Utf8TypedPath::components
947    ///
948    /// # Examples
949    ///
950    /// ```
951    /// use typed_path::Utf8TypedPathBuf;
952    ///
953    /// let path = Utf8TypedPathBuf::from("/tmp/foo.txt");
954    /// let mut it = path.iter();
955    ///
956    /// assert_eq!(it.next(), Some(typed_path::constants::unix::SEPARATOR_STR));
957    /// assert_eq!(it.next(), Some("tmp"));
958    /// assert_eq!(it.next(), Some("foo.txt"));
959    /// assert_eq!(it.next(), None)
960    /// ```
961    #[inline]
962    pub fn iter(&self) -> Utf8TypedIter<'_> {
963        self.to_path().iter()
964    }
965}
966
967impl fmt::Display for Utf8TypedPathBuf {
968    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
969        match self {
970            Self::Unix(path) => fmt::Display::fmt(path, f),
971            Self::Windows(path) => fmt::Display::fmt(path, f),
972        }
973    }
974}
975
976impl AsRef<[u8]> for Utf8TypedPathBuf {
977    #[inline]
978    fn as_ref(&self) -> &[u8] {
979        self.as_str().as_bytes()
980    }
981}
982
983impl AsRef<str> for Utf8TypedPathBuf {
984    #[inline]
985    fn as_ref(&self) -> &str {
986        self.as_str()
987    }
988}
989
990impl<'a> From<&'a str> for Utf8TypedPathBuf {
991    /// Creates a new typed pathbuf from a byte slice by determining if the path represents a
992    /// Windows or Unix path. This is accomplished by first trying to parse as a Windows path. If
993    /// the resulting path contains a prefix such as `C:` or begins with a `\`, it is assumed to be
994    /// a [`Utf8WindowsPathBuf`]; otherwise, the slice will be represented as a [`Utf8UnixPathBuf`].
995    ///
996    /// # Examples
997    ///
998    /// ```
999    /// use typed_path::Utf8TypedPathBuf;
1000    ///
1001    /// assert!(Utf8TypedPathBuf::from(r#"C:\some\path\to\file.txt"#).is_windows());
1002    /// assert!(Utf8TypedPathBuf::from(r#"\some\path\to\file.txt"#).is_windows());
1003    /// assert!(Utf8TypedPathBuf::from(r#"/some/path/to/file.txt"#).is_unix());
1004    ///
1005    /// // NOTE: If we don't start with a backslash, it's too difficult to
1006    /// //       determine and we therefore just assume a Unix/POSIX path.
1007    /// assert!(Utf8TypedPathBuf::from(r#"some\path\to\file.txt"#).is_unix());
1008    /// assert!(Utf8TypedPathBuf::from("file.txt").is_unix());
1009    /// assert!(Utf8TypedPathBuf::from("").is_unix());
1010    /// ```
1011    #[inline]
1012    fn from(s: &'a str) -> Self {
1013        Utf8TypedPath::derive(s).to_path_buf()
1014    }
1015}
1016
1017impl From<String> for Utf8TypedPathBuf {
1018    #[inline]
1019    fn from(s: String) -> Self {
1020        // NOTE: We use the typed path to check the underlying format, and then
1021        //       create it manually to avoid a clone of the vec itself
1022        match Utf8TypedPath::derive(s.as_str()) {
1023            Utf8TypedPath::Unix(_) => Utf8TypedPathBuf::Unix(Utf8UnixPathBuf::from(s)),
1024            Utf8TypedPath::Windows(_) => Utf8TypedPathBuf::Windows(Utf8WindowsPathBuf::from(s)),
1025        }
1026    }
1027}
1028
1029impl TryFrom<Utf8TypedPathBuf> for Utf8UnixPathBuf {
1030    type Error = Utf8TypedPathBuf;
1031
1032    fn try_from(path: Utf8TypedPathBuf) -> Result<Self, Self::Error> {
1033        match path {
1034            Utf8TypedPathBuf::Unix(path) => Ok(path),
1035            path => Err(path),
1036        }
1037    }
1038}
1039
1040impl TryFrom<Utf8TypedPathBuf> for Utf8WindowsPathBuf {
1041    type Error = Utf8TypedPathBuf;
1042
1043    fn try_from(path: Utf8TypedPathBuf) -> Result<Self, Self::Error> {
1044        match path {
1045            Utf8TypedPathBuf::Windows(path) => Ok(path),
1046            path => Err(path),
1047        }
1048    }
1049}
1050
1051impl PartialEq<Utf8TypedPath<'_>> for Utf8TypedPathBuf {
1052    fn eq(&self, path: &Utf8TypedPath<'_>) -> bool {
1053        path.eq(&self.to_path())
1054    }
1055}
1056
1057impl PartialEq<str> for Utf8TypedPathBuf {
1058    fn eq(&self, path: &str) -> bool {
1059        self.as_str() == path
1060    }
1061}
1062
1063impl PartialEq<Utf8TypedPathBuf> for str {
1064    fn eq(&self, path: &Utf8TypedPathBuf) -> bool {
1065        self == path.as_str()
1066    }
1067}
1068
1069impl<'a> PartialEq<&'a str> for Utf8TypedPathBuf {
1070    fn eq(&self, path: &&'a str) -> bool {
1071        self.as_str() == *path
1072    }
1073}
1074
1075impl PartialEq<Utf8TypedPathBuf> for &str {
1076    fn eq(&self, path: &Utf8TypedPathBuf) -> bool {
1077        *self == path.as_str()
1078    }
1079}
1080
1081#[cfg(feature = "serde")]
1082impl Serialize for Utf8TypedPathBuf {
1083    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1084    where
1085        S: Serializer,
1086    {
1087        match self {
1088            Self::Unix(unix_path) => unix_path.serialize(serializer),
1089            Self::Windows(windows_path) => windows_path.serialize(serializer),
1090        }
1091    }
1092}
1093
1094#[cfg(feature = "serde")]
1095struct Utf8TypedPathBufVisitor;
1096
1097#[cfg(feature = "serde")]
1098impl<'de> Visitor<'de> for Utf8TypedPathBufVisitor {
1099    type Value = Utf8TypedPathBuf;
1100
1101    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1102        formatter.write_str("UTF-8 typed path string")
1103    }
1104
1105    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1106    where
1107        E: de::Error,
1108    {
1109        Ok(Utf8TypedPathBuf::from(v))
1110    }
1111
1112    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
1113    where
1114        E: de::Error,
1115    {
1116        Ok(Utf8TypedPathBuf::from(v))
1117    }
1118
1119    fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
1120    where
1121        E: de::Error,
1122    {
1123        core::str::from_utf8(v)
1124            .map(Utf8TypedPathBuf::from)
1125            .map_err(|_| de::Error::invalid_value(de::Unexpected::Bytes(v), &self))
1126    }
1127
1128    fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
1129    where
1130        E: de::Error,
1131    {
1132        String::from_utf8(v)
1133            .map(Utf8TypedPathBuf::from)
1134            .map_err(|e| de::Error::invalid_value(de::Unexpected::Bytes(&e.into_bytes()), &self))
1135    }
1136}
1137
1138#[cfg(feature = "serde")]
1139impl<'de> Deserialize<'de> for Utf8TypedPathBuf {
1140    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1141    where
1142        D: Deserializer<'de>,
1143    {
1144        deserializer.deserialize_string(Utf8TypedPathBufVisitor)
1145    }
1146}
1147
1148#[cfg(all(test, feature = "serde"))]
1149mod tests {
1150    use crate::{Utf8TypedPath, Utf8TypedPathBuf};
1151
1152    #[test]
1153    fn serialize_unix() {
1154        let path = Utf8TypedPathBuf::from_unix("/tmp");
1155
1156        assert_eq!(serde_json::to_string(&path).unwrap(), r#""/tmp""#);
1157    }
1158
1159    #[test]
1160    fn serialize_windows() {
1161        let path = Utf8TypedPathBuf::from_windows(r"C:\tmp");
1162
1163        assert_eq!(serde_json::to_string(&path).unwrap(), r#""C:\\tmp""#);
1164    }
1165
1166    #[test]
1167    fn deserialize_unix_str() {
1168        let path = serde_json::from_str::<Utf8TypedPathBuf>(r#""/tmp""#).unwrap();
1169
1170        assert!(path.is_unix());
1171        assert_eq!(path, Utf8TypedPath::unix("/tmp"));
1172    }
1173
1174    #[test]
1175    fn deserialize_unix_bytes() {
1176        let path = serde_json::from_slice::<Utf8TypedPathBuf>(br#""/tmp""#).unwrap();
1177
1178        assert!(path.is_unix());
1179        assert_eq!(path, Utf8TypedPath::unix("/tmp"));
1180    }
1181
1182    #[test]
1183    fn deserialize_prefixed_windows_path() {
1184        let path = serde_json::from_str::<Utf8TypedPathBuf>(r#""C:\\tmp""#).unwrap();
1185
1186        assert!(path.is_windows());
1187        assert_eq!(path, Utf8TypedPath::windows(r"C:\tmp"));
1188    }
1189
1190    #[test]
1191    fn deserialize_root_windows_path() {
1192        let path =
1193            serde_json::from_str::<Utf8TypedPathBuf>(r#""\\some\\path\\to\\file.txt""#).unwrap();
1194
1195        assert!(path.is_windows());
1196        assert_eq!(path, Utf8TypedPath::windows(r"\some\path\to\file.txt"));
1197    }
1198}