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::ZvalConversion(DataType::ANY_OBJECT));
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_else(|| Error::InvalidProperty {
49 property: "name".to_string(),
50 })?;
51
52 T::from_name(name)
53 }
54}
55
56impl<T> FromZval<'_> for T
57where
58 T: RegisteredEnum + RegisteredClass,
59{
60 const TYPE: DataType = DataType::object(T::CLASS_NAME);
61
62 fn from_zval(zval: &Zval) -> Option<Self> {
63 zval.object()
64 .and_then(|obj| Self::from_zend_object(obj).ok())
65 }
66}
67
68impl<T> IntoZendObject for T
69where
70 T: RegisteredEnum + RegisteredClass,
71{
72 fn into_zend_object(self) -> Result<ZBox<ZendObject>> {
73 let case = zend_case(&self);
74 unsafe {
78 (*case).add_ref();
79 Ok(ZBox::from_raw(case))
80 }
81 }
82}
83
84impl<T> IntoZval for T
85where
86 T: RegisteredEnum + RegisteredClass,
87{
88 const TYPE: DataType = DataType::object(T::CLASS_NAME);
89 const NULLABLE: bool = false;
90
91 fn set_zval(self, zv: &mut Zval, _persistent: bool) -> Result<()> {
92 let case = zend_case(&self);
93 zv.set_object(unsafe { &mut *case });
97 Ok(())
98 }
99}
100
101fn zend_case<T: RegisteredEnum + RegisteredClass>(case: &T) -> *mut ZendObject {
107 let mut name = ZendStr::new(T::to_name(case), false);
108 unsafe {
111 zend_enum_get_case(
112 ptr::from_ref(T::get_metadata().ce()).cast_mut(),
113 &raw mut *name,
114 )
115 }
116}
117
118pub struct EnumCase {
120 pub name: &'static str,
122 pub discriminant: Option<Discriminant>,
124 pub docs: DocComments,
126}
127
128impl EnumCase {
129 #[must_use]
131 pub fn data_type(&self) -> DataType {
132 match self.discriminant {
133 Some(Discriminant::Int(_)) => DataType::Long,
134 Some(Discriminant::String(_)) => DataType::String,
135 None => DataType::Undef,
136 }
137 }
138}
139
140#[derive(Debug, PartialEq, Eq)]
143pub enum Discriminant {
144 Int(i64),
146 String(&'static str),
148}
149
150impl TryFrom<&Discriminant> for Zval {
151 type Error = Error;
152
153 fn try_from(value: &Discriminant) -> Result<Self> {
154 match value {
155 Discriminant::Int(i) => i.into_zval(false),
156 Discriminant::String(s) => s.into_zval(false),
157 }
158 }
159}
160
161#[cfg(test)]
162#[cfg(feature = "embed")]
163mod tests {
164 #![allow(clippy::unwrap_used)]
165 use super::*;
166 use crate::embed::Embed;
167
168 #[test]
169 fn test_zval_try_from_discriminant() {
170 Embed::run(|| {
171 let zval_int: Zval = Zval::try_from(&Discriminant::Int(42)).unwrap();
172 assert!(zval_int.is_long());
173 assert_eq!(zval_int.long().unwrap(), 42);
174
175 let zval_str: Zval = Zval::try_from(&Discriminant::String("foo")).unwrap();
176 assert!(zval_str.is_string());
177 assert_eq!(zval_str.string().unwrap().clone(), "foo");
178 });
179 }
180}