srad_macros/
lib.rs

1use std::collections::HashSet;
2
3use quote::{quote, ToTokens};
4use syn::{parse_macro_input, Attribute, Data, DataEnum, DataUnion, DeriveInput, Error};
5
6#[derive(Default)]
7struct TemplateFieldAttributes {
8    skip: bool,
9    parameter: bool,
10    rename: Option<String>,
11    default: Option<proc_macro2::TokenStream>,
12}
13
14fn parse_builder_attributes(attrs: &[Attribute]) -> syn::Result<TemplateFieldAttributes> {
15    let mut field_attrs = TemplateFieldAttributes::default();
16
17    for attr in attrs {
18        if !attr.path().is_ident("template") {
19            continue;
20        }
21
22        attr.parse_nested_meta(|meta| {
23            if meta.path.is_ident("skip") {
24                field_attrs.skip = true;
25                return Ok(());
26            }
27
28            if meta.path.is_ident("parameter") {
29                field_attrs.parameter = true;
30                return Ok(());
31            }
32
33            if meta.path.is_ident("default") {
34                let value = meta.value()?;
35                let expr: syn::Expr = value.parse()?;
36                field_attrs.default = Some(expr.into_token_stream());
37                return Ok(());
38            }
39
40            if meta.path.is_ident("rename") {
41                let value = meta.value()?;
42                let lit_string: syn::LitStr = value.parse()?;
43                field_attrs.rename = Some(lit_string.value());
44                return Ok(());
45            }
46
47            Err(meta.error("Unrecognised template attribute"))
48        })?;
49    }
50
51    Ok(field_attrs)
52}
53
54fn try_template(input: DeriveInput) -> syn::Result<proc_macro::TokenStream> {
55    let data_struct = match input.data {
56        Data::Struct(variant_data) => variant_data,
57        Data::Enum(DataEnum { enum_token, .. }) => {
58            return Err(Error::new_spanned(
59                enum_token,
60                "Template can not be derived for an Enum",
61            ))
62        }
63        Data::Union(DataUnion { union_token, .. }) => {
64            return Err(Error::new_spanned(
65                union_token,
66                "Template can not be derived for a Union",
67            ))
68        }
69    };
70
71    let fields = match data_struct.fields {
72        syn::Fields::Named(fields_named) => fields_named,
73        syn::Fields::Unnamed(fields_unnamed) => {
74            return Err(Error::new_spanned(
75                fields_unnamed,
76                "Template can not be derived for a tuple struct with Unnamed Fields",
77            ))
78        }
79        syn::Fields::Unit => {
80            return Err(Error::new_spanned(
81                &data_struct.fields,
82                "Template does not support unit structs",
83            ))
84        }
85    };
86
87    let mut unique_names = HashSet::with_capacity(fields.named.len());
88
89    let mut definition_metrics = Vec::new();
90    let mut definition_parameters = Vec::new();
91
92    let mut instance_metrics = Vec::new();
93    let mut instance_parameters = Vec::new();
94
95    let mut from_difference_metrics = Vec::new();
96    let mut from_difference_parameters = Vec::new();
97
98    let mut from_instance_defines = Vec::new();
99    let mut from_instance_metric_match = Vec::new();
100    let mut from_instance_parameter_match = Vec::new();
101    let mut from_instance_init_struct = Vec::new();
102
103    let mut update_from_instance_init_defines = Vec::new();
104    let mut update_from_instance_metric_match = Vec::new();
105    let mut update_from_instance_parameter_match = Vec::new();
106    let mut update_from_instance_update_defines = Vec::new();
107
108    for field in &fields.named {
109        let attrs = parse_builder_attributes(&field.attrs)?;
110        let field_ident = &field.ident;
111        let ty = field.ty.clone();
112        let name = if let Some(rename) = attrs.rename {
113            rename
114        } else {
115            field.ident.as_ref().unwrap().to_string()
116        };
117
118        let default = if let Some(default) = attrs.default {
119            default
120        } else {
121            quote! { <#ty as Default>::default() }
122        };
123
124        from_instance_defines.push(quote! {
125            let mut #field_ident = #default;
126        });
127
128        from_instance_init_struct.push(quote! {
129            #field_ident,
130        });
131
132        if attrs.skip {
133            continue;
134        }
135
136        if !unique_names.insert(name.clone()) {
137            return Err(Error::new_spanned(
138                field.ident.clone(),
139                format!("Duplicate name provided - {name}"),
140            ));
141        }
142
143        update_from_instance_init_defines.push(quote! {
144            let mut #field_ident = None;
145        });
146
147        update_from_instance_update_defines.push(quote! {
148            if let Some(v) = #field_ident {
149                self.#field_ident = v;
150            }
151        });
152
153        if attrs.parameter {
154            definition_parameters.push(quote! {
155                ::srad::types::TemplateParameter::new_template_parameter::<#ty>(
156                    #name.to_string(),
157                    #default
158                )
159            });
160
161            instance_parameters.push(quote! {
162                ::srad::types::TemplateParameter::new_template_parameter(
163                    #name.to_string(),
164                    self.#field_ident.clone()
165                )
166            });
167
168            from_instance_parameter_match.push(
169                quote! {
170                    #name => {
171                        #field_ident = match ::srad::types::TemplateParameterValue::try_from_template_parameter_value(
172                            parameter.value.map(::srad::types::ParameterValue::from)
173                        ) {
174                            Ok(v) => v,
175                            Err(_) => return Err(::srad::types::TemplateError::InvalidParameterValue(#name.to_string()))
176                        }
177                    },
178                }
179            );
180
181            from_difference_parameters.push(quote! {
182                if self.#field_ident != other.#field_ident {
183                    parameters.push(
184                        ::srad::types::TemplateParameter::new_template_parameter(
185                            #name.to_string(),
186                            self.#field_ident.clone()
187                        )
188                    )
189                }
190            });
191
192            update_from_instance_parameter_match.push(
193                quote! {
194                    #name => {
195                        #field_ident = match ::srad::types::TemplateParameterValue::try_from_template_parameter_value(
196                            parameter.value.map(::srad::types::ParameterValue::from)
197                        ) {
198                            Ok(v) => Some(v),
199                            Err(_) => return Err(::srad::types::TemplateError::InvalidParameterValue(#name.to_string()))
200                        };
201                    },
202                }
203            );
204        } else {
205            definition_metrics.push(quote! {
206                ::srad::types::TemplateMetric::new_template_metric::<#ty>(
207                    #name.to_string(),
208                    #default
209                )
210            });
211
212            instance_metrics.push(quote! {
213                ::srad::types::TemplateMetric::new_template_metric(
214                    #name.to_string(),
215                    self.#field_ident.clone()
216                )
217            });
218
219            from_instance_metric_match.push(
220                quote! {
221                    #name => {
222                        #field_ident = match ::srad::types::TemplateMetricValue::try_from_template_metric_value(
223                            metric.value.map(::srad::types::MetricValue::from)
224                        ) {
225                            Ok(v) => v,
226                            Err(e) => return Err(::srad::types::TemplateError::InvalidMetricValue(#name.to_string()))
227                        }
228                    },
229                }
230            );
231
232            from_difference_metrics.push(
233                quote! {
234                    if let Some(value) = ::srad::types::TemplateMetricValuePartial::metric_value_if_ne(&self.#field_ident, &other.#field_ident) {
235                        metrics.push(
236                            ::srad::types::TemplateMetric::new_template_metric_raw(
237                                #name.to_string(),
238                                <#ty as ::srad::types::traits::HasDataType>::default_datatype(),
239                                value
240                            )
241                        )
242                    }
243                }
244            );
245
246            update_from_instance_metric_match.push(
247                quote! {
248                    #name => {
249                        let mut tmp = self.#field_ident.clone();
250                        if let Err(_) = ::srad::types::TemplateMetricValuePartial::try_update_from_metric_value(
251                            &mut tmp,
252                            metric.value.map(::srad::types::MetricValue::from)
253                        ) {
254                            return Err(::srad::types::TemplateError::InvalidMetricValue(#name.to_string()))
255                        }
256                        #field_ident = Some(tmp);
257                    },
258                }
259            );
260        }
261    }
262
263    if unique_names.is_empty() {
264        return Err(Error::new_spanned(
265            fields,
266            "At least one field must be provided",
267        ));
268    }
269
270    let type_name = &input.ident;
271
272    Ok(quote!{
273
274        impl ::srad::types::TemplateMetricValue for #type_name {
275            type Error = ();
276            fn to_template_metric_value(self) -> Option<::srad::types::MetricValue> {
277                Some(::srad::types::Template::template_instance(&self).into())
278            }
279            fn try_from_template_metric_value(value: Option<::srad::types::MetricValue>) -> Result<Self, Self::Error> where Self: Sized {
280                match value {
281                    Some(value) => {
282                        Self::try_from(
283                            ::srad::types::TemplateInstance::try_from(value)
284                            .map_err(|_|())?
285                        )
286                        .map_err(|_|())
287                    },
288                    None => Err(()),
289                }
290            }
291        }
292
293        impl ::srad::types::TemplateMetricValuePartial for #type_name {
294            type Error = ();
295            fn metric_value_if_ne(&self, other: &Self) -> Option<Option<::srad::types::MetricValue>> {
296                if let Some(difference_instance) = ::srad::types::PartialTemplate::template_instance_from_difference(self, other)
297                {
298                    return Some(Some(difference_instance.into()))
299                }
300                return None
301            }
302            fn try_update_from_metric_value(&mut self, value: Option<::srad::types::MetricValue>) -> Result<(), Self::Error> {
303                let instance = match value {
304                    Some(val) => ::srad::types::TemplateInstance::try_from(val).map_err(|_|())?,
305                    None => return Ok(())
306                };
307                ::srad::types::PartialTemplate::update_from_instance(self, instance).map_err(|_|())
308            }
309        }
310
311        impl ::srad::types::Template for #type_name {
312
313            fn template_definition() -> ::srad::types::TemplateDefinition
314            {
315                let parameters = vec![
316                    #(#definition_parameters),*
317                ];
318                let metrics = vec![
319                    #(#definition_metrics),*
320                ];
321                ::srad::types::TemplateDefinition {
322                    version: Self::template_version().map(|version| version.to_owned()),
323                    metrics,
324                    parameters
325                }
326            }
327
328            fn template_instance(&self) -> ::srad::types::TemplateInstance
329            {
330                let parameters = vec![
331                   #(#instance_parameters),*
332                ];
333                let metrics = vec![
334                   #(#instance_metrics),*
335                ];
336                ::srad::types::TemplateInstance {
337                    template_ref: Self::template_definition_metric_name().to_owned(),
338                    version: Self::template_version().map(|version| version.to_owned()),
339                    metrics,
340                    parameters
341                }
342            }
343        }
344
345        impl ::srad::types::PartialTemplate for #type_name {
346
347            fn template_instance_from_difference(&self, other: &Self) -> Option<::srad::types::TemplateInstance>
348            {
349                let mut parameters = Vec::new();
350                let mut metrics = Vec::new();
351
352                #(#from_difference_metrics)*
353                #(#from_difference_parameters)*
354
355                if parameters.is_empty() && metrics.is_empty() {
356                    return None
357                }
358
359                Some(::srad::types::TemplateInstance{
360                    template_ref: Self::template_definition_metric_name().to_owned(),
361                    version: Self::template_version().map(|version| version.to_owned()),
362                    metrics,
363                    parameters
364                })
365            }
366
367            fn update_from_instance(&mut self, instance: ::srad::types::TemplateInstance) -> Result<(), ::srad::types::TemplateError> {
368
369                if instance.template_ref != Self::template_definition_metric_name() {
370                    return Err(::srad::types::TemplateError::RefMismatch(instance.template_ref))
371                }
372
373                if instance.version.as_deref() != Self::template_version() {
374                    return Err(::srad::types::TemplateError::VersionMismatch)
375                }
376
377                #(#update_from_instance_init_defines)*
378
379                for parameter in instance.parameters {
380                    let name = parameter.name.ok_or(::srad::types::TemplateError::InvalidPayload)?;
381                    match name.as_str() {
382                        #(#update_from_instance_parameter_match)*
383                        _ => return Err(::srad::types::TemplateError::UnknownParameter(name))
384                    }
385                }
386
387                for metric in instance.metrics {
388                    let name = metric.name.ok_or(::srad::types::TemplateError::InvalidPayload)?;
389                    match name.as_str() {
390                        #(#update_from_instance_metric_match)*
391                        _ => return Err(::srad::types::TemplateError::UnknownMetric(name))
392                    }
393                }
394
395                #(#update_from_instance_update_defines)*
396
397                Ok(())
398            }
399
400        }
401
402        impl TryFrom<::srad::types::TemplateInstance> for #type_name {
403
404            type Error = ::srad::types::TemplateError;
405
406            fn try_from(value: ::srad::types::TemplateInstance) -> Result<Self, Self::Error> {
407                if value.template_ref != Self::template_definition_metric_name() {
408                    return Err(::srad::types::TemplateError::RefMismatch(value.template_ref))
409                }
410
411                if value.version.as_deref() != Self::template_version() {
412                    return Err(::srad::types::TemplateError::VersionMismatch)
413                }
414
415                #(#from_instance_defines)*
416
417                for parameter in value.parameters {
418                    let name = parameter.name.ok_or(::srad::types::TemplateError::InvalidPayload)?;
419                    match name.as_str() {
420                        #(#from_instance_parameter_match)*
421                        _ => return Err(::srad::types::TemplateError::UnknownParameter(name))
422                    }
423                }
424
425                for metric in value.metrics {
426                    let name = metric.name.ok_or(::srad::types::TemplateError::InvalidPayload)?;
427                    match name.as_str() {
428                        #(#from_instance_metric_match)*
429                        _ => return Err(::srad::types::TemplateError::UnknownMetric(name))
430                    }
431                }
432
433                Ok(Self {
434                    #(#from_instance_init_struct)*
435                })
436            }
437
438        }
439
440    }.into())
441}
442
443/// # Template Derive Macro
444///
445/// The `#[derive(Template)]` macro provides automatic implementation of the `Template`,
446/// `PartialTemplate`, `TemplateMetricValuePartial` and `TemplateMetricValue` traits.
447///
448/// ## Requirements
449///
450/// - **Struct with named fields**: The macro only works with structs that have named fields
451/// - **TemplateMetadata implementation**: You must manually implement `TemplateMetadata` for your struct
452/// - All metric fields must implement `TemplateMetricValue`, `TemplateMetricValuePartial`.
453/// - All fields must implement `Clone`.
454/// - If no default attribute value is provided, fields must implement `Default`
455///
456/// ## Basic Usage
457///
458/// ```rust
459/// # use srad::types::{Template, TemplateMetadata};
460/// #[derive(Template)]
461/// struct SensorData {
462///     temperature: f64,
463///     humidity: f64,
464///     battery_level: u8,
465///     timestamp: u64,
466/// }
467///
468/// impl TemplateMetadata for SensorData {
469///     fn template_name() -> &'static str {
470///         "sensor_data"
471///     }
472///     
473///     fn template_version() -> Option<&'static str> {
474///         Some("1.0.0")
475///     }
476/// }
477///
478/// // Template and PartialTemplate are automatically implemented!
479/// ```
480///
481/// ## Field Attributes
482///
483/// The macro supports several attributes to customise field behavior:
484///
485/// ### `#[template(skip)]`
486///
487/// Excludes a field from the template definition and instances.
488///
489/// ### `#[template(parameter)]`
490///
491/// Marks a field as a template parameter rather than as a metric.  
492///
493/// ### `#[template(default = value)]`
494///
495/// Provides a default value for a field in the template definition. If not provided then [Default::default()]
496/// trait is used.
497///
498/// ### `#[template(rename = "new_name")]`
499///
500/// Changes the metric or parameter name in the template definition. By default, the field
501/// name is used. Names must be unique.
502///
503/// ## Example
504///
505/// ```rust
506/// use srad::types::{Template, PartialTemplate, TemplateMetadata};
507/// use std::collections::HashMap;
508///
509/// #[derive(Template, Clone)]
510/// struct MotorController {
511///     #[template(rename = "rpm")]
512///     revolutions_per_minute: f64,
513///     
514///     #[template(rename = "temp_c")]
515///     temperature_celsius: f64,
516///     
517///     #[template(parameter, default = 3000.0)]
518///     max_rpm: f64,
519///     
520///     #[template(parameter, default = 85.0)]
521///     temp_warning_threshold: f64,
522///     
523///     #[template(default = false)]
524///     alarm_active: bool,
525///     
526///     #[template(skip)]
527///     internal_diagnostics: HashMap<String, String>,
528/// }
529///
530/// impl TemplateMetadata for MotorController {
531///     fn template_name() -> &'static str {
532///         "motor_controller"
533///     }
534///     
535///     fn template_version() -> Option<&'static str> {
536///         Some("2.1.0")
537///     }
538/// }
539///
540/// // Usage
541/// let motor = MotorController {
542///     revolutions_per_minute: 2500.0,
543///     temperature_celsius: 72.0,
544///     max_rpm: 3000.0,
545///     temp_warning_threshold: 85.0,
546///     alarm_active: false,
547///     internal_diagnostics: HashMap::new(),
548/// };
549///
550/// // Get template definition
551/// let definition = MotorController::template_definition();
552///
553/// // Create instance
554/// let instance = motor.template_instance();
555///
556/// // Create differential update
557/// let updated_motor = MotorController {
558///     revolutions_per_minute: 2750.0,  // Changed
559///     temperature_celsius: 72.0,       // Same
560///     max_rpm: 3000.0,
561///     temp_warning_threshold: 85.0,
562///     alarm_active: false,
563///     internal_diagnostics: HashMap::new(),
564/// };
565///
566/// if let Some(diff) = updated_motor.template_instance_from_difference(&motor) {
567///     // diff contains only the 'rpm' metric
568/// }
569/// ```
570#[proc_macro_derive(Template, attributes(template))]
571pub fn template_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
572    let input = parse_macro_input!(input as DeriveInput);
573    match try_template(input) {
574        Ok(tokens) => tokens,
575        Err(err) => err.into_compile_error().into(),
576    }
577}