Skip to main content

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}