Skip to main content

sparkplug_b/
value.rs

1//! Typed value enums — the heart of the “make illegal states unrepresentable”
2//! design (ADR-2). A [`MetricValue`] fuses the Sparkplug datatype with its
3//! payload, so the runtime `checkType` of the Java reference is unnecessary.
4
5use bytes::Bytes;
6
7use crate::datatype::DataType;
8use crate::error::{Result, SparkplugError};
9use crate::model::{DataSet, PropertySet, PropertySetList, Template};
10
11/// The value carried by a [`crate::model::Metric`].
12///
13/// The active variant determines the on-wire [`DataType`]; the sole exception is
14/// [`MetricValue::Null`], which carries its declared datatype explicitly (a null
15/// metric still has a declared type, expressed on the wire as `is_null = true`).
16#[derive(Clone, Debug, PartialEq)]
17#[allow(missing_docs)] // variant names mirror the spec datatype names
18pub enum MetricValue {
19    Int8(i8),
20    Int16(i16),
21    Int32(i32),
22    Int64(i64),
23    UInt8(u8),
24    UInt16(u16),
25    UInt32(u32),
26    UInt64(u64),
27    Float(f32),
28    Double(f64),
29    Boolean(bool),
30    String(String),
31    Text(String),
32    Uuid(String),
33    /// Epoch milliseconds, UTC (`tck-id-payloads-timestamp-in-UTC`).
34    DateTime(i64),
35    Bytes(Bytes),
36    File(Bytes),
37    DataSet(DataSet),
38    Template(Box<Template>),
39    Int8Array(Vec<i8>),
40    Int16Array(Vec<i16>),
41    Int32Array(Vec<i32>),
42    Int64Array(Vec<i64>),
43    UInt8Array(Vec<u8>),
44    UInt16Array(Vec<u16>),
45    UInt32Array(Vec<u32>),
46    UInt64Array(Vec<u64>),
47    FloatArray(Vec<f32>),
48    DoubleArray(Vec<f64>),
49    BooleanArray(Vec<bool>),
50    StringArray(Vec<String>),
51    DateTimeArray(Vec<i64>),
52    /// A null value with its declared datatype (encoded as `is_null = true`).
53    Null(DataType),
54}
55
56impl MetricValue {
57    /// The Sparkplug [`DataType`] this value declares on the wire.
58    #[must_use]
59    pub fn datatype(&self) -> DataType {
60        match self {
61            Self::Int8(_) => DataType::Int8,
62            Self::Int16(_) => DataType::Int16,
63            Self::Int32(_) => DataType::Int32,
64            Self::Int64(_) => DataType::Int64,
65            Self::UInt8(_) => DataType::UInt8,
66            Self::UInt16(_) => DataType::UInt16,
67            Self::UInt32(_) => DataType::UInt32,
68            Self::UInt64(_) => DataType::UInt64,
69            Self::Float(_) => DataType::Float,
70            Self::Double(_) => DataType::Double,
71            Self::Boolean(_) => DataType::Boolean,
72            Self::String(_) => DataType::String,
73            Self::Text(_) => DataType::Text,
74            Self::Uuid(_) => DataType::Uuid,
75            Self::DateTime(_) => DataType::DateTime,
76            Self::Bytes(_) => DataType::Bytes,
77            Self::File(_) => DataType::File,
78            Self::DataSet(_) => DataType::DataSet,
79            Self::Template(_) => DataType::Template,
80            Self::Int8Array(_) => DataType::Int8Array,
81            Self::Int16Array(_) => DataType::Int16Array,
82            Self::Int32Array(_) => DataType::Int32Array,
83            Self::Int64Array(_) => DataType::Int64Array,
84            Self::UInt8Array(_) => DataType::UInt8Array,
85            Self::UInt16Array(_) => DataType::UInt16Array,
86            Self::UInt32Array(_) => DataType::UInt32Array,
87            Self::UInt64Array(_) => DataType::UInt64Array,
88            Self::FloatArray(_) => DataType::FloatArray,
89            Self::DoubleArray(_) => DataType::DoubleArray,
90            Self::BooleanArray(_) => DataType::BooleanArray,
91            Self::StringArray(_) => DataType::StringArray,
92            Self::DateTimeArray(_) => DataType::DateTimeArray,
93            Self::Null(dt) => *dt,
94        }
95    }
96
97    /// Whether this is a null value (`is_null = true` on the wire).
98    #[must_use]
99    pub fn is_null(&self) -> bool {
100        matches!(self, Self::Null(_))
101    }
102
103    /// Construct a checked null value, rejecting datatypes that are invalid for
104    /// a metric (`Unknown`, and the property-only `PropertySet`/`PropertySetList`).
105    /// This prevents emitting a non-conformant `datatype` field on the wire.
106    ///
107    /// # Errors
108    /// Returns [`SparkplugError::ValueTypeMismatch`] if `dt` is not a valid
109    /// metric datatype.
110    pub fn null(dt: DataType) -> Result<Self> {
111        match dt {
112            DataType::Unknown | DataType::PropertySet | DataType::PropertySetList => Err(
113                SparkplugError::ValueTypeMismatch(format!("{dt:?} is not a valid metric datatype")),
114            ),
115            _ => Ok(Self::Null(dt)),
116        }
117    }
118}
119
120/// A single cell value inside a [`DataSet`] row. Only basic scalar types are
121/// valid; a `Null` cell encodes as an unset value oneof.
122#[derive(Clone, Debug, PartialEq)]
123#[allow(missing_docs)]
124pub enum DataSetValue {
125    Int8(i8),
126    Int16(i16),
127    Int32(i32),
128    Int64(i64),
129    UInt8(u8),
130    UInt16(u16),
131    UInt32(u32),
132    UInt64(u64),
133    Float(f32),
134    Double(f64),
135    Boolean(bool),
136    String(String),
137    Text(String),
138    DateTime(i64),
139    Null,
140}
141
142impl DataSetValue {
143    /// The Sparkplug [`DataType`] this cell declares, or `None` for `Null`.
144    #[must_use]
145    pub fn datatype(&self) -> Option<DataType> {
146        let dt = match self {
147            Self::Int8(_) => DataType::Int8,
148            Self::Int16(_) => DataType::Int16,
149            Self::Int32(_) => DataType::Int32,
150            Self::Int64(_) => DataType::Int64,
151            Self::UInt8(_) => DataType::UInt8,
152            Self::UInt16(_) => DataType::UInt16,
153            Self::UInt32(_) => DataType::UInt32,
154            Self::UInt64(_) => DataType::UInt64,
155            Self::Float(_) => DataType::Float,
156            Self::Double(_) => DataType::Double,
157            Self::Boolean(_) => DataType::Boolean,
158            Self::String(_) => DataType::String,
159            Self::Text(_) => DataType::Text,
160            Self::DateTime(_) => DataType::DateTime,
161            Self::Null => return None,
162        };
163        Some(dt)
164    }
165
166    /// Whether this cell is null (unset value oneof on the wire).
167    #[must_use]
168    pub fn is_null(&self) -> bool {
169        matches!(self, Self::Null)
170    }
171}
172
173/// A value inside a [`PropertySet`] — basic scalars plus recursive property sets.
174#[derive(Clone, Debug, PartialEq)]
175#[allow(missing_docs)]
176pub enum PropertyValue {
177    Int8(i8),
178    Int16(i16),
179    Int32(i32),
180    Int64(i64),
181    UInt8(u8),
182    UInt16(u16),
183    UInt32(u32),
184    UInt64(u64),
185    Float(f32),
186    Double(f64),
187    Boolean(bool),
188    String(String),
189    Text(String),
190    DateTime(i64),
191    PropertySet(PropertySet),
192    PropertySetList(PropertySetList),
193    /// A null property value with its declared datatype.
194    Null(DataType),
195}
196
197impl PropertyValue {
198    /// The Sparkplug [`DataType`] this property value declares on the wire.
199    #[must_use]
200    pub fn datatype(&self) -> DataType {
201        match self {
202            Self::Int8(_) => DataType::Int8,
203            Self::Int16(_) => DataType::Int16,
204            Self::Int32(_) => DataType::Int32,
205            Self::Int64(_) => DataType::Int64,
206            Self::UInt8(_) => DataType::UInt8,
207            Self::UInt16(_) => DataType::UInt16,
208            Self::UInt32(_) => DataType::UInt32,
209            Self::UInt64(_) => DataType::UInt64,
210            Self::Float(_) => DataType::Float,
211            Self::Double(_) => DataType::Double,
212            Self::Boolean(_) => DataType::Boolean,
213            Self::String(_) => DataType::String,
214            Self::Text(_) => DataType::Text,
215            Self::DateTime(_) => DataType::DateTime,
216            Self::PropertySet(_) => DataType::PropertySet,
217            Self::PropertySetList(_) => DataType::PropertySetList,
218            Self::Null(dt) => *dt,
219        }
220    }
221}
222
223/// A template parameter value — basic scalar types only
224/// (`tck-id-payloads-template-parameter-value`).
225#[derive(Clone, Debug, PartialEq)]
226#[allow(missing_docs)]
227pub enum ParameterValue {
228    Int8(i8),
229    Int16(i16),
230    Int32(i32),
231    Int64(i64),
232    UInt8(u8),
233    UInt16(u16),
234    UInt32(u32),
235    UInt64(u64),
236    Float(f32),
237    Double(f64),
238    Boolean(bool),
239    String(String),
240    Text(String),
241    DateTime(i64),
242}
243
244impl ParameterValue {
245    /// The Sparkplug [`DataType`] this parameter value declares on the wire.
246    #[must_use]
247    pub fn datatype(&self) -> DataType {
248        match self {
249            Self::Int8(_) => DataType::Int8,
250            Self::Int16(_) => DataType::Int16,
251            Self::Int32(_) => DataType::Int32,
252            Self::Int64(_) => DataType::Int64,
253            Self::UInt8(_) => DataType::UInt8,
254            Self::UInt16(_) => DataType::UInt16,
255            Self::UInt32(_) => DataType::UInt32,
256            Self::UInt64(_) => DataType::UInt64,
257            Self::Float(_) => DataType::Float,
258            Self::Double(_) => DataType::Double,
259            Self::Boolean(_) => DataType::Boolean,
260            Self::String(_) => DataType::String,
261            Self::Text(_) => DataType::Text,
262            Self::DateTime(_) => DataType::DateTime,
263        }
264    }
265}