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