use crate::{
const_nonzero_bytes_with_null_terminator, const_str_without_null_terminator, C8StrError,
};
use core::{
ffi::{c_char, CStr},
fmt::{self, Display},
ops::{Deref, Index, RangeFrom, RangeFull},
slice, str,
};
#[repr(transparent)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct C8Str(str);
impl C8Str {
#[cfg(test)]
#[inline(always)]
pub(crate) fn inner(&self) -> &str {
&self.0
}
pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&C8Str, C8StrError> {
if let Some(bytes) = const_nonzero_bytes_with_null_terminator(bytes) {
match str::from_utf8(bytes) {
Ok(str) => Ok(unsafe {
Self::from_str_with_nul_unchecked(str)
}),
Err(e) => Err(C8StrError::not_utf8(e.valid_up_to())),
}
} else {
Err(C8StrError::missing_terminator())
}
}
pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&C8Str, C8StrError> {
if let Some(bytes2) = const_nonzero_bytes_with_null_terminator(bytes) {
if bytes.len() == bytes2.len() {
match str::from_utf8(bytes) {
Ok(str) => Ok(unsafe {
Self::from_str_with_nul_unchecked(str)
}),
Err(e) => Err(C8StrError::not_utf8(e.valid_up_to())),
}
} else {
Err(C8StrError::inner_zero(bytes2.len() - 1))
}
} else {
Err(C8StrError::missing_terminator())
}
}
#[inline(always)]
pub const unsafe fn from_utf8_bytes_with_nul_unchecked(bytes: &[u8]) -> &C8Str {
unsafe { Self::from_str_with_nul_unchecked(str::from_utf8_unchecked(bytes)) }
}
pub const unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a C8Str, C8StrError> {
let mut len = 0;
while ptr.add(len).read() != 0 {
len += 1;
}
Ok(Self::from_str_with_nul_unchecked(
match str::from_utf8(slice::from_raw_parts(ptr as *const u8, len + 1)) {
Ok(result) => result,
Err(e) => return Err(C8StrError::not_utf8(e.valid_up_to())),
},
))
}
pub const unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a C8Str {
let mut len = 0;
while ptr.add(len).read() != 0 {
len += 1;
}
Self::from_str_with_nul_unchecked(str::from_utf8_unchecked(slice::from_raw_parts(
ptr as *const u8,
len + 1,
)))
}
pub const fn from_str_with_nul(str: &str) -> Result<&C8Str, C8StrError> {
if let Some(bytes) = const_nonzero_bytes_with_null_terminator(str.as_bytes()) {
if bytes.len() == str.len() {
Ok(unsafe {
Self::from_str_with_nul_unchecked(str)
})
} else {
Err(C8StrError::inner_zero(bytes.len() - 1))
}
} else {
Err(C8StrError::missing_terminator())
}
}
#[inline(always)]
pub const unsafe fn from_str_with_nul_unchecked(str: &str) -> &C8Str {
unsafe {
&*(str as *const str as *const C8Str)
}
}
pub const fn from_c_str(c_str: &CStr) -> Result<&C8Str, C8StrError> {
let bytes = c_str.to_bytes_with_nul();
let s = match str::from_utf8(bytes) {
Ok(s) => s,
Err(e) => return Err(C8StrError::not_utf8(e.valid_up_to())),
};
Ok(unsafe {
Self::from_str_with_nul_unchecked(s)
})
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.0.len() - 1
}
#[inline(always)]
pub const fn len_with_nul(&self) -> usize {
self.0.len()
}
#[inline(always)]
pub const fn count_bytes(&self) -> usize {
self.len()
}
#[inline(always)]
pub const fn as_ptr(&self) -> *const c_char {
self.0.as_ptr() as *const c_char
}
#[inline(always)]
pub const fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
#[inline(always)]
pub const fn as_bytes_with_nul(&self) -> &[u8] {
self.0.as_bytes()
}
#[inline(always)]
pub const fn to_bytes(&self) -> &[u8] {
self.as_bytes()
}
#[inline(always)]
pub const fn to_bytes_with_nul(&self) -> &[u8] {
self.as_bytes_with_nul()
}
#[inline(always)]
pub const fn as_str(&self) -> &str {
const_str_without_null_terminator(&self.0)
}
#[inline(always)]
pub const fn as_str_with_nul(&self) -> &str {
&self.0
}
#[inline(always)]
pub const fn to_str(&self) -> &str {
const_str_without_null_terminator(&self.0)
}
#[inline(always)]
pub const fn as_c_str(&self) -> &CStr {
unsafe {
CStr::from_bytes_with_nul_unchecked(self.0.as_bytes())
}
}
#[inline(always)]
pub fn as_c8_str(&self) -> &C8Str {
self
}
}
impl AsRef<CStr> for C8Str {
#[inline(always)]
fn as_ref(&self) -> &CStr {
self.as_c_str()
}
}
impl AsRef<str> for C8Str {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for C8Str {
type Target = str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Display for C8Str {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl<'a> From<&'a C8Str> for &'a CStr {
#[inline(always)]
fn from(value: &'a C8Str) -> Self {
value.as_c_str()
}
}
impl<'a> From<&'a C8Str> for &'a str {
#[inline(always)]
fn from(value: &'a C8Str) -> Self {
value.as_str()
}
}
impl<'a> TryFrom<&'a CStr> for &'a C8Str {
type Error = C8StrError;
#[inline(always)]
fn try_from(value: &'a CStr) -> Result<Self, Self::Error> {
C8Str::from_c_str(value)
}
}
impl<'a> TryFrom<&'a str> for &'a C8Str {
type Error = C8StrError;
#[inline(always)]
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
C8Str::from_str_with_nul(value)
}
}
#[cfg(feature = "alloc")]
impl alloc::borrow::ToOwned for C8Str {
type Owned = crate::C8String;
#[inline(always)]
fn to_owned(&self) -> Self::Owned {
crate::C8String::from(self)
}
}
impl Index<RangeFrom<usize>> for C8Str {
type Output = C8Str;
#[inline(always)]
fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
assert!(index.start < self.0.len());
unsafe {
C8Str::from_str_with_nul_unchecked(&self.0[index])
}
}
}
impl Index<RangeFull> for C8Str {
type Output = C8Str;
#[inline(always)]
fn index(&self, _: RangeFull) -> &Self::Output {
self
}
}