use core::{
borrow::Borrow,
ffi::{c_char, CStr, FromBytesUntilNulError, FromBytesWithNulError},
num::NonZeroUsize,
ops::{Deref, Index},
slice::SliceIndex,
str::Utf8Error,
};
#[cfg(feature = "alloc")]
use crate::cstring::Utf8CString;
#[cfg(feature = "alloc")]
#[allow(unused_imports)]
use alloc::{borrow::Cow, borrow::ToOwned, ffi::CString, rc::Rc, string::String, sync::Arc};
#[repr(transparent)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Utf8CStr(CStr);
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum FromUtf8WithNul {
Utf8(Utf8Error),
CStr(FromBytesWithNulError),
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum FromUtf8UntilNul {
Utf8(Utf8Error),
CStr(FromBytesUntilNulError),
}
impl Utf8CStr {
pub fn from_utf8_with_nul(slice: &[u8]) -> Result<&Self, FromUtf8WithNul> {
let cstr = CStr::from_bytes_with_nul(slice)?;
let _ = cstr.to_str()?;
Ok(unsafe { Self::from_cstr_unchecked(cstr) })
}
pub fn from_utf8_until_nul(slice: &[u8]) -> Result<&Self, FromUtf8UntilNul> {
let cstr = CStr::from_bytes_until_nul(slice)?;
let _ = cstr.to_str()?;
Ok(unsafe { Self::from_cstr_unchecked(cstr) })
}
pub fn from_str_with_nul(str: &str) -> Result<&Self, FromBytesWithNulError> {
let cstr = CStr::from_bytes_with_nul(str.as_bytes())?;
Ok(unsafe { Self::from_cstr_unchecked(cstr) })
}
pub fn from_str_until_nul(str: &str) -> Result<&Self, FromBytesUntilNulError> {
let cstr = CStr::from_bytes_until_nul(str.as_bytes())?;
Ok(unsafe { Self::from_cstr_unchecked(cstr) })
}
#[must_use]
pub const unsafe fn from_utf8_with_nul_unchecked(slice: &[u8]) -> &Self {
unsafe { &*(core::ptr::from_ref(slice) as *const Utf8CStr) } }
#[must_use]
pub const unsafe fn from_cstr_unchecked(cstr: &CStr) -> &Self {
unsafe { Self::from_utf8_with_nul_unchecked(cstr.to_bytes_with_nul()) }
}
#[must_use]
pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a Self {
unsafe { Self::from_cstr_unchecked(CStr::from_ptr(ptr)) }
}
pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Self, Utf8Error> {
unsafe {
let cstr = CStr::from_ptr(ptr);
Self::from_cstr(cstr)
}
}
#[must_use]
pub const fn as_ptr(&self) -> *const c_char {
self.0.as_ptr()
}
pub fn from_cstr(cstr: &CStr) -> Result<&Self, Utf8Error> {
let _ = cstr.to_str()?;
Ok(unsafe { Self::from_cstr_unchecked(cstr) })
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn to_cstring(&self) -> Utf8CString {
self.to_owned()
}
#[must_use]
pub const fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.0.to_bytes()) }
}
#[must_use]
pub const fn as_str_with_nul(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.0.to_bytes_with_nul()) }
}
#[must_use]
pub const fn as_c_str(&self) -> &CStr {
&self.0
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8] {
self.0.to_bytes()
}
#[must_use]
pub const fn as_bytes_with_nul(&self) -> &[u8] {
self.0.to_bytes_with_nul()
}
#[must_use]
pub const fn len(&self) -> usize {
self.as_str().len()
}
#[must_use]
pub fn len_with_nul(&self) -> NonZeroUsize {
unsafe {
let len = self.as_str_with_nul().len();
NonZeroUsize::new_unchecked(len)
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
}
impl Deref for Utf8CStr {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<CStr> for Utf8CStr {
fn as_ref(&self) -> &CStr {
self.as_c_str()
}
}
impl AsRef<str> for Utf8CStr {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for Utf8CStr {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Borrow<CStr> for Utf8CStr {
fn borrow(&self) -> &CStr {
self.as_c_str()
}
}
super::cmp_impls! {
impl Utf8CStr {
CStr: Utf8CStr::as_c_str => core::convert::identity,
str: Utf8CStr::as_str => core::convert::identity,
&str: Utf8CStr::as_str => Deref::deref,
#[cfg(feature = "alloc")]
CString: Utf8CStr::as_c_str => CString::as_c_str,
#[cfg(feature = "alloc")]
String: Utf8CStr::as_str => String::as_str
}
}
impl<I> Index<I> for Utf8CStr
where
I: SliceIndex<str>,
{
type Output = I::Output;
fn index(&self, index: I) -> &Self::Output {
self.as_str().index(index)
}
}
impl core::fmt::Debug for Utf8CStr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.as_str_with_nul().fmt(f)
}
}
impl core::fmt::Display for Utf8CStr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.as_str().fmt(f)
}
}
impl Default for &Utf8CStr {
fn default() -> Self {
unsafe { Utf8CStr::from_utf8_with_nul_unchecked(b"\0") }
}
}
#[cfg(feature = "alloc")]
impl<'a> From<&'a Utf8CStr> for Cow<'a, CStr> {
fn from(value: &'a Utf8CStr) -> Self {
Cow::Borrowed(value.as_c_str())
}
}
#[cfg(feature = "alloc")]
impl<'a> From<&'a Utf8CStr> for Cow<'a, str> {
fn from(value: &'a Utf8CStr) -> Self {
Cow::Borrowed(value.as_str())
}
}
#[cfg(feature = "alloc")]
impl From<&Utf8CStr> for Rc<CStr> {
fn from(value: &Utf8CStr) -> Self {
Rc::from(value.as_c_str())
}
}
#[cfg(feature = "alloc")]
impl From<&Utf8CStr> for Rc<Utf8CStr> {
fn from(value: &Utf8CStr) -> Self {
let rc = Rc::<CStr>::from(value);
unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Utf8CStr) }
}
}
#[cfg(feature = "alloc")]
impl From<&Utf8CStr> for Rc<str> {
fn from(value: &Utf8CStr) -> Self {
Rc::from(value.as_str())
}
}
#[cfg(feature = "alloc")]
impl From<&Utf8CStr> for Arc<Utf8CStr> {
fn from(value: &Utf8CStr) -> Self {
let arc = Arc::<[u8]>::from(value.as_bytes_with_nul());
unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Utf8CStr) }
}
}
#[cfg(feature = "alloc")]
impl From<&Utf8CStr> for Arc<str> {
fn from(value: &Utf8CStr) -> Self {
Arc::from(value.as_str())
}
}
#[cfg(feature = "alloc")]
impl From<&Utf8CStr> for String {
fn from(value: &Utf8CStr) -> Self {
value.as_str().to_owned()
}
}
#[cfg(feature = "alloc")]
impl ToOwned for Utf8CStr {
type Owned = Utf8CString;
fn to_owned(&self) -> Self::Owned {
unsafe { Utf8CString::from_cstring_unchecked(self.0.to_owned()) }
}
}
impl core::fmt::Display for FromUtf8WithNul {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FromUtf8WithNul::Utf8(e) => e.fmt(f),
FromUtf8WithNul::CStr(e) => e.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromUtf8WithNul {}
impl From<Utf8Error> for FromUtf8WithNul {
fn from(value: Utf8Error) -> Self {
Self::Utf8(value)
}
}
impl From<FromBytesWithNulError> for FromUtf8WithNul {
fn from(value: FromBytesWithNulError) -> Self {
Self::CStr(value)
}
}
impl core::fmt::Display for FromUtf8UntilNul {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FromUtf8UntilNul::Utf8(e) => e.fmt(f),
FromUtf8UntilNul::CStr(e) => e.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FromUtf8UntilNul {}
impl From<Utf8Error> for FromUtf8UntilNul {
fn from(value: Utf8Error) -> Self {
Self::Utf8(value)
}
}
impl From<FromBytesUntilNulError> for FromUtf8UntilNul {
fn from(value: FromBytesUntilNulError) -> Self {
Self::CStr(value)
}
}