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}