typed_path/common/utf8/pathbuf.rs
1use alloc::borrow::Cow;
2use alloc::collections::TryReserveError;
3use alloc::string::FromUtf8Error;
4use core::borrow::Borrow;
5use core::hash::{Hash, Hasher};
6use core::iter::{Extend, FromIterator};
7use core::marker::PhantomData;
8use core::ops::Deref;
9use core::str::FromStr;
10use core::{cmp, fmt};
11
12#[cfg(feature = "serde")]
13use serde_core::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
14
15use crate::no_std_compat::*;
16use crate::{CheckedPathError, Encoding, PathBuf, Utf8Encoding, Utf8Iter, Utf8Path};
17
18/// An owned, mutable path that mirrors [`std::path::PathBuf`], but operatings using a
19/// [`Utf8Encoding`] to determine how to parse the underlying str.
20///
21/// This type provides methods like [`push`] and [`set_extension`] that mutate
22/// the path in place. It also implements [`Deref`] to [`Utf8Path`], meaning that
23/// all methods on [`Utf8Path`] slices are available on `Utf8PathBuf` values as well.
24///
25/// [`push`]: Utf8PathBuf::push
26/// [`set_extension`]: Utf8PathBuf::set_extension
27///
28/// # Examples
29///
30/// You can use [`push`] to build up a `Utf8PathBuf` from
31/// components:
32///
33/// ```
34/// use typed_path::{Utf8PathBuf, Utf8WindowsEncoding};
35///
36/// // NOTE: A pathbuf cannot be created on its own without a defined encoding
37/// let mut path = Utf8PathBuf::<Utf8WindowsEncoding>::new();
38///
39/// path.push(r"C:\");
40/// path.push("windows");
41/// path.push("system32");
42///
43/// path.set_extension("dll");
44/// ```
45///
46/// However, [`push`] is best used for dynamic situations. This is a better way
47/// to do this when you know all of the components ahead of time:
48///
49/// ```
50/// use typed_path::{Utf8PathBuf, Utf8WindowsEncoding};
51///
52/// let path: Utf8PathBuf<Utf8WindowsEncoding> = [
53/// r"C:\",
54/// "windows",
55/// "system32.dll",
56/// ].iter().collect();
57/// ```
58///
59/// We can still do better than this! Since these are all strings, we can use
60/// `From::from`:
61///
62/// ```
63/// use typed_path::{Utf8PathBuf, Utf8WindowsEncoding};
64///
65/// let path = Utf8PathBuf::<Utf8WindowsEncoding>::from(r"C:\windows\system32.dll");
66/// ```
67///
68/// Which method works best depends on what kind of situation you're in.
69pub struct Utf8PathBuf<T>
70where
71 T: Utf8Encoding,
72{
73 /// Encoding associated with path buf
74 pub(crate) _encoding: PhantomData<T>,
75
76 /// Path as an unparsed string
77 pub(crate) inner: String,
78}
79
80impl<T> Utf8PathBuf<T>
81where
82 T: Utf8Encoding,
83{
84 /// Allocates an empty `Utf8PathBuf`.
85 ///
86 /// # Examples
87 ///
88 /// ```
89 /// use typed_path::{Utf8PathBuf, Utf8UnixEncoding};
90 ///
91 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
92 /// let path = Utf8PathBuf::<Utf8UnixEncoding>::new();
93 /// ```
94 pub fn new() -> Self {
95 Utf8PathBuf {
96 _encoding: PhantomData,
97 inner: String::new(),
98 }
99 }
100
101 /// Creates a new `PathBuf` with a given capacity used to create the
102 /// internal [`String`]. See [`with_capacity`] defined on [`String`].
103 ///
104 /// # Examples
105 ///
106 /// ```
107 /// use typed_path::{Utf8PathBuf, Utf8UnixEncoding};
108 ///
109 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
110 /// let mut path = Utf8PathBuf::<Utf8UnixEncoding>::with_capacity(10);
111 /// let capacity = path.capacity();
112 ///
113 /// // This push is done without reallocating
114 /// path.push(r"C:\");
115 ///
116 /// assert_eq!(capacity, path.capacity());
117 /// ```
118 ///
119 /// [`with_capacity`]: String::with_capacity
120 #[inline]
121 pub fn with_capacity(capacity: usize) -> Self {
122 Utf8PathBuf {
123 _encoding: PhantomData,
124 inner: String::with_capacity(capacity),
125 }
126 }
127
128 /// Coerces to a [`Utf8Path`] slice.
129 ///
130 /// # Examples
131 ///
132 /// ```
133 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
134 ///
135 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
136 /// let p = Utf8PathBuf::<Utf8UnixEncoding>::from("/test");
137 /// assert_eq!(Utf8Path::new("/test"), p.as_path());
138 /// ```
139 #[inline]
140 pub fn as_path(&self) -> &Utf8Path<T> {
141 self
142 }
143
144 /// Extends `self` with `path`.
145 ///
146 /// If `path` is absolute, it replaces the current path.
147 ///
148 /// With [`Utf8WindowsPathBuf`]:
149 ///
150 /// * if `path` has a root but no prefix (e.g., `\windows`), it
151 /// replaces everything except for the prefix (if any) of `self`.
152 /// * if `path` has a prefix but no root, it replaces `self`.
153 /// * if `self` has a verbatim prefix (e.g. `\\?\C:\windows`)
154 /// and `path` is not empty, the new path is normalized: all references
155 /// to `.` and `..` are removed.
156 ///
157 /// [`Utf8WindowsPathBuf`]: crate::Utf8WindowsPathBuf
158 ///
159 /// # Examples
160 ///
161 /// Pushing a relative path extends the existing path:
162 ///
163 /// ```
164 /// use typed_path::{Utf8PathBuf, Utf8UnixEncoding};
165 ///
166 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
167 /// let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/tmp");
168 /// path.push("file.bk");
169 /// assert_eq!(path, Utf8PathBuf::from("/tmp/file.bk"));
170 /// ```
171 ///
172 /// Pushing an absolute path replaces the existing path:
173 ///
174 /// ```
175 /// use typed_path::{Utf8PathBuf, Utf8UnixEncoding};
176 ///
177 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
178 /// let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/tmp");
179 /// path.push("/etc");
180 /// assert_eq!(path, Utf8PathBuf::from("/etc"));
181 /// ```
182 pub fn push<P: AsRef<Utf8Path<T>>>(&mut self, path: P) {
183 T::push(&mut self.inner, path.as_ref().as_str());
184 }
185
186 /// Like [`Utf8PathBuf::push`], extends `self` with `path`, but also checks to ensure that
187 /// `path` abides by a set of rules.
188 ///
189 /// # Rules
190 ///
191 /// 1. `path` cannot contain a prefix component.
192 /// 2. `path` cannot contain a root component.
193 /// 3. `path` cannot contain invalid filename bytes.
194 /// 4. `path` cannot contain parent components such that the current path would be escaped.
195 ///
196 /// # Examples
197 ///
198 /// Pushing a relative path extends the existing path:
199 ///
200 /// ```
201 /// use typed_path::{Utf8PathBuf, Utf8UnixEncoding};
202 ///
203 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
204 /// let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/tmp");
205 ///
206 /// // Pushing a relative path works like normal
207 /// assert!(path.push_checked("file.bk").is_ok());
208 /// assert_eq!(path, Utf8PathBuf::from("/tmp/file.bk"));
209 /// ```
210 ///
211 /// Pushing a relative path that contains unresolved parent directory references fails
212 /// with an error:
213 ///
214 /// ```
215 /// use typed_path::{CheckedPathError, Utf8PathBuf, Utf8UnixEncoding};
216 ///
217 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
218 /// let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/tmp");
219 ///
220 /// // Pushing a relative path that contains parent directory references that cannot be
221 /// // resolved within the path is considered an error as this is considered a path
222 /// // traversal attack!
223 /// assert_eq!(path.push_checked(".."), Err(CheckedPathError::PathTraversalAttack));
224 /// assert_eq!(path, Utf8PathBuf::from("/tmp"));
225 /// ```
226 ///
227 /// Pushing an absolute path fails with an error:
228 ///
229 /// ```
230 /// use typed_path::{CheckedPathError, Utf8PathBuf, Utf8UnixEncoding};
231 ///
232 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
233 /// let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/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, Utf8PathBuf::from("/tmp"));
238 /// ```
239 pub fn push_checked<P: AsRef<Utf8Path<T>>>(&mut self, path: P) -> Result<(), CheckedPathError> {
240 T::push_checked(&mut self.inner, path.as_ref().as_str())
241 }
242
243 /// Truncates `self` to [`self.parent`].
244 ///
245 /// Returns `false` and does nothing if [`self.parent`] is [`None`].
246 /// Otherwise, returns `true`.
247 ///
248 /// [`self.parent`]: Utf8Path::parent
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
254 ///
255 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
256 /// let mut p = Utf8PathBuf::<Utf8UnixEncoding>::from("/spirited/away.rs");
257 ///
258 /// p.pop();
259 /// assert_eq!(Utf8Path::new("/spirited"), p);
260 /// p.pop();
261 /// assert_eq!(Utf8Path::new("/"), p);
262 /// ```
263 pub fn pop(&mut self) -> bool {
264 match self.parent().map(|p| p.as_str().len()) {
265 Some(len) => {
266 self.inner.truncate(len);
267 true
268 }
269 None => false,
270 }
271 }
272
273 /// Updates [`self.file_name`] to `file_name`.
274 ///
275 /// If [`self.file_name`] was [`None`], this is equivalent to pushing
276 /// `file_name`.
277 ///
278 /// Otherwise it is equivalent to calling [`pop`] and then pushing
279 /// `file_name`. The new path will be a sibling of the original path.
280 /// (That is, it will have the same parent.)
281 ///
282 /// [`self.file_name`]: Utf8Path::file_name
283 /// [`pop`]: Utf8PathBuf::pop
284 ///
285 /// # Examples
286 ///
287 /// ```
288 /// use typed_path::{Utf8PathBuf, Utf8UnixEncoding};
289 ///
290 /// // NOTE: A pathbuf cannot be created on its own without a defined encoding
291 /// let mut buf = Utf8PathBuf::<Utf8UnixEncoding>::from("/");
292 /// assert!(buf.file_name() == None);
293 /// buf.set_file_name("bar");
294 /// assert!(buf == Utf8PathBuf::from("/bar"));
295 /// assert!(buf.file_name().is_some());
296 /// buf.set_file_name("baz.txt");
297 /// assert!(buf == Utf8PathBuf::from("/baz.txt"));
298 /// ```
299 pub fn set_file_name<S: AsRef<str>>(&mut self, file_name: S) {
300 self._set_file_name(file_name.as_ref())
301 }
302
303 fn _set_file_name(&mut self, file_name: &str) {
304 if self.file_name().is_some() {
305 let popped = self.pop();
306 debug_assert!(popped);
307 }
308 self.push(file_name);
309 }
310
311 /// Updates [`self.extension`] to `extension`.
312 ///
313 /// Returns `false` and does nothing if [`self.file_name`] is [`None`],
314 /// returns `true` and updates the extension otherwise.
315 ///
316 /// If [`self.extension`] is [`None`], the extension is added; otherwise
317 /// it is replaced.
318 ///
319 /// Anything trailing [`self.file_name`], such as separators or a current directory segment,
320 /// is discarded alongside the old extension.
321 ///
322 /// [`self.file_name`]: Utf8Path::file_name
323 /// [`self.extension`]: Utf8Path::extension
324 ///
325 /// # Examples
326 ///
327 /// ```
328 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
329 ///
330 /// let mut p = Utf8PathBuf::<Utf8UnixEncoding>::from("/feel/the");
331 ///
332 /// p.set_extension("force");
333 /// assert_eq!(Utf8Path::new("/feel/the.force"), p.as_path());
334 ///
335 /// p.set_extension("dark_side");
336 /// assert_eq!(Utf8Path::new("/feel/the.dark_side"), p.as_path());
337 /// ```
338 ///
339 /// Trailing separators are dropped along with the old extension:
340 ///
341 /// ```
342 /// use typed_path::{Utf8Path, Utf8PathBuf, Utf8UnixEncoding};
343 ///
344 /// let mut p = Utf8PathBuf::<Utf8UnixEncoding>::from("/feel/the.force/");
345 ///
346 /// p.set_extension("dark_side");
347 /// assert_eq!(Utf8Path::new("/feel/the.dark_side"), p.as_path());
348 /// ```
349 pub fn set_extension<S: AsRef<str>>(&mut self, extension: S) -> bool {
350 self._set_extension(extension.as_ref())
351 }
352
353 fn _set_extension(&mut self, extension: &str) -> bool {
354 // The file stem borrows from our buffer, so the offset just past it is where the old
355 // extension begins. Everything from there on is discarded, which also covers anything
356 // trailing the file name such as separators or a current directory segment
357 let end_of_stem = match self.file_stem() {
358 Some(stem) => (stem.as_ptr() as usize - self.inner.as_ptr() as usize) + stem.len(),
359 None => return false,
360 };
361
362 self.inner.truncate(end_of_stem);
363
364 // Add the new extension if it exists
365 if !extension.is_empty() {
366 self.inner.push('.');
367 self.inner.push_str(extension);
368 }
369
370 true
371 }
372
373 /// Consumes the `PathBuf`, yielding its internal [`String`] storage.
374 ///
375 /// # Examples
376 ///
377 /// ```
378 /// use typed_path::{Utf8PathBuf, Utf8UnixEncoding};
379 ///
380 /// let p = Utf8PathBuf::<Utf8UnixEncoding>::from("/the/head");
381 /// let s = p.into_string();
382 /// ```
383 #[inline]
384 pub fn into_string(self) -> String {
385 self.inner
386 }
387
388 /// Converts this [`Utf8PathBuf`] into a [boxed](Box) [`Utf8Path`].
389 #[inline]
390 pub fn into_boxed_path(self) -> Box<Utf8Path<T>> {
391 let rw = Box::into_raw(self.inner.into_boxed_str()) as *mut Utf8Path<T>;
392 unsafe { Box::from_raw(rw) }
393 }
394
395 /// Invokes [`capacity`] on the underlying instance of [`String`].
396 ///
397 /// [`capacity`]: String::capacity
398 #[inline]
399 pub fn capacity(&self) -> usize {
400 self.inner.capacity()
401 }
402
403 /// Invokes [`clear`] on the underlying instance of [`String`].
404 ///
405 /// [`clear`]: String::clear
406 #[inline]
407 pub fn clear(&mut self) {
408 self.inner.clear()
409 }
410
411 /// Invokes [`reserve`] on the underlying instance of [`String`].
412 ///
413 /// [`reserve`]: String::reserve
414 #[inline]
415 pub fn reserve(&mut self, additional: usize) {
416 self.inner.reserve(additional)
417 }
418
419 /// Invokes [`try_reserve`] on the underlying instance of [`String`].
420 ///
421 /// [`try_reserve`]: String::try_reserve
422 #[inline]
423 pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
424 self.inner.try_reserve(additional)
425 }
426
427 /// Invokes [`reserve_exact`] on the underlying instance of [`String`].
428 ///
429 /// [`reserve_exact`]: String::reserve_exact
430 #[inline]
431 pub fn reserve_exact(&mut self, additional: usize) {
432 self.inner.reserve_exact(additional)
433 }
434
435 /// Invokes [`try_reserve_exact`] on the underlying instance of [`String`].
436 ///
437 /// [`try_reserve_exact`]: String::try_reserve_exact
438 #[inline]
439 pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
440 self.inner.try_reserve_exact(additional)
441 }
442
443 /// Invokes [`shrink_to_fit`] on the underlying instance of [`String`].
444 ///
445 /// [`shrink_to_fit`]: String::shrink_to_fit
446 #[inline]
447 pub fn shrink_to_fit(&mut self) {
448 self.inner.shrink_to_fit()
449 }
450
451 /// Invokes [`shrink_to`] on the underlying instance of [`String`].
452 ///
453 /// [`shrink_to`]: String::shrink_to
454 #[inline]
455 pub fn shrink_to(&mut self, min_capacity: usize) {
456 self.inner.shrink_to(min_capacity)
457 }
458
459 /// Consumes [`PathBuf`] and returns a new [`Utf8PathBuf`] by checking that the path contains
460 /// valid UTF-8.
461 ///
462 /// # Errors
463 ///
464 /// Returns `Err` if the path is not UTF-8 with a description as to why the
465 /// provided component is not UTF-8.
466 ///
467 /// # Examples
468 ///
469 /// ```
470 /// use typed_path::{PathBuf, Utf8PathBuf, UnixEncoding, Utf8UnixEncoding};
471 ///
472 /// let path_buf = PathBuf::<UnixEncoding>::from(&[0xf0, 0x9f, 0x92, 0x96]);
473 /// let utf8_path_buf = Utf8PathBuf::<Utf8UnixEncoding>::from_bytes_path_buf(path_buf).unwrap();
474 /// assert_eq!(utf8_path_buf.as_str(), "💖");
475 /// ```
476 pub fn from_bytes_path_buf<U>(path_buf: PathBuf<U>) -> Result<Self, FromUtf8Error>
477 where
478 U: Encoding,
479 {
480 Ok(Self {
481 _encoding: PhantomData,
482 inner: String::from_utf8(path_buf.inner)?,
483 })
484 }
485
486 /// Consumes [`PathBuf`] and returns a new [`Utf8PathBuf`] by checking that the path contains
487 /// valid UTF-8.
488 ///
489 /// # Errors
490 ///
491 /// Returns `Err` if the path is not UTF-8 with a description as to why the
492 /// provided component is not UTF-8.
493 ///
494 /// # Safety
495 ///
496 /// The path passed in must be valid UTF-8.
497 ///
498 /// # Examples
499 ///
500 /// ```
501 /// use typed_path::{PathBuf, Utf8PathBuf, UnixEncoding, Utf8UnixEncoding};
502 ///
503 /// let path_buf = PathBuf::<UnixEncoding>::from(&[0xf0, 0x9f, 0x92, 0x96]);
504 /// let utf8_path_buf = unsafe {
505 /// Utf8PathBuf::<Utf8UnixEncoding>::from_bytes_path_buf_unchecked(path_buf)
506 /// };
507 /// assert_eq!(utf8_path_buf.as_str(), "💖");
508 /// ```
509 pub unsafe fn from_bytes_path_buf_unchecked<U>(path_buf: PathBuf<U>) -> Self
510 where
511 U: Encoding,
512 {
513 Self {
514 _encoding: PhantomData,
515 inner: String::from_utf8_unchecked(path_buf.inner),
516 }
517 }
518
519 /// Consumes [`Utf8PathBuf`] and returns a new [`PathBuf`]
520 ///
521 /// # Examples
522 ///
523 /// ```
524 /// use typed_path::{PathBuf, Utf8PathBuf, UnixEncoding, Utf8UnixEncoding};
525 ///
526 /// let utf8_path_buf = Utf8PathBuf::<Utf8UnixEncoding>::from("💖");
527 /// let path_buf = utf8_path_buf.into_bytes_path_buf::<UnixEncoding>();
528 /// assert_eq!(path_buf.as_bytes(), &[0xf0, 0x9f, 0x92, 0x96]);
529 /// ```
530 pub fn into_bytes_path_buf<U>(self) -> PathBuf<U>
531 where
532 U: Encoding,
533 {
534 PathBuf {
535 _encoding: PhantomData,
536 inner: self.inner.into_bytes(),
537 }
538 }
539}
540
541impl<T> Clone for Utf8PathBuf<T>
542where
543 T: Utf8Encoding,
544{
545 #[inline]
546 fn clone(&self) -> Self {
547 Self {
548 _encoding: self._encoding,
549 inner: self.inner.clone(),
550 }
551 }
552}
553
554impl<T> fmt::Debug for Utf8PathBuf<T>
555where
556 T: Utf8Encoding,
557{
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 f.debug_struct("Utf8PathBuf")
560 .field("_encoding", &T::label())
561 .field("inner", &self.inner)
562 .finish()
563 }
564}
565
566impl<T> AsRef<[u8]> for Utf8PathBuf<T>
567where
568 T: Utf8Encoding,
569{
570 #[inline]
571 fn as_ref(&self) -> &[u8] {
572 self.as_str().as_bytes()
573 }
574}
575
576impl<T> AsRef<str> for Utf8PathBuf<T>
577where
578 T: Utf8Encoding,
579{
580 #[inline]
581 fn as_ref(&self) -> &str {
582 self.as_str()
583 }
584}
585
586impl<T> AsRef<Utf8Path<T>> for Utf8PathBuf<T>
587where
588 T: Utf8Encoding,
589{
590 #[inline]
591 fn as_ref(&self) -> &Utf8Path<T> {
592 self
593 }
594}
595
596impl<T> Borrow<Utf8Path<T>> for Utf8PathBuf<T>
597where
598 T: Utf8Encoding,
599{
600 #[inline]
601 fn borrow(&self) -> &Utf8Path<T> {
602 self.deref()
603 }
604}
605
606impl<T> Default for Utf8PathBuf<T>
607where
608 T: Utf8Encoding,
609{
610 #[inline]
611 fn default() -> Utf8PathBuf<T> {
612 Utf8PathBuf::new()
613 }
614}
615
616impl<T> fmt::Display for Utf8PathBuf<T>
617where
618 T: Utf8Encoding,
619{
620 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
621 fmt::Display::fmt(&self.inner, f)
622 }
623}
624
625impl<T> Deref for Utf8PathBuf<T>
626where
627 T: Utf8Encoding,
628{
629 type Target = Utf8Path<T>;
630
631 #[inline]
632 fn deref(&self) -> &Utf8Path<T> {
633 Utf8Path::new(&self.inner)
634 }
635}
636
637impl<T> Eq for Utf8PathBuf<T> where T: Utf8Encoding {}
638
639impl<T> PartialEq for Utf8PathBuf<T>
640where
641 T: Utf8Encoding,
642{
643 fn eq(&self, other: &Self) -> bool {
644 self.components() == other.components()
645 }
646}
647
648impl<T, P> Extend<P> for Utf8PathBuf<T>
649where
650 T: Utf8Encoding,
651 P: AsRef<Utf8Path<T>>,
652{
653 fn extend<I: IntoIterator<Item = P>>(&mut self, iter: I) {
654 iter.into_iter().for_each(move |p| self.push(p.as_ref()));
655 }
656}
657
658impl<T> From<Box<Utf8Path<T>>> for Utf8PathBuf<T>
659where
660 T: Utf8Encoding,
661{
662 fn from(boxed: Box<Utf8Path<T>>) -> Self {
663 boxed.into_path_buf()
664 }
665}
666
667impl<T, V> From<&V> for Utf8PathBuf<T>
668where
669 T: Utf8Encoding,
670 V: ?Sized + AsRef<str>,
671{
672 /// Converts a borrowed [`str`] to a [`Utf8PathBuf`].
673 ///
674 /// Allocates a [`Utf8PathBuf`] and copies the data into it.
675 #[inline]
676 fn from(s: &V) -> Self {
677 Utf8PathBuf::from(s.as_ref().to_string())
678 }
679}
680
681impl<T> From<String> for Utf8PathBuf<T>
682where
683 T: Utf8Encoding,
684{
685 /// Converts a [`String`] into a [`Utf8PathBuf`]
686 ///
687 /// This conversion does not allocate or copy memory.
688 #[inline]
689 fn from(inner: String) -> Self {
690 Utf8PathBuf {
691 _encoding: PhantomData,
692 inner,
693 }
694 }
695}
696
697impl<T> From<Utf8PathBuf<T>> for String
698where
699 T: Utf8Encoding,
700{
701 /// Converts a [`Utf8PathBuf`] into a [`String`]
702 ///
703 /// This conversion does not allocate or copy memory.
704 #[inline]
705 fn from(path_buf: Utf8PathBuf<T>) -> Self {
706 path_buf.inner
707 }
708}
709
710impl<T> FromStr for Utf8PathBuf<T>
711where
712 T: Utf8Encoding,
713{
714 type Err = core::convert::Infallible;
715
716 #[inline]
717 fn from_str(s: &str) -> Result<Self, Self::Err> {
718 Ok(Utf8PathBuf::from(s))
719 }
720}
721
722impl<'a, T> From<Cow<'a, Utf8Path<T>>> for Utf8PathBuf<T>
723where
724 T: Utf8Encoding,
725{
726 /// Converts a clone-on-write pointer to an owned path.
727 ///
728 /// Converting from a `Cow::Owned` does not clone or allocate.
729 #[inline]
730 fn from(p: Cow<'a, Utf8Path<T>>) -> Self {
731 p.into_owned()
732 }
733}
734
735impl<T, P> FromIterator<P> for Utf8PathBuf<T>
736where
737 T: Utf8Encoding,
738 P: AsRef<Utf8Path<T>>,
739{
740 fn from_iter<I: IntoIterator<Item = P>>(iter: I) -> Self {
741 let mut buf = Utf8PathBuf::new();
742 buf.extend(iter);
743 buf
744 }
745}
746
747impl<T> Hash for Utf8PathBuf<T>
748where
749 T: Utf8Encoding,
750{
751 fn hash<H: Hasher>(&self, h: &mut H) {
752 self.as_path().hash(h)
753 }
754}
755
756impl<'a, T> IntoIterator for &'a Utf8PathBuf<T>
757where
758 T: Utf8Encoding,
759{
760 type IntoIter = Utf8Iter<'a, T>;
761 type Item = &'a str;
762
763 #[inline]
764 fn into_iter(self) -> Self::IntoIter {
765 self.iter()
766 }
767}
768
769impl<T> cmp::PartialOrd for Utf8PathBuf<T>
770where
771 T: Utf8Encoding,
772{
773 #[inline]
774 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
775 Some(self.cmp(other))
776 }
777}
778
779impl<T> cmp::Ord for Utf8PathBuf<T>
780where
781 T: Utf8Encoding,
782{
783 #[inline]
784 fn cmp(&self, other: &Self) -> cmp::Ordering {
785 self.components().cmp(other.components())
786 }
787}
788
789#[cfg(feature = "serde")]
790struct Utf8PathBufVisitor<T>(PhantomData<T>)
791where
792 T: Utf8Encoding;
793
794#[cfg(feature = "serde")]
795impl<'de, T> Visitor<'de> for Utf8PathBufVisitor<T>
796where
797 T: Utf8Encoding,
798{
799 type Value = Utf8PathBuf<T>;
800
801 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
802 formatter.write_str("UTF-8 path string")
803 }
804
805 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
806 where
807 E: de::Error,
808 {
809 Ok(Utf8PathBuf::from(v))
810 }
811
812 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
813 where
814 E: de::Error,
815 {
816 Ok(Utf8PathBuf::from(v))
817 }
818
819 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
820 where
821 E: de::Error,
822 {
823 core::str::from_utf8(v)
824 .map(Utf8PathBuf::from)
825 .map_err(|_| de::Error::invalid_value(de::Unexpected::Bytes(v), &self))
826 }
827
828 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
829 where
830 E: de::Error,
831 {
832 String::from_utf8(v)
833 .map(Utf8PathBuf::from)
834 .map_err(|e| de::Error::invalid_value(de::Unexpected::Bytes(&e.into_bytes()), &self))
835 }
836}
837
838#[cfg(feature = "serde")]
839impl<T> Serialize for Utf8PathBuf<T>
840where
841 T: Utf8Encoding,
842{
843 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
844 where
845 S: Serializer,
846 {
847 self.as_str().serialize(serializer)
848 }
849}
850
851#[cfg(feature = "serde")]
852impl<'de, T> Deserialize<'de> for Utf8PathBuf<T>
853where
854 T: Utf8Encoding,
855{
856 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
857 where
858 D: Deserializer<'de>,
859 {
860 deserializer.deserialize_string(Utf8PathBufVisitor(PhantomData))
861 }
862}
863
864#[cfg(any(
865 unix,
866 all(target_vendor = "fortanix", target_env = "sgx"),
867 target_os = "solid_asp3",
868 target_os = "hermit",
869 all(
870 target_os = "wasi",
871 any(not(target_env = "p2"), feature = "nightly-wasip2")
872 )
873))]
874#[cfg(feature = "std")]
875mod std_conversions {
876 use std::ffi::{OsStr, OsString};
877 #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
878 use std::os::fortanix_sgx as os;
879 #[cfg(target_os = "solid_asp3")]
880 use std::os::solid as os;
881 #[cfg(any(target_os = "hermit", unix))]
882 use std::os::unix as os;
883 #[cfg(target_os = "wasi")]
884 use std::os::wasi as os;
885
886 use os::ffi::{OsStrExt, OsStringExt};
887
888 use super::*;
889
890 impl<T> From<Utf8PathBuf<T>> for OsString
891 where
892 T: Utf8Encoding,
893 {
894 #[inline]
895 fn from(path_buf: Utf8PathBuf<T>) -> Self {
896 OsStringExt::from_vec(path_buf.into_string().into_bytes())
897 }
898 }
899
900 impl<T> AsRef<OsStr> for Utf8PathBuf<T>
901 where
902 T: Utf8Encoding,
903 {
904 #[inline]
905 fn as_ref(&self) -> &OsStr {
906 OsStrExt::from_bytes(self.as_str().as_bytes())
907 }
908 }
909}
910
911#[cfg(test)]
912mod tests {
913 use super::*;
914 use crate::{Utf8UnixEncoding, Utf8WindowsEncoding};
915
916 #[test]
917 fn set_extension_should_replace_the_extension_of_the_file_name() {
918 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/feel/the");
919 assert!(path.set_extension("force"));
920 assert_eq!(path, Utf8PathBuf::from("/feel/the.force"));
921
922 assert!(path.set_extension("dark_side"));
923 assert_eq!(path, Utf8PathBuf::from("/feel/the.dark_side"));
924
925 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.tar.gz");
926 assert!(path.set_extension("xz"));
927 assert_eq!(path, Utf8PathBuf::from("foo.tar.xz"));
928 }
929
930 #[test]
931 fn set_extension_should_remove_the_extension_when_given_an_empty_extension() {
932 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.tar.gz");
933 assert!(path.set_extension(""));
934 assert_eq!(path, Utf8PathBuf::from("foo.tar"));
935
936 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.");
937 assert!(path.set_extension(""));
938 assert_eq!(path, Utf8PathBuf::from("foo"));
939 }
940
941 #[test]
942 fn set_extension_should_discard_anything_trailing_the_file_name() {
943 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("a.bc/");
944 assert!(path.set_extension("z"));
945 assert_eq!(path, Utf8PathBuf::from("a.z"));
946
947 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("a.bc/////");
948 assert!(path.set_extension("z"));
949 assert_eq!(path, Utf8PathBuf::from("a.z"));
950
951 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("test.reallylongext/");
952 assert!(path.set_extension("a"));
953 assert_eq!(path, Utf8PathBuf::from("test.a"));
954
955 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/usr/bin/");
956 assert!(path.set_extension("z"));
957 assert_eq!(path, Utf8PathBuf::from("/usr/bin.z"));
958
959 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.txt/.//");
960 assert!(path.set_extension("z"));
961 assert_eq!(path, Utf8PathBuf::from("foo.z"));
962
963 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo.tar.gz/");
964 assert!(path.set_extension(""));
965 assert_eq!(path, Utf8PathBuf::from("foo.tar"));
966
967 let mut path = Utf8PathBuf::<Utf8WindowsEncoding>::from(r"C:\a.bc\");
968 assert!(path.set_extension("z"));
969 assert_eq!(path, Utf8PathBuf::from(r"C:\a.z"));
970
971 let mut path = Utf8PathBuf::<Utf8WindowsEncoding>::from("C:/a.bc/");
972 assert!(path.set_extension("z"));
973 assert_eq!(path, Utf8PathBuf::from("C:/a.z"));
974 }
975
976 #[test]
977 fn set_extension_should_do_nothing_when_there_is_no_file_name() {
978 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/");
979 assert!(!path.set_extension("z"));
980 assert_eq!(path, Utf8PathBuf::from("/"));
981
982 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("foo/..");
983 assert!(!path.set_extension("z"));
984 assert_eq!(path, Utf8PathBuf::from("foo/.."));
985
986 let mut path = Utf8PathBuf::<Utf8WindowsEncoding>::from(r"C:\");
987 assert!(!path.set_extension("z"));
988 assert_eq!(path, Utf8PathBuf::from(r"C:\"));
989 }
990
991 #[test]
992 fn set_extension_should_support_multibyte_file_names() {
993 let mut path = Utf8PathBuf::<Utf8UnixEncoding>::from("/tmp/日本語.txt/");
994 assert!(path.set_extension("z"));
995 assert_eq!(path, Utf8PathBuf::from("/tmp/日本語.z"));
996 }
997
998 #[cfg(feature = "serde")]
999 #[test]
1000 fn deserialize_invalid_utf8() {
1001 use crate::Utf8WindowsPathBuf;
1002
1003 let bytes = vec![b'"', b'f', b'o', b'o', b'\\', 0xff, b'"'];
1004
1005 assert!(serde_json::from_slice::<Utf8WindowsPathBuf>(&bytes).is_err());
1006 }
1007}