Skip to main content

os_str_bytes/
lib.rs

1//! This crate provides additional functionality for [`OsStr`] and
2//! [`OsString`], without resorting to panics or corruption for invalid UTF-8.
3//! Thus, familiar methods from [`str`] and [`String`] can be used.
4//!
5//! # Usage
6//!
7//! The most important trait included is [`OsStrBytesExt`], which provides
8//! methods analogous to those of [`str`] but for [`OsStr`]. These methods will
9//! never panic for invalid UTF-8 in a platform string, so they can be used to
10//! manipulate [`OsStr`] values with the same simplicity possible for [`str`].
11//!
12//! Additionally, the following wrappers are provided. They are primarily
13//! legacy types from when this crate needed to perform more frequent encoding
14//! conversions. However, they may be useful for their trait implementations.
15//! - [`RawOsStr`] is a wrapper for [`OsStr`].
16//! - [`RawOsString`] is a wrapper for [`OsString`].
17//!
18//! # User Input
19//!
20//! Most methods in this crate should not be used to convert byte sequences
21//! that did not originate from [`OsStr`] or a related struct. The encoding
22//! used by this crate is an implementation detail, so it does not make sense
23//! to expose it to users.
24//!
25//! For user input with an unknown encoding similar to UTF-8, use the following
26//! IO-safe methods, which avoid errors when [writing to streams on
27//! Windows][windows_considerations]. These methods will not accept or return
28//! byte sequences that are invalid for input and output streams. Therefore,
29//! they can be used to convert between bytes strings exposed to users and
30//! platform strings.
31//! - [`OsStrBytes::from_io_bytes`]
32//! - [`OsStrBytes::to_io_bytes`]
33//! - [`OsStrBytes::to_io_bytes_lossy`]
34//! - [`OsStringBytes::from_io_vec`]
35//! - [`OsStringBytes::into_io_vec`]
36//! - [`OsStringBytes::into_io_vec_lossy`]
37//!
38//! # Features
39//!
40//! These features are optional and can be enabled or disabled in a
41//! "Cargo.toml" file.
42//!
43//! ### Default Features
44//!
45//! - **memchr** -
46//!   Changes the implementation to use crate [memchr] for better performance.
47//!   This feature is useless when the "raw\_os\_str" feature is disabled.
48//!
49//!   For more information, see [`OsStrBytesExt`][memchr_complexity].
50//!
51//! - **raw\_os\_str** -
52//!   Provides:
53//!   - [`iter`]
54//!   - [`NonUnicodeOsStr`]
55//!   - [`OsStrBytesExt`]
56//!   - [`OsUnit`]
57//!   - [`Pattern`]
58//!   - [`RawOsStr`]
59//!   - [`RawOsStrCow`]
60//!   - [`RawOsString`]
61//!
62//! ### Optional Features
63//!
64//! - **checked\_conversions** -
65//!   Provides:
66//!   - [`EncodingError`]
67//!   - [`OsStrBytes::from_raw_bytes`]
68//!   - [`OsStringBytes::from_raw_vec`]
69//!   - [`RawOsStr::cow_from_raw_bytes`]
70//!   - [`RawOsString::from_raw_vec`]
71//!
72//!   Because this feature should not be used in libraries, the
73//!   "OS\_STR\_BYTES\_CHECKED\_CONVERSIONS" environment variable must be
74//!   defined during compilation.
75//!
76//! - **conversions** -
77//!   Provides methods that require encoding conversion and may be expensive:
78//!   - [`OsStrBytesExt::ends_with_os`]
79//!   - [`OsStrBytesExt::starts_with_os`]
80//!   - [`RawOsStr::assert_cow_from_raw_bytes`]
81//!   - [`RawOsStr::ends_with_os`]
82//!   - [`RawOsStr::starts_with_os`]
83//!   - [`RawOsStr::to_raw_bytes`]
84//!   - [`RawOsString::assert_from_raw_vec`]
85//!   - [`RawOsString::into_raw_vec`]
86//!   - [`OsStrBytes::assert_from_raw_bytes`]
87//!   - [`OsStrBytes::to_raw_bytes`]
88//!   - [`OsStringBytes::assert_from_raw_vec`]
89//!   - [`OsStringBytes::into_raw_vec`]
90//!
91//!   For more information, see [Encoding Conversions].
92//!
93//! # Implementation
94//!
95//! Some methods return [`Cow`] to account for platform differences. However,
96//! no guarantee is made that the same variant of that enum will always be
97//! returned for the same platform. Whichever can be constructed most
98//! efficiently will be returned.
99//!
100//! All traits are [sealed], meaning that they can only be implemented by this
101//! crate. Otherwise, backward compatibility would be more difficult to
102//! maintain for new features.
103//!
104//! # Encoding Conversions
105//!
106//! Methods provided by the "conversions" feature use an intentionally
107//! unspecified encoding. It may vary for different platforms, so defining it
108//! would run contrary to the goal of generic string handling. However, the
109//! following invariants will always be upheld:
110//!
111//! - The encoding will be compatible with UTF-8. In particular, splitting an
112//!   encoded byte sequence by a UTF-8–encoded character always produces
113//!   other valid byte sequences. They can be re-encoded without error using
114//!   [`RawOsString::into_os_string`] and similar methods.
115//!
116//! - All characters valid in platform strings are representable. [`OsStr`] and
117//!   [`OsString`] can always be losslessly reconstructed from extracted bytes.
118//!
119//! Note that the chosen encoding may not match how [`OsStr`] stores these
120//! strings internally, which is undocumented. For instance, the result of
121//! calling [`OsStr::len`] will not necessarily match the number of bytes this
122//! crate uses to represent the same string. However, unlike the encoding used
123//! by [`OsStr`], the encoding used by this crate can be validated safely using
124//! the following methods:
125//! - [`OsStrBytes::assert_from_raw_bytes`]
126//! - [`RawOsStr::assert_cow_from_raw_bytes`]
127//! - [`RawOsString::assert_from_raw_vec`]
128//!
129//! Concatenation may yield unexpected results without a UTF-8 separator. If
130//! two platform strings need to be concatenated, the only safe way to do so is
131//! using [`OsString::push`]. This limitation also makes it undesirable to use
132//! the bytes in interchange.
133//!
134//! Since this encoding can change between versions and platforms, it should
135//! not be used for storage. The standard library provides implementations of
136//! [`OsStrExt`] and [`OsStringExt`] for various platforms, which should be
137//! preferred for that use case.
138//!
139//! # Related Crates
140//!
141//! - [print\_bytes] -
142//!   Used to print byte and platform strings as losslessly as possible.
143//!
144//! - [uniquote] -
145//!   Used to display paths using escapes instead of replacement characters.
146//!
147//! # Examples
148//!
149//! ```
150//! # use std::io;
151//! #
152//! # #[cfg(feature = "raw_os_str")]
153//! # {
154//! # #[cfg(any())]
155//! use std::env;
156//! use std::fs;
157//! # use std::path::Path;
158//!
159//! use os_str_bytes::OsStrBytesExt;
160//!
161//! # mod env {
162//! #   use std::ffi::OsString;
163//! #   use std::iter;
164//! #
165//! #   use tempfile::NamedTempFile;
166//! #
167//! #   pub(super) fn args_os() -> impl Iterator<Item = OsString> {
168//! #       let file = NamedTempFile::with_prefix("os_str_bytes_").unwrap();
169//! #       iter::from_fn(move || Some(file.path().as_os_str().to_owned()))
170//! #           .take(2)
171//! #   }
172//! # }
173//! #
174//! for file in env::args_os().skip(1) {
175//!     if !file.starts_with('-') {
176//!         let string = "Hello, world!";
177//! #       assert!(Path::new(&file).exists());
178//!         fs::write(&file, string)?;
179//!         assert_eq!(string, fs::read_to_string(file)?);
180//!     }
181//! }
182//! # }
183//! #
184//! # Ok::<_, io::Error>(())
185//! ```
186//!
187//! [Encoding Conversions]: #encoding-conversions
188//! [memchr]: https://crates.io/crates/memchr
189//! [memchr_complexity]: OsStrBytesExt#complexity
190//! [`OsStrExt`]: ::std::os::unix::ffi::OsStrExt
191//! [`OsStringExt`]: ::std::os::unix::ffi::OsStringExt
192//! [print\_bytes]: https://crates.io/crates/print_bytes
193//! [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed
194//! [uniquote]: https://crates.io/crates/uniquote
195//! [windows_considerations]: https://doc.rust-lang.org/std/io/struct.Stdout.html#note-windows-portability-considerations
196
197#![cfg_attr(
198    all(target_os = "uefi", not(feature = "conversions")),
199    allow(unused_features)
200)]
201// Only require a nightly compiler when building documentation for docs.rs.
202// This is a private option that should not be used.
203// https://github.com/rust-lang/docs.rs/issues/147#issuecomment-389544407
204// https://github.com/dylni/os_str_bytes/issues/2
205#![cfg_attr(os_str_bytes_docs_rs, feature(doc_cfg))]
206#![cfg_attr(
207    all(target_vendor = "fortanix", target_env = "sgx"),
208    feature(sgx_platform)
209)]
210#![cfg_attr(target_os = "uefi", feature(uefi_std))]
211#![cfg_attr(all(target_os = "wasi", target_env = "p2"), feature(wasip2))]
212#![warn(unused_results)]
213
214use std::borrow::Cow;
215use std::ffi::OsStr;
216use std::ffi::OsString;
217use std::path::Path;
218use std::path::PathBuf;
219
220macro_rules! if_checked_conversions {
221    ( $($item:item)+ ) => {
222    $(
223        #[cfg(feature = "checked_conversions")]
224        $item
225    )+
226    };
227}
228
229if_checked_conversions! {
230    use std::error::Error;
231    use std::fmt;
232    use std::fmt::Display;
233    use std::fmt::Formatter;
234    use std::result;
235}
236
237#[cfg(not(os_str_bytes_docs_rs))]
238if_checked_conversions! {
239    const _: &str = env!(
240        "OS_STR_BYTES_CHECKED_CONVERSIONS",
241        "The 'OS_STR_BYTES_CHECKED_CONVERSIONS' environment variable must be \
242         defined to use the 'checked_conversions' feature.",
243    );
244}
245
246#[cfg(all(feature = "memchr", not(feature = "raw_os_str")))]
247const _: &str = env!(
248    "__OS_STR_BYTES_CI",
249    concat!(
250        "The 'memchr' feature is useless when 'raw_os_str' is disabled; it \
251         should be disabled too.",
252    ),
253);
254
255macro_rules! if_conversions {
256    ( $($item:item)+ ) => {
257    $(
258        #[cfg(feature = "conversions")]
259        $item
260    )+
261    };
262}
263
264if_conversions! {
265    macro_rules! expect_encoded {
266        ( $result:expr ) => {
267            $result.expect("invalid raw bytes")
268        };
269    }
270}
271
272macro_rules! if_os_conversions {
273    ( $($item:item)+ ) => {
274    $(
275        #[cfg(any(feature = "conversions", feature = "raw_os_str"))]
276        $item
277    )+
278    };
279}
280
281macro_rules! if_raw_str {
282    ( $($item:item)+ ) => {
283    $(
284        #[cfg(feature = "raw_os_str")]
285        $item
286    )+
287    };
288}
289
290#[cfg_attr(
291    all(target_family = "wasm", target_os = "unknown"),
292    path = "wasm/mod.rs"
293)]
294#[cfg_attr(any(target_os = "uefi", windows), path = "windows/mod.rs")]
295#[cfg_attr(
296    not(any(
297        all(target_family = "wasm", target_os = "unknown"),
298        target_os = "uefi",
299        windows,
300    )),
301    path = "common/mod.rs"
302)]
303mod imp;
304use imp::convert_io;
305
306if_conversions! {
307    use imp::convert;
308}
309
310#[cfg(any(
311    all(feature = "conversions", any(target_os = "uefi", windows)),
312    feature = "raw_os_str",
313))]
314mod util;
315
316if_raw_str! {
317    mod ext;
318    pub use ext::OsStrBytesExt;
319
320    pub mod iter;
321    pub use iter::item::OsUnit;
322    pub use iter::item::NonUnicodeOsStr;
323
324    mod pattern;
325    pub use pattern::Pattern;
326
327    mod raw_str;
328    pub use raw_str::RawOsStr;
329    pub use raw_str::RawOsStrCow;
330    pub use raw_str::RawOsString;
331}
332
333if_checked_conversions! {
334    /// The error that occurs when a byte sequence is not representable in the
335    /// platform encoding.
336    ///
337    /// [`Result::unwrap`] should almost always be called on results containing
338    /// this error. It should be known whether or not byte sequences are
339    /// properly encoded for the platform, since [the module-level
340    /// documentation][encoding] discourages using encoded bytes in
341    /// interchange. Results are returned primarily to make panicking behavior
342    /// explicit.
343    ///
344    /// On Unix, this error is never returned, but [`OsStrExt`] or
345    /// [`OsStringExt`] should be used instead if that needs to be guaranteed.
346    ///
347    /// [encoding]: self#encoding-conversions
348    /// [`OsStrExt`]: ::std::os::unix::ffi::OsStrExt
349    /// [`OsStringExt`]: ::std::os::unix::ffi::OsStringExt
350    /// [`Result::unwrap`]: ::std::result::Result::unwrap
351    #[derive(Clone, Debug, PartialEq)]
352    #[cfg_attr(
353        os_str_bytes_docs_rs,
354        doc(cfg(feature = "checked_conversions"))
355    )]
356    pub struct EncodingError(convert::EncodingError);
357
358    impl Display for EncodingError {
359        #[inline]
360        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
361            self.0.fmt(f)
362        }
363    }
364
365    impl Error for EncodingError {}
366}
367
368if_checked_conversions! {
369    type Result<T> = result::Result<T, EncodingError>;
370}
371
372if_conversions! {
373    fn from_raw_bytes<'a, S>(string: S) -> convert::Result<Cow<'a, OsStr>>
374    where
375        S: Into<Cow<'a, [u8]>>,
376    {
377        match string.into() {
378            Cow::Borrowed(string) => convert::os_str_from_bytes(string),
379            Cow::Owned(string) => {
380                convert::os_string_from_vec(string).map(Cow::Owned)
381            }
382        }
383    }
384}
385
386if_conversions! {
387    fn cow_os_str_into_path(string: Cow<'_, OsStr>) -> Cow<'_, Path> {
388        match string {
389            Cow::Borrowed(string) => Cow::Borrowed(Path::new(string)),
390            Cow::Owned(string) => Cow::Owned(string.into()),
391        }
392    }
393}
394
395/// A platform agnostic variant of [`OsStrExt`].
396///
397/// For more information, see [the module-level documentation][module].
398///
399/// [module]: self
400/// [`OsStrExt`]: ::std::os::unix::ffi::OsStrExt
401pub trait OsStrBytes: private::Sealed + ToOwned {
402    if_conversions! {
403        /// Converts a byte string into an equivalent platform-native string.
404        ///
405        /// # Panics
406        ///
407        /// Panics if the string is not valid for the [unspecified encoding]
408        /// used by this crate.
409        ///
410        /// # Examples
411        ///
412        /// ```
413        /// use std::env;
414        /// use std::ffi::OsStr;
415        /// # use std::io;
416        ///
417        /// use os_str_bytes::OsStrBytes;
418        ///
419        /// let os_string = env::current_exe()?;
420        /// let os_bytes = os_string.to_raw_bytes();
421        /// assert_eq!(os_string, OsStr::assert_from_raw_bytes(os_bytes));
422        /// #
423        /// # Ok::<_, io::Error>(())
424        /// ```
425        ///
426        /// [unspecified encoding]: self#encoding-conversions
427        #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))]
428        #[must_use = "method should not be used for validation"]
429        #[track_caller]
430        fn assert_from_raw_bytes<'a, S>(string: S) -> Cow<'a, Self>
431        where
432            S: Into<Cow<'a, [u8]>>;
433    }
434
435    /// Converts a byte string into an equivalent platform-native string, if it
436    /// is [IO-safe].
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// use std::ffi::OsStr;
442    /// use std::io;
443    /// use std::io::Read;
444    ///
445    /// use os_str_bytes::OsStrBytes;
446    ///
447    /// let mut io_string = Vec::new();
448    /// let _ = io::stdin().read_to_end(&mut io_string)?;
449    /// let os_string = OsStr::from_io_bytes(&io_string).ok_or_else(|| {
450    ///     io::Error::new(io::ErrorKind::InvalidInput, "invalid input")
451    /// })?;
452    /// println!("{:?}", os_string);
453    /// #
454    /// # Ok::<_, io::Error>(())
455    /// ```
456    ///
457    /// [IO-safe]: self#user-input
458    #[must_use]
459    fn from_io_bytes(string: &[u8]) -> Option<&Self>;
460
461    if_checked_conversions! {
462        /// Converts a byte string into an equivalent platform-native string.
463        ///
464        /// [`assert_from_raw_bytes`] should almost always be used instead. For
465        /// more information, see [`EncodingError`].
466        ///
467        /// # Errors
468        ///
469        /// See documentation for [`EncodingError`].
470        ///
471        /// # Examples
472        ///
473        /// ```
474        /// use std::env;
475        /// use std::ffi::OsStr;
476        /// # use std::io;
477        ///
478        /// use os_str_bytes::OsStrBytes;
479        ///
480        /// let os_string = env::current_exe()?;
481        /// let os_bytes = os_string.to_raw_bytes();
482        /// assert_eq!(os_string, OsStr::from_raw_bytes(os_bytes).unwrap());
483        /// #
484        /// # Ok::<_, io::Error>(())
485        /// ```
486        ///
487        /// [`assert_from_raw_bytes`]: Self::assert_from_raw_bytes
488        #[cfg_attr(
489            os_str_bytes_docs_rs,
490            doc(cfg(feature = "checked_conversions"))
491        )]
492        fn from_raw_bytes<'a, S>(string: S) -> Result<Cow<'a, Self>>
493        where
494            S: Into<Cow<'a, [u8]>>;
495    }
496
497    /// Converts a platform-native string into an equivalent byte string, if it
498    /// is [IO-safe].
499    ///
500    /// # Examples
501    ///
502    /// ```
503    /// use std::env;
504    /// use std::io;
505    /// use std::io::Write;
506    ///
507    /// use os_str_bytes::OsStrBytes;
508    ///
509    /// let os_string = env::current_exe()?;
510    /// let io_string = os_string.to_io_bytes().ok_or_else(|| {
511    ///     io::Error::new(io::ErrorKind::InvalidInput, "invalid input")
512    /// })?;
513    /// io::stdout().write_all(io_string)?;
514    /// #
515    /// # Ok::<_, io::Error>(())
516    /// ```
517    ///
518    /// [IO-safe]: self#user-input
519    #[must_use]
520    fn to_io_bytes(&self) -> Option<&'_ [u8]>;
521
522    /// Converts a platform-native string into an equivalent byte string.
523    ///
524    /// If the string is not [IO-safe], invalid characters will be replaced
525    /// with [`REPLACEMENT_CHARACTER`].
526    ///
527    /// # Examples
528    ///
529    /// ```
530    /// use std::env;
531    /// use std::io;
532    /// use std::io::Write;
533    ///
534    /// use os_str_bytes::OsStrBytes;
535    ///
536    /// let os_string = env::current_exe()?;
537    /// let io_string = os_string.to_io_bytes_lossy();
538    /// io::stdout().write_all(&io_string)?;
539    /// #
540    /// # Ok::<_, io::Error>(())
541    /// ```
542    ///
543    /// [IO-safe]: self#user-input
544    /// [`REPLACEMENT_CHARACTER`]: char::REPLACEMENT_CHARACTER
545    #[must_use]
546    fn to_io_bytes_lossy(&self) -> Cow<'_, [u8]>;
547
548    if_conversions! {
549        /// Converts a platform-native string into an equivalent byte string.
550        ///
551        /// The returned string will use an [unspecified encoding].
552        ///
553        /// # Examples
554        ///
555        /// ```
556        /// use std::ffi::OsStr;
557        ///
558        /// use os_str_bytes::OsStrBytes;
559        ///
560        /// let string = "foobar";
561        /// let os_string = OsStr::new(string);
562        /// assert_eq!(string.as_bytes(), &*os_string.to_raw_bytes());
563        /// ```
564        ///
565        /// [unspecified encoding]: self#encoding-conversions
566        #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))]
567        #[must_use]
568        fn to_raw_bytes(&self) -> Cow<'_, [u8]>;
569    }
570}
571
572impl OsStrBytes for OsStr {
573    if_conversions! {
574        #[inline]
575        fn assert_from_raw_bytes<'a, S>(string: S) -> Cow<'a, Self>
576        where
577            S: Into<Cow<'a, [u8]>>,
578        {
579            expect_encoded!(from_raw_bytes(string))
580        }
581    }
582
583    #[inline]
584    fn from_io_bytes(string: &[u8]) -> Option<&Self> {
585        convert_io::os_str_from_bytes(string)
586    }
587
588    if_checked_conversions! {
589        #[inline]
590        fn from_raw_bytes<'a, S>(string: S) -> Result<Cow<'a, Self>>
591        where
592            S: Into<Cow<'a, [u8]>>,
593        {
594            from_raw_bytes(string).map_err(EncodingError)
595        }
596    }
597
598    #[inline]
599    fn to_io_bytes(&self) -> Option<&'_ [u8]> {
600        convert_io::os_str_to_bytes(self)
601    }
602
603    #[inline]
604    fn to_io_bytes_lossy(&self) -> Cow<'_, [u8]> {
605        convert_io::os_str_to_bytes_lossy(self)
606    }
607
608    if_conversions! {
609        #[inline]
610        fn to_raw_bytes(&self) -> Cow<'_, [u8]> {
611            convert::os_str_to_bytes(self)
612        }
613    }
614}
615
616impl OsStrBytes for Path {
617    if_conversions! {
618        #[inline]
619        fn assert_from_raw_bytes<'a, S>(string: S) -> Cow<'a, Self>
620        where
621            S: Into<Cow<'a, [u8]>>,
622        {
623            cow_os_str_into_path(OsStr::assert_from_raw_bytes(string))
624        }
625    }
626
627    #[inline]
628    fn from_io_bytes(string: &[u8]) -> Option<&Self> {
629        OsStr::from_io_bytes(string).map(Self::new)
630    }
631
632    if_checked_conversions! {
633        #[inline]
634        fn from_raw_bytes<'a, S>(string: S) -> Result<Cow<'a, Self>>
635        where
636            S: Into<Cow<'a, [u8]>>,
637        {
638            OsStr::from_raw_bytes(string).map(cow_os_str_into_path)
639        }
640    }
641
642    #[inline]
643    fn to_io_bytes(&self) -> Option<&'_ [u8]> {
644        self.as_os_str().to_io_bytes()
645    }
646
647    #[inline]
648    fn to_io_bytes_lossy(&self) -> Cow<'_, [u8]> {
649        self.as_os_str().to_io_bytes_lossy()
650    }
651
652    if_conversions! {
653        #[inline]
654        fn to_raw_bytes(&self) -> Cow<'_, [u8]> {
655            self.as_os_str().to_raw_bytes()
656        }
657    }
658}
659
660/// A platform agnostic variant of [`OsStringExt`].
661///
662/// For more information, see [the module-level documentation][module].
663///
664/// [module]: self
665/// [`OsStringExt`]: ::std::os::unix::ffi::OsStringExt
666pub trait OsStringBytes: private::Sealed + Sized {
667    if_conversions! {
668        /// Converts a byte string into an equivalent platform-native string.
669        ///
670        /// # Panics
671        ///
672        /// Panics if the string is not valid for the [unspecified encoding]
673        /// used by this crate.
674        ///
675        /// # Examples
676        ///
677        /// ```
678        /// use std::env;
679        /// use std::ffi::OsString;
680        /// # use std::io;
681        ///
682        /// use os_str_bytes::OsStringBytes;
683        ///
684        /// let os_string = env::current_exe()?;
685        /// let os_bytes = os_string.clone().into_raw_vec();
686        /// assert_eq!(os_string, OsString::assert_from_raw_vec(os_bytes));
687        /// #
688        /// # Ok::<_, io::Error>(())
689        /// ```
690        ///
691        /// [unspecified encoding]: self#encoding-conversions
692        #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))]
693        #[must_use = "method should not be used for validation"]
694        #[track_caller]
695        fn assert_from_raw_vec(string: Vec<u8>) -> Self;
696    }
697
698    /// Converts a byte string into an equivalent platform-native string, if it
699    /// is [IO-safe].
700    ///
701    /// # Examples
702    ///
703    /// ```
704    /// use std::ffi::OsString;
705    /// use std::io;
706    /// use std::io::Read;
707    ///
708    /// use os_str_bytes::OsStringBytes;
709    ///
710    /// let mut io_string = Vec::new();
711    /// let _ = io::stdin().read_to_end(&mut io_string)?;
712    /// let os_string = OsString::from_io_vec(io_string).ok_or_else(|| {
713    ///     io::Error::new(io::ErrorKind::InvalidInput, "invalid input")
714    /// })?;
715    /// println!("{:?}", os_string);
716    /// #
717    /// # Ok::<_, io::Error>(())
718    /// ```
719    ///
720    /// [IO-safe]: self#user-input
721    #[must_use]
722    fn from_io_vec(string: Vec<u8>) -> Option<Self>;
723
724    if_checked_conversions! {
725        /// Converts a byte string into an equivalent platform-native string.
726        ///
727        /// [`assert_from_raw_vec`] should almost always be used instead. For
728        /// more information, see [`EncodingError`].
729        ///
730        /// # Errors
731        ///
732        /// See documentation for [`EncodingError`].
733        ///
734        /// # Examples
735        ///
736        /// ```
737        /// use std::env;
738        /// use std::ffi::OsString;
739        /// # use std::io;
740        ///
741        /// use os_str_bytes::OsStringBytes;
742        ///
743        /// let os_string = env::current_exe()?;
744        /// let os_bytes = os_string.clone().into_raw_vec();
745        /// assert_eq!(
746        ///     os_string,
747        ///     OsString::from_raw_vec(os_bytes).unwrap(),
748        /// );
749        /// #
750        /// # Ok::<_, io::Error>(())
751        /// ```
752        ///
753        /// [`assert_from_raw_vec`]: Self::assert_from_raw_vec
754        #[cfg_attr(
755            os_str_bytes_docs_rs,
756            doc(cfg(feature = "checked_conversions"))
757        )]
758        fn from_raw_vec(string: Vec<u8>) -> Result<Self>;
759    }
760
761    /// Converts a platform-native string into an equivalent byte string, if it
762    /// is [IO-safe].
763    ///
764    /// # Examples
765    ///
766    /// ```
767    /// use std::env;
768    /// use std::io;
769    /// use std::io::Write;
770    ///
771    /// use os_str_bytes::OsStringBytes;
772    ///
773    /// let os_string = env::current_exe()?;
774    /// let io_string = os_string.into_io_vec().ok_or_else(|| {
775    ///     io::Error::new(io::ErrorKind::InvalidInput, "invalid input")
776    /// })?;
777    /// io::stdout().write_all(&io_string)?;
778    /// #
779    /// # Ok::<_, io::Error>(())
780    /// ```
781    ///
782    /// [IO-safe]: self#user-input
783    #[must_use]
784    fn into_io_vec(self) -> Option<Vec<u8>>;
785
786    /// Converts a platform-native string into an equivalent byte string.
787    ///
788    /// If the string is not [IO-safe], invalid characters will be replaced
789    /// with [`REPLACEMENT_CHARACTER`].
790    ///
791    /// # Examples
792    ///
793    /// ```
794    /// use std::env;
795    /// use std::io;
796    /// use std::io::Write;
797    ///
798    /// use os_str_bytes::OsStringBytes;
799    ///
800    /// let os_string = env::current_exe()?;
801    /// let io_string = os_string.into_io_vec_lossy();
802    /// io::stdout().write_all(&io_string)?;
803    /// #
804    /// # Ok::<_, io::Error>(())
805    /// ```
806    ///
807    /// [IO-safe]: self#user-input
808    /// [`REPLACEMENT_CHARACTER`]: char::REPLACEMENT_CHARACTER
809    #[must_use]
810    fn into_io_vec_lossy(self) -> Vec<u8>;
811
812    if_conversions! {
813        /// Converts a platform-native string into an equivalent byte string.
814        ///
815        /// The returned string will use an [unspecified encoding].
816        ///
817        /// # Examples
818        ///
819        /// ```
820        /// use std::ffi::OsString;
821        ///
822        /// use os_str_bytes::OsStringBytes;
823        ///
824        /// let string = "foobar".to_owned();
825        /// let os_string: OsString = string.clone().into();
826        /// assert_eq!(string.into_bytes(), os_string.into_raw_vec());
827        /// ```
828        ///
829        /// [unspecified encoding]: self#encoding-conversions
830        #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "conversions")))]
831        #[must_use]
832        fn into_raw_vec(self) -> Vec<u8>;
833    }
834}
835
836impl OsStringBytes for OsString {
837    if_conversions! {
838        #[inline]
839        fn assert_from_raw_vec(string: Vec<u8>) -> Self {
840            expect_encoded!(convert::os_string_from_vec(string))
841        }
842    }
843
844    if_checked_conversions! {
845        #[inline]
846        fn from_raw_vec(string: Vec<u8>) -> Result<Self> {
847            convert::os_string_from_vec(string).map_err(EncodingError)
848        }
849    }
850
851    #[inline]
852    fn from_io_vec(string: Vec<u8>) -> Option<Self> {
853        convert_io::os_string_from_vec(string)
854    }
855
856    #[inline]
857    fn into_io_vec(self) -> Option<Vec<u8>> {
858        convert_io::os_string_into_vec(self)
859    }
860
861    #[inline]
862    fn into_io_vec_lossy(self) -> Vec<u8> {
863        convert_io::os_string_into_vec_lossy(self)
864    }
865
866    if_conversions! {
867        #[inline]
868        fn into_raw_vec(self) -> Vec<u8> {
869            convert::os_string_into_vec(self)
870        }
871    }
872}
873
874impl OsStringBytes for PathBuf {
875    if_conversions! {
876        #[inline]
877        fn assert_from_raw_vec(string: Vec<u8>) -> Self {
878            OsString::assert_from_raw_vec(string).into()
879        }
880    }
881
882    if_checked_conversions! {
883        #[inline]
884        fn from_raw_vec(string: Vec<u8>) -> Result<Self> {
885            OsString::from_raw_vec(string).map(Into::into)
886        }
887    }
888
889    #[inline]
890    fn from_io_vec(string: Vec<u8>) -> Option<Self> {
891        OsString::from_io_vec(string).map(Into::into)
892    }
893
894    #[inline]
895    fn into_io_vec(self) -> Option<Vec<u8>> {
896        self.into_os_string().into_io_vec()
897    }
898
899    #[inline]
900    fn into_io_vec_lossy(self) -> Vec<u8> {
901        self.into_os_string().into_io_vec_lossy()
902    }
903
904    if_conversions! {
905        #[inline]
906        fn into_raw_vec(self) -> Vec<u8> {
907            self.into_os_string().into_raw_vec()
908        }
909    }
910}
911
912mod private {
913    use std::ffi::OsStr;
914    use std::ffi::OsString;
915    use std::path::Path;
916    use std::path::PathBuf;
917
918    if_raw_str! {
919        use std::borrow::Cow;
920
921        use super::RawOsStr;
922    }
923
924    pub trait Sealed {}
925
926    impl Sealed for char {}
927    impl Sealed for OsStr {}
928    impl Sealed for OsString {}
929    impl Sealed for Path {}
930    impl Sealed for PathBuf {}
931    impl Sealed for &str {}
932    impl Sealed for &String {}
933
934    if_raw_str! {
935        impl Sealed for Cow<'_, RawOsStr> {}
936    }
937}