use std::{
borrow::{Borrow, Cow},
convert::TryFrom,
ffi::{CStr, CString},
fmt::Debug,
mem::ManuallyDrop,
ops::Deref,
ptr::NonNull,
slice,
};
use parking_lot::{
lock_api::{Mutex, RawMutex},
RawMutex as RawMutexStruct,
};
use crate::{
bindings::{
ext_php_rs_zend_string_init, ext_php_rs_zend_string_release, zend_string,
zend_string_init_interned,
},
errors::{Error, Result},
};
pub type ZendStr = zend_string;
#[allow(clippy::len_without_is_empty)]
impl ZendStr {
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_c_str(&self) -> &CStr {
unsafe {
let slice = slice::from_raw_parts(self.val.as_ptr() as *const u8, self.len());
CStr::from_bytes_with_nul_unchecked(slice)
}
}
pub fn as_str(&self) -> Option<&str> {
self.as_c_str().to_str().ok()
}
}
impl Debug for ZendStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_c_str().fmt(f)
}
}
impl ToOwned for ZendStr {
type Owned = ZendString;
fn to_owned(&self) -> Self::Owned {
Self::Owned::from_c_str(self.as_c_str(), false)
}
}
impl PartialEq for ZendStr {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_c_str().eq(other.as_c_str())
}
}
impl<'a> From<&'a ZendStr> for &'a CStr {
fn from(value: &'a ZendStr) -> Self {
value.as_c_str()
}
}
impl<'a> TryFrom<&'a ZendStr> for &'a str {
type Error = Error;
fn try_from(value: &'a ZendStr) -> Result<Self> {
value.as_str().ok_or(Error::InvalidCString)
}
}
impl<'a> TryFrom<&ZendStr> for String {
type Error = Error;
fn try_from(value: &ZendStr) -> Result<Self> {
value
.as_str()
.map(|s| s.to_string())
.ok_or(Error::InvalidCString)
}
}
impl<'a> From<&'a ZendStr> for Cow<'a, ZendStr> {
fn from(value: &'a ZendStr) -> Self {
Cow::Borrowed(value)
}
}
pub struct ZendString {
inner: NonNull<ZendStr>,
}
static INTERNED_LOCK: Mutex<RawMutexStruct, ()> = Mutex::const_new(RawMutex::INIT, ());
impl ZendString {
pub fn new(str: &str, persistent: bool) -> Result<Self> {
Ok(Self::from_c_str(&CString::new(str)?, persistent))
}
pub fn from_c_str(str: &CStr, persistent: bool) -> Self {
let ptr = unsafe {
ext_php_rs_zend_string_init(str.as_ptr(), str.to_bytes().len() as _, persistent)
};
Self {
inner: NonNull::new(ptr).expect("Failed to allocate for Zend string"),
}
}
pub fn new_interned(str: &str, persistent: bool) -> Result<Self> {
Ok(Self::interned_from_c_str(&CString::new(str)?, persistent))
}
pub fn interned_from_c_str(str: &CStr, persistent: bool) -> Self {
let _lock = INTERNED_LOCK.lock();
let ptr = unsafe {
zend_string_init_interned.expect("`zend_string_init_interned` not ready")(
str.as_ptr(),
str.to_bytes().len() as _,
persistent,
)
};
Self {
inner: NonNull::new(ptr).expect("Failed to allocate for Zend string"),
}
}
pub fn as_zend_str(&self) -> &ZendStr {
unsafe { self.inner.as_ref() }
}
pub(crate) fn as_mut_zend_str(&mut self) -> &mut ZendStr {
unsafe { self.inner.as_mut() }
}
pub fn into_inner(self) -> *mut ZendStr {
let this = ManuallyDrop::new(self);
this.inner.as_ptr()
}
}
impl Drop for ZendString {
fn drop(&mut self) {
unsafe { ext_php_rs_zend_string_release(self.inner.as_ptr()) };
}
}
impl Deref for ZendString {
type Target = ZendStr;
fn deref(&self) -> &Self::Target {
self.as_zend_str()
}
}
impl Debug for ZendString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_zend_str().fmt(f)
}
}
impl Borrow<ZendStr> for ZendString {
#[inline]
fn borrow(&self) -> &ZendStr {
self.deref()
}
}
impl AsRef<ZendStr> for ZendString {
#[inline]
fn as_ref(&self) -> &ZendStr {
self
}
}
impl From<&CStr> for ZendString {
fn from(value: &CStr) -> Self {
Self::from_c_str(value, false)
}
}
impl From<CString> for ZendString {
fn from(value: CString) -> Self {
Self::from_c_str(&value, false)
}
}
impl TryFrom<&str> for ZendString {
type Error = Error;
fn try_from(value: &str) -> Result<Self> {
Self::new(value, false)
}
}
impl TryFrom<String> for ZendString {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
Self::new(value.as_str(), false)
}
}
impl From<ZendString> for Cow<'_, ZendStr> {
fn from(value: ZendString) -> Self {
Cow::Owned(value)
}
}
impl From<Cow<'_, ZendStr>> for ZendString {
fn from(value: Cow<'_, ZendStr>) -> Self {
value.into_owned()
}
}