use alloc::{borrow::Cow, ffi::CString, rc::Rc, sync::Arc};
use core::{
ffi::{CStr, FromBytesWithNulError},
str::FromStr,
};
use crate::flex::{
FlexStr, ImmutableBytes, RefCounted, RefCountedMut, partial_eq_impl, ref_counted_mut_impl,
};
pub use flexstr_support::InteriorNulError;
use flexstr_support::StringToFromBytes;
use inline_flexstr::{InlineFlexStr, TooLongOrNulError};
pub type LocalCStr = FlexStr<'static, CStr, Rc<CStr>>;
pub type SharedCStr = FlexStr<'static, CStr, Arc<CStr>>;
pub type LocalCStrRef<'s> = FlexStr<'s, CStr, Rc<CStr>>;
pub type SharedCStrRef<'s> = FlexStr<'s, CStr, Arc<CStr>>;
const _: () = assert!(
size_of::<Option<LocalCStr>>() <= size_of::<CString>() + size_of::<usize>(),
"Option<LocalCStr> must be less than or equal to the size of CString plus one machine word"
);
const _: () = assert!(
size_of::<Option<SharedCStr>>() <= size_of::<CString>() + size_of::<usize>(),
"Option<SharedCStr> must be less than or equal to the size of CString plus one machine word"
);
impl<'s, R: RefCounted<CStr>> FlexStr<'s, CStr, R> {
fn from_bytes_without_nul(bytes: &'s [u8]) -> Self {
match InlineFlexStr::try_from_bytes_with_or_without_nul(bytes) {
Ok(inline) => FlexStr::from_inline(inline),
Err(TooLongOrNulError::TooLong(_)) => FlexStr::from_owned(
#[cfg(feature = "safe")]
CString::new(bytes).expect("Unexpected interior NUL byte"),
#[cfg(not(feature = "safe"))]
unsafe {
CString::from_vec_unchecked(bytes.into())
},
),
Err(TooLongOrNulError::NulError(e)) => {
unreachable!("Interior NUL byte found at position {}", e.position)
}
}
}
pub fn try_from_bytes_with_or_without_nul(bytes: &'s [u8]) -> Result<Self, InteriorNulError> {
match CStr::from_bytes_with_nul(bytes) {
Ok(cstr) => Ok(FlexStr::from_borrowed(cstr)),
Err(FromBytesWithNulError::NotNulTerminated) => Ok(Self::from_bytes_without_nul(bytes)),
Err(FromBytesWithNulError::InteriorNul { position }) => {
Err(InteriorNulError { position })
}
}
}
#[inline]
pub fn as_bytes_with_nul(&self) -> &[u8] {
self.as_raw_bytes()
}
}
impl ImmutableBytes for CStr {}
ref_counted_mut_impl!(CStr);
impl<'s, R: RefCounted<CStr>> From<CString> for FlexStr<'s, CStr, R> {
fn from(s: CString) -> Self {
FlexStr::from_owned(s)
}
}
impl<'s, R: RefCounted<CStr>> TryFrom<&'s str> for FlexStr<'s, CStr, R> {
type Error = InteriorNulError;
#[inline]
fn try_from(s: &'s str) -> Result<Self, Self::Error> {
FlexStr::try_from_bytes_with_or_without_nul(s.as_bytes())
}
}
impl<'s, R: RefCounted<CStr>> TryFrom<&'s [u8]> for FlexStr<'s, CStr, R> {
type Error = InteriorNulError;
#[inline]
fn try_from(bytes: &'s [u8]) -> Result<Self, Self::Error> {
FlexStr::try_from_bytes_with_or_without_nul(bytes)
}
}
partial_eq_impl!(CStr, CStr);
partial_eq_impl!(&CStr, CStr);
partial_eq_impl!(CString, CStr);
partial_eq_impl!(Cow<'s, CStr>, CStr);
impl<'s, S: ?Sized + StringToFromBytes, R: RefCounted<S>> AsRef<CStr> for FlexStr<'s, S, R>
where
S: AsRef<CStr>,
{
fn as_ref(&self) -> &CStr {
self.as_ref_type().as_ref()
}
}
impl<R: RefCounted<CStr>> FromStr for FlexStr<'static, CStr, R> {
type Err = InteriorNulError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
FlexStr::try_from_bytes_with_or_without_nul(s.as_bytes()).map(FlexStr::into_owned)
}
}