use std::ptr;
use crate::{
boxed::ZBox,
class::RegisteredClass,
convert::{FromZendObject, FromZval, IntoZendObject, IntoZval},
describe::DocComments,
error::{Error, Result},
ffi::zend_enum_get_case,
flags::{ClassFlags, DataType},
types::{ZendObject, ZendStr, Zval},
};
pub trait RegisteredEnum {
const CASES: &'static [EnumCase];
fn from_name(name: &str) -> Result<Self>
where
Self: Sized;
fn to_name(&self) -> &'static str;
}
impl<T> FromZendObject<'_> for T
where
T: RegisteredEnum,
{
fn from_zend_object(obj: &ZendObject) -> Result<Self> {
if !ClassFlags::from_bits_truncate(unsafe { (*obj.ce).ce_flags }).contains(ClassFlags::Enum)
{
return Err(Error::InvalidProperty);
}
let name = obj
.get_properties()?
.get("name")
.and_then(Zval::indirect)
.and_then(Zval::str)
.ok_or(Error::InvalidProperty)?;
T::from_name(name)
}
}
impl<T> FromZval<'_> for T
where
T: RegisteredEnum + RegisteredClass,
{
const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
fn from_zval(zval: &Zval) -> Option<Self> {
zval.object()
.and_then(|obj| Self::from_zend_object(obj).ok())
}
}
impl<T> IntoZendObject for T
where
T: RegisteredEnum + RegisteredClass,
{
fn into_zend_object(self) -> Result<ZBox<ZendObject>> {
let mut name = ZendStr::new(T::to_name(&self), false);
let variant = unsafe {
zend_enum_get_case(
ptr::from_ref(T::get_metadata().ce()).cast_mut(),
&raw mut *name,
)
};
Ok(unsafe { ZBox::from_raw(variant) })
}
}
impl<T> IntoZval for T
where
T: RegisteredEnum + RegisteredClass,
{
const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
const NULLABLE: bool = false;
fn set_zval(self, zv: &mut Zval, _persistent: bool) -> Result<()> {
let obj = self.into_zend_object()?;
zv.set_object(obj.into_raw());
Ok(())
}
}
pub struct EnumCase {
pub name: &'static str,
pub discriminant: Option<Discriminant>,
pub docs: DocComments,
}
impl EnumCase {
#[must_use]
pub fn data_type(&self) -> DataType {
match self.discriminant {
Some(Discriminant::Int(_)) => DataType::Long,
Some(Discriminant::String(_)) => DataType::String,
None => DataType::Undef,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Discriminant {
Int(i64),
String(&'static str),
}
impl TryFrom<&Discriminant> for Zval {
type Error = Error;
fn try_from(value: &Discriminant) -> Result<Self> {
match value {
Discriminant::Int(i) => i.into_zval(false),
Discriminant::String(s) => s.into_zval(false),
}
}
}
#[cfg(test)]
#[cfg(feature = "embed")]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::embed::Embed;
#[test]
fn test_zval_try_from_discriminant() {
Embed::run(|| {
let zval_int: Zval = Zval::try_from(&Discriminant::Int(42)).unwrap();
assert!(zval_int.is_long());
assert_eq!(zval_int.long().unwrap(), 42);
let zval_str: Zval = Zval::try_from(&Discriminant::String("foo")).unwrap();
assert!(zval_str.is_string());
assert_eq!(zval_str.string().unwrap().clone(), "foo");
});
}
}