1use std::ptr;
4
5use crate::{
6 boxed::ZBox,
7 class::RegisteredClass,
8 convert::{FromZendObject, FromZval, IntoZendObject, IntoZval},
9 describe::DocComments,
10 error::{Error, Result},
11 ffi::zend_enum_get_case,
12 flags::{ClassFlags, DataType},
13 types::{ZendObject, ZendStr, Zval},
14};
15
16pub trait RegisteredEnum {
18 const CASES: &'static [EnumCase];
20
21 fn from_name(name: &str) -> Result<Self>
26 where
27 Self: Sized;
28
29 fn to_name(&self) -> &'static str;
31}
32
33impl<T> FromZendObject<'_> for T
34where
35 T: RegisteredEnum,
36{
37 fn from_zend_object(obj: &ZendObject) -> Result<Self> {
38 if !ClassFlags::from_bits_truncate(unsafe { (*obj.ce).ce_flags }).contains(ClassFlags::Enum)
39 {
40 return Err(Error::InvalidProperty);
41 }
42
43 let name = obj
44 .get_properties()?
45 .get("name")
46 .and_then(Zval::indirect)
47 .and_then(Zval::str)
48 .ok_or(Error::InvalidProperty)?;
49
50 T::from_name(name)
51 }
52}
53
54impl<T> FromZval<'_> for T
55where
56 T: RegisteredEnum + RegisteredClass,
57{
58 const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
59
60 fn from_zval(zval: &Zval) -> Option<Self> {
61 zval.object()
62 .and_then(|obj| Self::from_zend_object(obj).ok())
63 }
64}
65
66impl<T> IntoZendObject for T
67where
68 T: RegisteredEnum + RegisteredClass,
69{
70 fn into_zend_object(self) -> Result<ZBox<ZendObject>> {
71 let mut name = ZendStr::new(T::to_name(&self), false);
72 let variant = unsafe {
73 zend_enum_get_case(
74 ptr::from_ref(T::get_metadata().ce()).cast_mut(),
75 &raw mut *name,
76 )
77 };
78
79 Ok(unsafe { ZBox::from_raw(variant) })
80 }
81}
82
83impl<T> IntoZval for T
84where
85 T: RegisteredEnum + RegisteredClass,
86{
87 const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
88 const NULLABLE: bool = false;
89
90 fn set_zval(self, zv: &mut Zval, _persistent: bool) -> Result<()> {
91 let obj = self.into_zend_object()?;
92 zv.set_object(obj.into_raw());
93 Ok(())
94 }
95}
96pub struct EnumCase {
110 pub name: &'static str,
112 pub discriminant: Option<Discriminant>,
114 pub docs: DocComments,
116}
117
118impl EnumCase {
119 #[must_use]
121 pub fn data_type(&self) -> DataType {
122 match self.discriminant {
123 Some(Discriminant::Int(_)) => DataType::Long,
124 Some(Discriminant::String(_)) => DataType::String,
125 None => DataType::Undef,
126 }
127 }
128}
129
130#[derive(Debug, PartialEq, Eq)]
133pub enum Discriminant {
134 Int(i64),
136 String(&'static str),
138}
139
140impl TryFrom<&Discriminant> for Zval {
141 type Error = Error;
142
143 fn try_from(value: &Discriminant) -> Result<Self> {
144 match value {
145 Discriminant::Int(i) => i.into_zval(false),
146 Discriminant::String(s) => s.into_zval(false),
147 }
148 }
149}
150
151#[cfg(test)]
152#[cfg(feature = "embed")]
153mod tests {
154 #![allow(clippy::unwrap_used)]
155 use super::*;
156 use crate::embed::Embed;
157
158 #[test]
159 fn test_zval_try_from_discriminant() {
160 Embed::run(|| {
161 let zval_int: Zval = Zval::try_from(&Discriminant::Int(42)).unwrap();
162 assert!(zval_int.is_long());
163 assert_eq!(zval_int.long().unwrap(), 42);
164
165 let zval_str: Zval = Zval::try_from(&Discriminant::String("foo")).unwrap();
166 assert!(zval_str.is_string());
167 assert_eq!(zval_str.string().unwrap().clone(), "foo");
168 });
169 }
170}