1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Fields};
4
5#[proc_macro_derive(UbfStruct, attributes(ubf))]
26pub fn derive_ubf_struct(input: TokenStream) -> TokenStream {
27 let input = parse_macro_input!(input as DeriveInput);
28
29 let name = &input.ident;
30
31 let fields = match &input.data {
33 Data::Struct(data) => match &data.fields {
34 Fields::Named(fields) => &fields.named,
35 _ => panic!("UbfStruct only supports named fields"),
36 },
37 _ => panic!("UbfStruct only supports structs"),
38 };
39
40 let mut from_ubf_fields = Vec::new();
42 let mut to_ubf_fields = Vec::new();
43
44 for field in fields {
45 let field_name = field.ident.as_ref().unwrap();
46 let field_type = &field.ty;
47
48 let mut field_expr: Option<proc_macro2::TokenStream> = None;
50 let mut default_value: Option<String> = None;
51
52 for attr in &field.attrs {
53 if attr.path().is_ident("ubf") {
54 let tokens_str = attr
56 .meta
57 .require_list()
58 .expect("Expected meta list")
59 .tokens
60 .to_string();
61
62 for part in tokens_str.split(',') {
64 let part = part.trim();
65
66 if part.starts_with("field") {
67 if let Some(eq_pos) = part.find('=') {
69 let value_str = part[eq_pos + 1..].trim();
70 field_expr =
72 Some(value_str.parse().expect("Failed to parse field expression"));
73 }
74 } else if part.starts_with("default") {
75 if let Some(eq_pos) = part.find('=') {
77 let value_str = part[eq_pos + 1..].trim();
78 default_value = Some(value_str.trim_matches('"').to_string());
79 }
80 }
81 }
82 }
83 }
84
85 let fid = field_expr.unwrap_or_else(|| {
86 panic!(
87 "Field {} must have #[ubf(field = ...)] attribute",
88 field_name
89 )
90 });
91
92 let field_getter = generate_field_getter(
94 field_name,
95 field_type,
96 fid.clone(),
97 default_value.as_deref(),
98 );
99 from_ubf_fields.push(field_getter);
100
101 let field_setter = generate_field_setter(field_name, field_type, fid);
103 to_ubf_fields.push(field_setter);
104 }
105
106 let field_names: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
107
108 let expanded = quote! {
110 impl ::endurox_sys::ubf_struct::UbfStruct for #name {
111 fn from_ubf(buf: &::endurox_sys::ubf::UbfBuffer) -> Result<Self, ::endurox_sys::ubf_struct::UbfError> {
112 #(#from_ubf_fields)*
113
114 Ok(Self {
115 #(#field_names),*
116 })
117 }
118
119 fn to_ubf(&self) -> Result<::endurox_sys::ubf::UbfBuffer, ::endurox_sys::ubf_struct::UbfError> {
120 let mut buf = ::endurox_sys::ubf::UbfBuffer::new(2048)
121 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::AllocationError(e))?;
122 self.update_ubf(&mut buf)?;
123 Ok(buf)
124 }
125
126 fn update_ubf(&self, buf: &mut ::endurox_sys::ubf::UbfBuffer) -> Result<(), ::endurox_sys::ubf_struct::UbfError> {
127 #(#to_ubf_fields)*
128 Ok(())
129 }
130 }
131 };
132
133 TokenStream::from(expanded)
134}
135
136fn generate_field_getter(
137 field_name: &syn::Ident,
138 field_type: &syn::Type,
139 field_id: proc_macro2::TokenStream,
140 default_value: Option<&str>,
141) -> proc_macro2::TokenStream {
142 let type_str = quote!(#field_type).to_string();
143
144 let is_option = type_str.starts_with("Option <");
146
147 if is_option {
148 if type_str.contains("String") {
150 quote! {
152 let #field_name = buf.get_string(#field_id, 0).ok();
153 }
154 } else if type_str.contains("i64") || type_str.contains("i32") || type_str.contains("long")
155 {
156 quote! {
158 let #field_name = buf.get_long(#field_id, 0).ok().map(|v| v as _);
159 }
160 } else if type_str.contains("f64")
161 || type_str.contains("f32")
162 || type_str.contains("double")
163 {
164 quote! {
166 let #field_name = buf.get_double(#field_id, 0).ok().map(|v| v as _);
167 }
168 } else if type_str.contains("bool") {
169 quote! {
171 let #field_name = if buf.is_present(#field_id, 0) { Some(true) } else { None };
172 }
173 } else {
174 let inner_type_str = type_str
177 .trim_start_matches("Option <")
178 .trim_end_matches(">")
179 .trim();
180 let inner_type: proc_macro2::TokenStream =
181 inner_type_str.parse().expect("Failed to parse inner type");
182
183 quote! {
184 let #field_name = <#inner_type as ::endurox_sys::ubf_struct::UbfStruct>::from_ubf(buf).ok();
185 }
186 }
187 } else {
188 if type_str.contains("String") {
190 if let Some(default) = default_value {
191 quote! {
192 let #field_name = buf.get_string(#field_id, 0)
193 .unwrap_or_else(|_| #default.to_string());
194 }
195 } else {
196 quote! {
197 let #field_name = buf.get_string(#field_id, 0)
198 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::FieldNotFound(
199 format!("Field {} ({}): {}", stringify!(#field_name), #field_id, e)
200 ))?;
201 }
202 }
203 } else if type_str.contains("i64") || type_str.contains("i32") || type_str.contains("long")
204 {
205 quote! {
206 let #field_name = buf.get_long(#field_id, 0)
207 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::FieldNotFound(
208 format!("Field {} ({}): {}", stringify!(#field_name), #field_id, e)
209 ))? as #field_type;
210 }
211 } else if type_str.contains("f64")
212 || type_str.contains("f32")
213 || type_str.contains("double")
214 {
215 quote! {
216 let #field_name = buf.get_double(#field_id, 0)
217 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::FieldNotFound(
218 format!("Field {} ({}): {}", stringify!(#field_name), #field_id, e)
219 ))? as #field_type;
220 }
221 } else if type_str.contains("bool") {
222 quote! {
223 let #field_name = buf.is_present(#field_id, 0);
224 }
225 } else {
226 quote! {
228 let #field_name = <#field_type as ::endurox_sys::ubf_struct::UbfStruct>::from_ubf(buf)?;
229 }
230 }
231 }
232}
233
234fn generate_field_setter(
235 field_name: &syn::Ident,
236 field_type: &syn::Type,
237 field_id: proc_macro2::TokenStream,
238) -> proc_macro2::TokenStream {
239 let type_str = quote!(#field_type).to_string();
240
241 let is_option = type_str.starts_with("Option <");
243
244 if is_option {
245 if type_str.contains("String") {
247 quote! {
249 if let Some(ref value) = self.#field_name {
250 buf.add_string(#field_id, value)
251 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
252 format!("Field {}: {}", stringify!(#field_name), e)
253 ))?;
254 }
255 }
256 } else if type_str.contains("i64") || type_str.contains("i32") || type_str.contains("long")
257 {
258 quote! {
260 if let Some(value) = self.#field_name {
261 buf.add_long(#field_id, value as i64)
262 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
263 format!("Field {}: {}", stringify!(#field_name), e)
264 ))?;
265 }
266 }
267 } else if type_str.contains("f64")
268 || type_str.contains("f32")
269 || type_str.contains("double")
270 {
271 quote! {
273 if let Some(value) = self.#field_name {
274 buf.add_double(#field_id, value as f64)
275 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
276 format!("Field {}: {}", stringify!(#field_name), e)
277 ))?;
278 }
279 }
280 } else if type_str.contains("bool") {
281 quote! {
283 if let Some(value) = self.#field_name {
284 if value {
285 buf.add_long(#field_id, 1)
286 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
287 format!("Field {}: {}", stringify!(#field_name), e)
288 ))?;
289 }
290 }
291 }
292 } else {
293 quote! {
295 if let Some(ref nested) = self.#field_name {
296 nested.update_ubf(buf)?;
297 }
298 }
299 }
300 } else {
301 if type_str.contains("String") {
303 quote! {
304 buf.add_string(#field_id, &self.#field_name)
305 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
306 format!("Field {}: {}", stringify!(#field_name), e)
307 ))?;
308 }
309 } else if type_str.contains("i64") || type_str.contains("i32") || type_str.contains("long")
310 {
311 quote! {
312 buf.add_long(#field_id, self.#field_name as i64)
313 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
314 format!("Field {}: {}", stringify!(#field_name), e)
315 ))?;
316 }
317 } else if type_str.contains("f64")
318 || type_str.contains("f32")
319 || type_str.contains("double")
320 {
321 quote! {
322 buf.add_double(#field_id, self.#field_name as f64)
323 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
324 format!("Field {}: {}", stringify!(#field_name), e)
325 ))?;
326 }
327 } else if type_str.contains("bool") {
328 quote! {
329 if self.#field_name {
330 buf.add_long(#field_id, 1)
331 .map_err(|e| ::endurox_sys::ubf_struct::UbfError::TypeError(
332 format!("Field {}: {}", stringify!(#field_name), e)
333 ))?;
334 }
335 }
336 } else {
337 quote! {
339 self.#field_name.update_ubf(buf)?;
340 }
341 }
342 }
343}