srad_types/
template.rs

1use thiserror::Error;
2
3use crate::{
4    payload::{self, metric, DataType},
5    traits::{self, HasDataType},
6    FromValueTypeError, MetricValue, ParameterValue,
7};
8
9pub trait TemplateMetricValue {
10    type Error;
11
12    fn to_template_metric_value(self) -> Option<MetricValue>;
13
14    fn try_from_template_metric_value(value: Option<MetricValue>) -> Result<Self, Self::Error>
15    where
16        Self: Sized;
17}
18
19impl<T> TemplateMetricValue for T
20where
21    T: traits::MetricValue,
22{
23    type Error = ();
24
25    fn to_template_metric_value(self) -> Option<MetricValue> {
26        Some(T::into(self))
27    }
28
29    fn try_from_template_metric_value(value: Option<MetricValue>) -> Result<Self, Self::Error>
30    where
31        Self: Sized,
32    {
33        match value {
34            Some(value) => Self::try_from(value).map_err(|_| ()),
35            None => Err(()),
36        }
37    }
38}
39
40impl<T> TemplateMetricValue for Option<T>
41where
42    T: traits::MetricValue,
43{
44    type Error = ();
45
46    fn to_template_metric_value(self) -> Option<MetricValue> {
47        self.map(T::into)
48    }
49
50    fn try_from_template_metric_value(value: Option<MetricValue>) -> Result<Self, Self::Error>
51    where
52        Self: Sized,
53    {
54        match value {
55            Some(value) => {
56                let value = value.try_into().map_err(|_| ())?;
57                Ok(Some(value))
58            }
59            None => Ok(None),
60        }
61    }
62}
63
64pub trait TemplateParameterValue {
65    type Error;
66    fn to_template_parameter_value(self) -> Option<ParameterValue>;
67    fn try_from_template_parameter_value(
68        value: Option<ParameterValue>,
69    ) -> Result<Self, Self::Error>
70    where
71        Self: Sized;
72}
73
74impl<T> TemplateParameterValue for T
75where
76    T: traits::ParameterValue,
77{
78    type Error = ();
79
80    fn to_template_parameter_value(self) -> Option<ParameterValue> {
81        Some(T::into(self))
82    }
83
84    fn try_from_template_parameter_value(value: Option<ParameterValue>) -> Result<Self, Self::Error>
85    where
86        Self: Sized,
87    {
88        match value {
89            Some(value) => Self::try_from(value).map_err(|_| ()),
90            None => Err(()),
91        }
92    }
93}
94
95impl<T> TemplateParameterValue for Option<T>
96where
97    T: traits::ParameterValue,
98{
99    type Error = ();
100
101    fn to_template_parameter_value(self) -> Option<ParameterValue> {
102        self.map(T::into)
103    }
104
105    fn try_from_template_parameter_value(value: Option<ParameterValue>) -> Result<Self, Self::Error>
106    where
107        Self: Sized,
108    {
109        match value {
110            Some(value) => {
111                let value = value.try_into().map_err(|_| ())?;
112                Ok(Some(value))
113            }
114            None => Ok(None),
115        }
116    }
117}
118
119//A trait to support recursive partial templates
120pub trait TemplateMetricValuePartial {
121    type Error;
122    fn metric_value_if_ne(&self, other: &Self) -> Option<Option<MetricValue>>;
123    fn try_update_from_metric_value(
124        &mut self,
125        other: Option<MetricValue>,
126    ) -> Result<(), Self::Error>;
127}
128
129macro_rules! impl_template_metric_value_partial {
130
131    ($($ty:ty),* $(,)?) => {
132        $(
133            impl TemplateMetricValuePartial for $ty {
134                type Error = ();
135                fn metric_value_if_ne(&self, other: &Self) -> Option<Option<MetricValue>> {
136                    if self == other {
137                        return None
138                    }
139                    Some(Some(self.clone().into()))
140                }
141                fn try_update_from_metric_value(&mut self, other: Option<MetricValue>) -> Result<(), Self::Error> {
142                    *self = <$ty>::try_from_template_metric_value(other)?;
143                    Ok(())
144                }
145            }
146        )*
147    };
148
149    ($($ty:ty),* $(,)?) => {
150        $(
151            impl TemplateMetricValuePartial for Vec<$ty> {
152                fn metric_value_if_ne(&self, other: &Self) -> Option<Option<MetricValue>> {
153                    if self == other {
154                        return None
155                    }
156                    Some(Some(self.clone().into()))
157                }
158                fn try_update_from_metric_value(&mut self, other: Option<MetricValue>) -> Result<(), ()> {
159                    *self = <$ty>::try_from_template_metric_value(other)?;
160                    Ok(())
161                }
162            }
163        )*
164    };
165
166}
167
168impl_template_metric_value_partial!(bool, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, String);
169
170impl<T> TemplateMetricValuePartial for Option<T>
171where
172    T: TemplateMetricValuePartial + TemplateMetricValue + Into<MetricValue> + PartialEq + Clone,
173{
174    type Error = ();
175
176    fn metric_value_if_ne(&self, other: &Self) -> Option<Option<MetricValue>> {
177        if self == other {
178            return None;
179        }
180        Some(self.clone().map(|x| x.into()))
181    }
182
183    fn try_update_from_metric_value(
184        &mut self,
185        other: Option<MetricValue>,
186    ) -> Result<(), Self::Error> {
187        *self = match other {
188            Some(value) => Some(T::try_from_template_metric_value(Some(value)).map_err(|_| ())?),
189            None => None,
190        };
191        Ok(())
192    }
193}
194
195pub type TemplateMetric = payload::Metric;
196
197impl TemplateMetric {
198    pub fn new_template_metric_raw(
199        name: String,
200        datatype: DataType,
201        value: Option<MetricValue>,
202    ) -> Self {
203        TemplateMetric {
204            name: Some(name),
205            alias: None,
206            timestamp: None,
207            datatype: Some(datatype as u32),
208            is_historical: None,
209            is_transient: None,
210            is_null: None,
211            metadata: None,
212            properties: None,
213            value: value.map(payload::metric::Value::from),
214        }
215    }
216
217    pub fn new_template_metric<T: TemplateMetricValue + traits::HasDataType>(
218        name: String,
219        value: T,
220    ) -> Self {
221        Self::new_template_metric_raw(
222            name,
223            T::default_datatype(),
224            value.to_template_metric_value(),
225        )
226    }
227}
228
229pub type TemplateParameter = payload::template::Parameter;
230
231impl TemplateParameter {
232    pub fn new_template_parameter<T: TemplateParameterValue + traits::HasDataType>(
233        name: String,
234        value: T,
235    ) -> Self {
236        TemplateParameter {
237            name: Some(name),
238            r#type: Some(T::default_datatype() as u32),
239            value: value
240                .to_template_parameter_value()
241                .map(payload::template::parameter::Value::from),
242        }
243    }
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub struct TemplateDefinition {
248    pub version: Option<String>,
249    pub metrics: Vec<TemplateMetric>,
250    pub parameters: Vec<TemplateParameter>,
251}
252
253impl HasDataType for TemplateDefinition {
254    fn supported_datatypes() -> &'static [DataType] {
255        static SUPPORTED_TYPES: [DataType; 1] = [DataType::Template];
256        &SUPPORTED_TYPES
257    }
258}
259
260impl traits::MetricValue for TemplateDefinition {}
261
262impl From<TemplateDefinition> for payload::Template {
263    fn from(value: TemplateDefinition) -> Self {
264        payload::Template {
265            version: value.version,
266            metrics: value.metrics,
267            parameters: value.parameters,
268            template_ref: None,
269            is_definition: Some(true),
270        }
271    }
272}
273
274impl From<TemplateDefinition> for MetricValue {
275    fn from(value: TemplateDefinition) -> Self {
276        MetricValue::new(metric::Value::TemplateValue(value.into()))
277    }
278}
279
280impl TryFrom<MetricValue> for TemplateDefinition {
281    type Error = FromValueTypeError;
282
283    fn try_from(value: MetricValue) -> Result<Self, Self::Error> {
284        if let metric::Value::TemplateValue(template) = value.0 {
285            // [tck-id-payloads-template-definition-ref] A Template Definition MUST omit the template_ref field
286            if template.template_ref.is_some() {
287                return Err(FromValueTypeError::InvalidValue(
288                    "Template payload violates tck-id-payloads-template-definition-ref".into(),
289                ));
290            }
291            // [tck-id-payloads-template-definition-is-definition] A Template Definition MUST have is_definition set to true.
292            if template.is_definition.unwrap_or(false) {
293                return Err(FromValueTypeError::InvalidValue(
294                    "Template payload violates tck-id-payloads-template-definition-is_definition"
295                        .into(),
296                ));
297            }
298            Ok(TemplateDefinition {
299                version: template.version,
300                metrics: template.metrics,
301                parameters: template.parameters,
302            })
303        } else {
304            Err(FromValueTypeError::InvalidVariantType)
305        }
306    }
307}
308
309#[derive(Debug, PartialEq)]
310pub struct TemplateInstance {
311    //name of the metric that represents the template definition
312    pub template_ref: String,
313    pub version: Option<String>,
314    pub metrics: Vec<TemplateMetric>,
315    pub parameters: Vec<TemplateParameter>,
316}
317
318impl HasDataType for TemplateInstance {
319    fn supported_datatypes() -> &'static [DataType] {
320        static SUPPORTED_TYPES: [DataType; 1] = [DataType::Template];
321        &SUPPORTED_TYPES
322    }
323}
324
325impl traits::MetricValue for TemplateInstance {}
326
327impl From<TemplateInstance> for payload::Template {
328    fn from(value: TemplateInstance) -> Self {
329        payload::Template {
330            version: value.version,
331            metrics: value.metrics,
332            parameters: value.parameters,
333            template_ref: Some(value.template_ref),
334            is_definition: Some(false),
335        }
336    }
337}
338
339impl From<TemplateInstance> for MetricValue {
340    fn from(value: TemplateInstance) -> Self {
341        MetricValue::new(metric::Value::TemplateValue(value.into()))
342    }
343}
344
345impl TryFrom<MetricValue> for TemplateInstance {
346    type Error = FromValueTypeError;
347
348    fn try_from(value: MetricValue) -> Result<Self, Self::Error> {
349        if let metric::Value::TemplateValue(template) = value.0 {
350            //[tck-id-payloads-template-instance-is-definition] A Template Instance MUST have is_definition set to false.
351            if template.is_definition.unwrap_or(true) {
352                return Err(FromValueTypeError::InvalidValue(
353                    "Template payload violates tck-id-payloads-template-instance-is_definition"
354                        .into(),
355                ));
356            }
357            let template_ref = template
358                .template_ref
359                .ok_or(FromValueTypeError::InvalidValue(
360                    "Template payload violates tck-id-payloads-template-instance-ref".into(),
361                ))?;
362            Ok(TemplateInstance {
363                template_ref,
364                version: template.version,
365                metrics: template.metrics,
366                parameters: template.parameters,
367            })
368        } else {
369            Err(FromValueTypeError::InvalidVariantType)
370        }
371    }
372}
373
374#[derive(Debug)]
375pub enum TemplateValue {
376    Definition(TemplateDefinition),
377    Instance(TemplateInstance),
378}
379
380impl TryFrom<MetricValue> for TemplateValue {
381    type Error = FromValueTypeError;
382
383    fn try_from(value: MetricValue) -> Result<Self, Self::Error> {
384        if let metric::Value::TemplateValue(template) = &value.0 {
385            let is_def = match template.is_definition {
386                Some(is_def) => is_def,
387                None => {
388                    return Err(FromValueTypeError::InvalidValue(
389                        "Template field template_ref cannot be None".into(),
390                    ))
391                }
392            };
393            Ok(match is_def {
394                true => TemplateValue::Definition(TemplateDefinition::try_from(value)?),
395                false => TemplateValue::Instance(TemplateInstance::try_from(value)?),
396            })
397        } else {
398            Err(FromValueTypeError::InvalidVariantType)
399        }
400    }
401}
402
403/// Provides metadata information for a Template
404///
405/// This trait defines basic metadata for Sparkplug templates
406pub trait TemplateMetadata {
407    ///Provide an optional version for the template
408    fn template_version() -> Option<&'static str> {
409        None
410    }
411
412    ///Provide the name of the template
413    fn template_name() -> &'static str;
414
415    ///Provides a name that will be used as the metric name for the templates definition in a birth message.
416    ///
417    /// This should be unique in the context of an Edge Node.
418    /// By default, this method combines the template name and version (if available) to create
419    /// a metric name that follows Sparkplug conventions. The format is:
420    /// - With version: `"template_name:version"`
421    /// - Without version: `"template_name"`
422    fn template_definition_metric_name() -> String {
423        let version = Self::template_version();
424        match version {
425            Some(version) => format!("{}:{}", Self::template_name(), version),
426            None => Self::template_name().into(),
427        }
428    }
429}
430
431#[derive(Debug, Error)]
432pub enum TemplateError {
433    #[error("Invalid Template Payload")]
434    InvalidPayload,
435    #[error("Unexpected Parameter: {0}")]
436    UnknownParameter(String),
437    #[error("Unexpected Metric: {0}")]
438    UnknownMetric(String),
439    #[error("Template ref mismatch: {0}")]
440    RefMismatch(String),
441    #[error("Template version mismatch")]
442    VersionMismatch,
443    #[error("Invalid value for parameter field {0}")]
444    InvalidParameterValue(String),
445    #[error("Invalid value for metric field {0}")]
446    InvalidMetricValue(String),
447}
448
449/// Trait used to represent a Template
450///
451/// **It is strongly recommended to use the [srad_macros::Template] derive macro instead of
452/// implementing this trait manually**
453pub trait Template: TemplateMetadata + TryFrom<TemplateInstance> {
454    /// Returns the template definition
455    fn template_definition() -> TemplateDefinition;
456    /// Creates a template instance from the current state
457    fn template_instance(&self) -> TemplateInstance;
458}
459
460impl<T> HasDataType for T
461where
462    T: Template,
463{
464    fn supported_datatypes() -> &'static [DataType] {
465        static SUPPORTED_TYPES: [DataType; 1] = [DataType::Template];
466        &SUPPORTED_TYPES
467    }
468}
469
470/// Trait used to represent a Template implementation that supports generating partial template instances
471/// and is updatable from partial template instances
472///
473/// **It is strongly recommended to use the [srad_macros::Template] derive macro instead of
474/// implementing this trait manually**
475pub trait PartialTemplate: Template {
476    /// Create a template instance based on the difference between a template and another copy
477    fn template_instance_from_difference(&self, other: &Self) -> Option<TemplateInstance>;
478    /// Update the template from a [TemplateInstance]
479    fn update_from_instance(&mut self, instance: TemplateInstance) -> Result<(), TemplateError>;
480}