ext_php_rs/builders/
enum_builder.rs1use std::{ffi::CString, ptr};
2
3use crate::{
4 builders::FunctionBuilder,
5 convert::IntoZval,
6 describe::DocComments,
7 enum_::{Discriminant, EnumCase},
8 error::Result,
9 ffi::{zend_enum_add_case, zend_register_internal_enum},
10 flags::{DataType, MethodFlags},
11 types::{ZendStr, Zval},
12 zend::{ClassEntry, FunctionEntry},
13};
14
15#[must_use]
17pub struct EnumBuilder {
18 pub(crate) name: String,
19 pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
20 pub(crate) cases: Vec<&'static EnumCase>,
21 pub(crate) datatype: DataType,
22 register: Option<fn(&'static mut ClassEntry)>,
23 pub(crate) docs: DocComments,
24}
25
26impl EnumBuilder {
27 pub fn new<T: Into<String>>(name: T) -> Self {
29 Self {
30 name: name.into(),
31 methods: Vec::default(),
32 cases: Vec::default(),
33 datatype: DataType::Undef,
34 register: None,
35 docs: DocComments::default(),
36 }
37 }
38
39 pub fn case(mut self, case: &'static EnumCase) -> Self {
45 let data_type = case.data_type();
46 assert!(
47 data_type == self.datatype || self.cases.is_empty(),
48 "Cannot add case with data type {:?} to enum with data type {:?}",
49 data_type,
50 self.datatype
51 );
52
53 self.datatype = data_type;
54 self.cases.push(case);
55
56 self
57 }
58
59 pub fn method(mut self, method: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
61 self.methods.push((method, flags));
62 self
63 }
64
65 pub fn registration(mut self, register: fn(&'static mut ClassEntry)) -> Self {
72 self.register = Some(register);
73 self
74 }
75
76 pub fn docs(mut self, docs: DocComments) -> Self {
78 self.docs = docs;
79 self
80 }
81
82 pub fn register(self) -> Result<()> {
94 let mut methods = self
95 .methods
96 .into_iter()
97 .map(|(method, flags)| {
98 method.build().map(|mut method| {
99 method.flags |= flags.bits();
100 method
101 })
102 })
103 .collect::<Result<Vec<_>>>()?;
104 methods.push(FunctionEntry::end());
105
106 let class = unsafe {
107 zend_register_internal_enum(
108 CString::new(self.name)?.as_ptr(),
109 self.datatype.as_u32().try_into()?,
110 methods.into_boxed_slice().as_ptr(),
111 )
112 };
113
114 for case in self.cases {
115 let name = ZendStr::new_interned(case.name, true);
116 let value = match &case.discriminant {
117 Some(value) => Self::create_enum_value(value)?,
118 None => ptr::null_mut(),
119 };
120 unsafe {
121 zend_enum_add_case(class, name.into_raw(), value);
122 }
123 }
124
125 if let Some(register) = self.register {
126 register(unsafe { &mut *class });
127 } else {
128 panic!("Enum was not registered with a registration function",);
129 }
130
131 Ok(())
132 }
133
134 fn create_enum_value(discriminant: &Discriminant) -> Result<*mut Zval> {
135 let value: Zval = match discriminant {
136 Discriminant::Int(i) => i.into_zval(false)?,
137 Discriminant::String(s) => s.into_zval(true)?,
138 };
139
140 let boxed_value = Box::new(value);
141 Ok(Box::into_raw(boxed_value).cast())
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::enum_::Discriminant;
149
150 const case1: EnumCase = EnumCase {
151 name: "Variant1",
152 discriminant: None,
153 docs: &[],
154 };
155 const case2: EnumCase = EnumCase {
156 name: "Variant2",
157 discriminant: Some(Discriminant::Int(42)),
158 docs: &[],
159 };
160 const case3: EnumCase = EnumCase {
161 name: "Variant3",
162 discriminant: Some(Discriminant::String("foo")),
163 docs: &[],
164 };
165
166 #[test]
167 fn test_new_enum_builder() {
168 let builder = EnumBuilder::new("MyEnum");
169 assert_eq!(builder.name, "MyEnum");
170 assert!(builder.methods.is_empty());
171 assert!(builder.cases.is_empty());
172 assert_eq!(builder.datatype, DataType::Undef);
173 assert!(builder.register.is_none());
174 }
175
176 #[test]
177 fn test_enum_case() {
178 let builder = EnumBuilder::new("MyEnum").case(&case1);
179 assert_eq!(builder.cases.len(), 1);
180 assert_eq!(builder.cases[0].name, "Variant1");
181 assert_eq!(builder.datatype, DataType::Undef);
182
183 let builder = EnumBuilder::new("MyEnum").case(&case2);
184 assert_eq!(builder.cases.len(), 1);
185 assert_eq!(builder.cases[0].name, "Variant2");
186 assert_eq!(builder.cases[0].discriminant, Some(Discriminant::Int(42)));
187 assert_eq!(builder.datatype, DataType::Long);
188
189 let builder = EnumBuilder::new("MyEnum").case(&case3);
190 assert_eq!(builder.cases.len(), 1);
191 assert_eq!(builder.cases[0].name, "Variant3");
192 assert_eq!(
193 builder.cases[0].discriminant,
194 Some(Discriminant::String("foo"))
195 );
196 assert_eq!(builder.datatype, DataType::String);
197 }
198
199 #[test]
200 #[should_panic(expected = "Cannot add case with data type Long to enum with data type Undef")]
201 fn test_enum_case_mismatch() {
202 #[allow(unused_must_use)]
203 EnumBuilder::new("MyEnum").case(&case1).case(&case2); }
205
206 const docs: DocComments = &["This is a test enum"];
207 #[test]
208 fn test_docs() {
209 let builder = EnumBuilder::new("MyEnum").docs(docs);
210 assert_eq!(builder.docs.len(), 1);
211 assert_eq!(builder.docs[0], "This is a test enum");
212 }
213}