inline_flexstr/
cstr.rs

1use alloc::{borrow::Cow, ffi::CString};
2use core::{
3    ffi::{CStr, FromBytesWithNulError},
4    fmt,
5    str::FromStr,
6};
7
8use crate::inline::{INLINE_CAPACITY, InlineFlexStr, TooLongForInlining, inline_partial_eq_impl};
9
10use flexstr_support::{InteriorNulError, StringToFromBytes};
11
12/// Inline `CStr` type
13pub type InlineCStr = InlineFlexStr<CStr>;
14
15// *** TooLongOrNulError ***
16
17/// Error type returned when a C String is too long for inline storage or has an interior NUL byte.
18#[derive(Debug)]
19pub enum TooLongOrNulError {
20    /// The C String is too long for inline storage
21    TooLong(TooLongForInlining),
22    /// The C String has an interior NUL byte
23    NulError(InteriorNulError),
24}
25
26impl fmt::Display for TooLongOrNulError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            TooLongOrNulError::TooLong(e) => e.fmt(f),
30            TooLongOrNulError::NulError(e) => e.fmt(f),
31        }
32    }
33}
34
35impl core::error::Error for TooLongOrNulError {}
36
37// *** InlineFlexStr ***
38
39impl InlineFlexStr<CStr> {
40    fn try_from_bytes_without_nul(bytes: &[u8]) -> Result<Self, TooLongOrNulError> {
41        if bytes.len() < INLINE_CAPACITY {
42            let mut inline = Self::from_bytes(bytes);
43            inline.append_nul_zero();
44            Ok(inline)
45        } else {
46            Err(TooLongOrNulError::TooLong(TooLongForInlining {
47                length: bytes.len(),
48                inline_capacity: INLINE_CAPACITY,
49            }))
50        }
51    }
52
53    /// Attempt to create an inlined string from borrowed bytes with or without a trailing NUL byte.
54    pub fn try_from_bytes_with_or_without_nul(bytes: &[u8]) -> Result<Self, TooLongOrNulError> {
55        match CStr::from_bytes_with_nul(bytes) {
56            Ok(cstr) => Self::try_from_type(cstr).map_err(TooLongOrNulError::TooLong),
57            Err(FromBytesWithNulError::NotNulTerminated) => Self::try_from_bytes_without_nul(bytes),
58            Err(FromBytesWithNulError::InteriorNul { position }) => {
59                Err(TooLongOrNulError::NulError(InteriorNulError { position }))
60            }
61        }
62    }
63
64    /// Borrow the CStr as bytes with a trailing NUL byte
65    #[inline]
66    pub fn as_bytes_with_nul(&self) -> &[u8] {
67        self.as_raw_bytes()
68    }
69}
70
71// *** TryFrom for InlineFlexStr ***
72
73// NOTE: Cannot be implemented generically because of impl<T, U> TryFrom<U> for T where U: Into<T>
74impl<'s> TryFrom<&'s CStr> for InlineFlexStr<CStr> {
75    type Error = TooLongForInlining;
76
77    #[inline]
78    fn try_from(s: &'s CStr) -> Result<Self, Self::Error> {
79        InlineFlexStr::try_from_type(s)
80    }
81}
82
83impl<'s> TryFrom<&'s str> for InlineFlexStr<CStr> {
84    type Error = TooLongOrNulError;
85
86    #[inline]
87    fn try_from(s: &'s str) -> Result<Self, Self::Error> {
88        InlineFlexStr::try_from_bytes_with_or_without_nul(s.as_bytes())
89    }
90}
91
92impl<'s> TryFrom<&'s [u8]> for InlineFlexStr<CStr> {
93    type Error = TooLongOrNulError;
94
95    #[inline]
96    fn try_from(bytes: &'s [u8]) -> Result<Self, Self::Error> {
97        InlineFlexStr::try_from_bytes_with_or_without_nul(bytes)
98    }
99}
100
101// *** PartialEq ***
102
103inline_partial_eq_impl!(CStr, CStr);
104inline_partial_eq_impl!(&CStr, CStr);
105inline_partial_eq_impl!(CString, CStr);
106inline_partial_eq_impl!(Cow<'_, CStr>, CStr);
107
108// *** AsRef ***
109
110impl<S: ?Sized + StringToFromBytes> AsRef<CStr> for InlineFlexStr<S>
111where
112    S: AsRef<CStr>,
113{
114    fn as_ref(&self) -> &CStr {
115        self.as_ref_type().as_ref()
116    }
117}
118
119// *** FromStr ***
120
121impl FromStr for InlineFlexStr<CStr> {
122    type Err = TooLongOrNulError;
123
124    fn from_str(s: &str) -> Result<Self, Self::Err> {
125        InlineFlexStr::try_from_bytes_with_or_without_nul(s.as_bytes())
126    }
127}