use std::{convert::TryFrom, ffi::CString, fmt::Debug};
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 struct ZendString {
ptr: *mut zend_string,
free: bool,
}
impl ZendString {
pub fn new(str: &str, persistent: bool) -> Result<Self> {
Ok(Self {
ptr: unsafe {
ext_php_rs_zend_string_init(CString::new(str)?.as_ptr(), str.len() as _, persistent)
},
free: true,
})
}
#[allow(clippy::unwrap_used)]
pub fn new_interned(str_: &str) -> Result<Self> {
Ok(Self {
ptr: unsafe {
zend_string_init_interned.unwrap()(
CString::new(str_)?.as_ptr(),
str_.len() as _,
true,
)
},
free: true,
})
}
pub unsafe fn from_ptr(ptr: *mut zend_string, free: bool) -> Result<Self> {
if ptr.is_null() {
return Err(Error::InvalidPointer);
}
Ok(Self { ptr, free })
}
pub fn release(mut self) -> *mut zend_string {
self.free = false;
self.ptr
}
pub fn as_str(&self) -> Option<&str> {
unsafe {
let ptr = self.ptr.as_ref()?;
let slice = std::slice::from_raw_parts(ptr.val.as_ptr() as *const u8, ptr.len as _);
std::str::from_utf8(slice).ok()
}
}
pub(crate) fn borrow_ptr(&self) -> *mut zend_string {
self.ptr
}
}
impl Drop for ZendString {
fn drop(&mut self) {
if self.free && !self.ptr.is_null() {
unsafe { ext_php_rs_zend_string_release(self.ptr) };
}
}
}
impl TryFrom<String> for ZendString {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
ZendString::new(value.as_str(), false)
}
}
impl TryFrom<ZendString> for String {
type Error = Error;
fn try_from(value: ZendString) -> Result<Self> {
<String as TryFrom<&ZendString>>::try_from(&value)
}
}
impl TryFrom<&ZendString> for String {
type Error = Error;
fn try_from(s: &ZendString) -> Result<Self> {
s.as_str()
.map(|s| s.to_string())
.ok_or(Error::InvalidPointer)
}
}
impl Debug for ZendString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.as_str() {
Some(str) => str.fmt(f),
None => Option::<()>::None.fmt(f),
}
}
}