use std::{
borrow::Cow,
convert::TryFrom,
ffi::{CStr, CString},
fmt::Debug,
slice,
};
use parking_lot::{const_mutex, Mutex};
use crate::{
boxed::{ZBox, ZBoxable},
convert::{FromZval, IntoZval},
error::{Error, Result},
ffi::{
ext_php_rs_zend_string_init, ext_php_rs_zend_string_release, zend_string,
zend_string_init_interned,
},
flags::DataType,
macros::try_from_zval,
types::Zval,
};
pub type ZendStr = zend_string;
static INTERNED_LOCK: Mutex<()> = const_mutex(());
#[allow(clippy::len_without_is_empty)]
impl ZendStr {
pub fn new(str: &str, persistent: bool) -> Result<ZBox<Self>> {
Ok(Self::from_c_str(&CString::new(str)?, persistent))
}
pub fn from_c_str(str: &CStr, persistent: bool) -> ZBox<Self> {
unsafe {
let ptr =
ext_php_rs_zend_string_init(str.as_ptr(), str.to_bytes().len() as _, persistent);
ZBox::from_raw(
ptr.as_mut()
.expect("Failed to allocate memory for new Zend string"),
)
}
}
pub fn new_interned(str: &str, persistent: bool) -> Result<ZBox<Self>> {
Ok(Self::interned_from_c_str(&CString::new(str)?, persistent))
}
pub fn interned_from_c_str(str: &CStr, persistent: bool) -> ZBox<Self> {
let _lock = INTERNED_LOCK.lock();
unsafe {
let ptr = zend_string_init_interned.expect("`zend_string_init_interned` not ready")(
str.as_ptr(),
str.to_bytes().len() as _,
persistent,
);
ZBox::from_raw(
ptr.as_mut()
.expect("Failed to allocate memory for new Zend string"),
)
}
}
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() + 1);
CStr::from_bytes_with_nul_unchecked(slice)
}
}
pub fn as_str(&self) -> Option<&str> {
self.as_c_str().to_str().ok()
}
}
unsafe impl ZBoxable for ZendStr {
fn free(&mut self) {
unsafe { ext_php_rs_zend_string_release(self) };
}
}
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 = ZBox<ZendStr>;
fn to_owned(&self) -> Self::Owned {
Self::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)
}
}
impl From<&CStr> for ZBox<ZendStr> {
fn from(value: &CStr) -> Self {
ZendStr::from_c_str(value, false)
}
}
impl From<CString> for ZBox<ZendStr> {
fn from(value: CString) -> Self {
ZendStr::from_c_str(&value, false)
}
}
impl TryFrom<&str> for ZBox<ZendStr> {
type Error = Error;
fn try_from(value: &str) -> Result<Self> {
ZendStr::new(value, false)
}
}
impl TryFrom<String> for ZBox<ZendStr> {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
ZendStr::new(value.as_str(), false)
}
}
impl From<ZBox<ZendStr>> for Cow<'_, ZendStr> {
fn from(value: ZBox<ZendStr>) -> Self {
Cow::Owned(value)
}
}
impl From<Cow<'_, ZendStr>> for ZBox<ZendStr> {
fn from(value: Cow<'_, ZendStr>) -> Self {
value.into_owned()
}
}
macro_rules! try_into_zval_str {
($type: ty) => {
impl TryFrom<$type> for Zval {
type Error = Error;
fn try_from(value: $type) -> Result<Self> {
let mut zv = Self::new();
zv.set_string(&value, false)?;
Ok(zv)
}
}
impl IntoZval for $type {
const TYPE: DataType = DataType::String;
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
zv.set_string(&self, persistent)
}
}
};
}
try_into_zval_str!(String);
try_into_zval_str!(&str);
try_from_zval!(String, string, String);
impl<'a> FromZval<'a> for &'a str {
const TYPE: DataType = DataType::String;
fn from_zval(zval: &'a Zval) -> Option<Self> {
zval.str()
}
}