use core::str;
use crate::error::{Error, Result};
#[allow(rustdoc::invalid_rust_codeblocks)]
#[derive(Debug, Eq)]
#[repr(transparent)]
pub struct CStr {
data: [u8],
}
impl<'a> TryFrom<&'a str> for &'a CStr {
type Error = Error;
fn try_from(value: &str) -> Result<&CStr> {
match validate_cstr(value) {
Some(cs) => Ok(cs),
None => Err(Error::malformed_bytes(format!(
"cstring with interior null: {:?}",
value,
))),
}
}
}
impl CStr {
#[cfg(feature = "serde")]
pub(crate) fn from_str(value: &str) -> Result<&CStr> {
value.try_into()
}
const fn from_str_unchecked(value: &str) -> &Self {
unsafe { &*(value.as_bytes() as *const [u8] as *const CStr) }
}
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.data) }
}
pub fn len(&self) -> usize {
self.as_str().len()
}
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
pub fn to_lowercase(&self) -> CString {
CString::from_string_unchecked(self.as_str().to_lowercase())
}
pub fn to_uppercase(&self) -> CString {
CString::from_string_unchecked(self.as_str().to_uppercase())
}
pub(crate) fn append_to(&self, buf: &mut Vec<u8>) {
buf.extend(&self.data);
buf.push(0);
}
}
impl PartialEq for CStr {
fn eq(&self, other: &CStr) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<str> for CStr {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<CString> for CStr {
fn eq(&self, other: &CString) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<String> for CStr {
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl std::hash::Hash for CStr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl Ord for CStr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialOrd for CStr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::fmt::Display for CStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl std::borrow::ToOwned for CStr {
type Owned = CString;
fn to_owned(&self) -> Self::Owned {
self.into()
}
}
impl AsRef<CStr> for CStr {
fn as_ref(&self) -> &CStr {
self
}
}
impl AsRef<str> for CStr {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> From<&'a CStr> for &'a str {
fn from(value: &'a CStr) -> Self {
value.as_str()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for &CStr {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
#[doc(hidden)]
#[diagnostic::on_unimplemented(message = "the string literal contains a zero byte")]
pub trait ValidCStr {}
#[doc(hidden)]
pub struct IsValidCStr<const VALID: bool>;
#[doc(hidden)]
impl ValidCStr for IsValidCStr<true> {}
#[doc(hidden)]
pub const fn validate_cstr(text: &str) -> Option<&CStr> {
let bytes = text.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == 0 {
return None;
}
i += 1;
}
Some(CStr::from_str_unchecked(text))
}
#[doc(hidden)]
pub const fn assert_valid_cstr<T: ValidCStr>() {}
#[allow(rustdoc::invalid_rust_codeblocks)]
#[macro_export]
macro_rules! cstr {
($text:literal) => {{
const VALIDATED: Option<&$crate::raw::CStr> = $crate::raw::validate_cstr($text);
const VALID: bool = VALIDATED.is_some();
$crate::raw::assert_valid_cstr::<$crate::raw::IsValidCStr<VALID>>();
VALIDATED.unwrap()
}};
}
pub use cstr;
#[derive(Clone, Eq)]
#[repr(transparent)]
pub struct CString {
data: String,
}
impl TryFrom<String> for CString {
type Error = Error;
fn try_from(data: String) -> Result<Self> {
let _: &CStr = data.as_str().try_into()?;
Ok(Self { data })
}
}
impl TryFrom<&str> for CString {
type Error = Error;
fn try_from(data: &str) -> Result<Self> {
let cs: &CStr = data.try_into()?;
Ok(cs.into())
}
}
impl CString {
pub(crate) fn from_string_unchecked(data: String) -> Self {
Self { data }
}
pub fn into_string(self) -> String {
self.data
}
pub fn as_str(&self) -> &str {
self.as_ref().as_str()
}
}
impl From<&CStr> for CString {
fn from(value: &CStr) -> Self {
Self {
data: value.as_str().into(),
}
}
}
impl AsRef<CStr> for CString {
fn as_ref(&self) -> &CStr {
CStr::from_str_unchecked(self.data.as_str())
}
}
impl From<CString> for String {
fn from(value: CString) -> Self {
value.into_string()
}
}
impl std::ops::Deref for CString {
type Target = CStr;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl std::borrow::Borrow<CStr> for CString {
fn borrow(&self) -> &CStr {
self.as_ref()
}
}
impl std::fmt::Debug for CString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.data.fmt(f)
}
}
impl std::fmt::Display for CString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.data.fmt(f)
}
}
impl PartialEq for CString {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl PartialEq<CStr> for CString {
fn eq(&self, other: &CStr) -> bool {
self.data.as_str() == other.as_str()
}
}
impl PartialEq<String> for CString {
fn eq(&self, other: &String) -> bool {
&self.data == other
}
}
impl PartialEq<str> for CString {
fn eq(&self, other: &str) -> bool {
self.data.as_str() == other
}
}
impl std::hash::Hash for CString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for CString {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.data.serialize(serializer)
}
}