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#[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}