Skip to main content

coapum_senml/
pack.rs

1//! SenML Pack - collection of SenML records
2
3use crate::{Result, SenMLError, SenMLRecord};
4use serde::{Deserialize, Serialize};
5
6#[cfg(feature = "validation")]
7use validator::Validate;
8
9/// A SenML Pack represents a collection of SenML records with optional base values
10///
11/// According to RFC 8428, a SenML Pack is an array of SenML Records. The first
12/// record can contain base values (fields starting with 'b') that apply to
13/// subsequent records, reducing redundancy in the representation.
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(transparent)]
16pub struct SenMLPack {
17    /// Array of SenML records
18    pub records: Vec<SenMLRecord>,
19}
20
21/// Base values that can be applied to multiple records in a pack
22#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
23#[cfg_attr(feature = "validation", derive(Validate))]
24pub struct BaseValues {
25    /// Base Name - prepended to record names
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub bn: Option<String>,
28
29    /// Base Time - added to record timestamps  
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub bt: Option<f64>,
32
33    /// Base Unit - used when record has no unit
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub bu: Option<String>,
36
37    /// Base Value - added to numeric record values
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub bv: Option<f64>,
40
41    /// Base Sum - added to sum values
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub bs: Option<f64>,
44
45    /// Base Version - SenML version number  
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub bver: Option<i32>,
48}
49
50impl SenMLPack {
51    /// Create a new empty pack
52    pub fn new() -> Self {
53        Self {
54            records: Vec::new(),
55        }
56    }
57
58    /// Create a pack with base values
59    pub fn with_base_values(base: BaseValues) -> Self {
60        let mut base_record = SenMLRecord::default();
61
62        // Set base values in the first record
63        if let Some(bn) = base.bn {
64            base_record.n = Some(bn);
65        }
66
67        // Note: Base values are typically stored as fields starting with 'b'
68        // but serde flattening will handle this during serialization
69
70        Self {
71            records: vec![base_record],
72        }
73    }
74
75    /// Add a record to this pack
76    pub fn add_record(&mut self, record: SenMLRecord) {
77        self.records.push(record);
78    }
79
80    /// Add multiple records to this pack
81    pub fn add_records<I>(&mut self, records: I)
82    where
83        I: IntoIterator<Item = SenMLRecord>,
84    {
85        self.records.extend(records);
86    }
87
88    /// Get base values from the first record (if any)
89    pub fn base_values(&self) -> BaseValues {
90        self.records
91            .first()
92            .map(|record| self.extract_base_values(record))
93            .unwrap_or_default()
94    }
95
96    /// Check if this pack has base values
97    pub fn has_base_values(&self) -> bool {
98        if let Some(first) = self.records.first() {
99            // Check if first record has base-like values
100            first.n.as_ref().map_or(false, |n| n.ends_with('/'))
101                || first.t.is_some()
102                || first.u.is_some()
103        } else {
104            false
105        }
106    }
107
108    /// Get the number of records in this pack
109    pub fn len(&self) -> usize {
110        self.records.len()
111    }
112
113    /// Check if this pack is empty
114    pub fn is_empty(&self) -> bool {
115        self.records.is_empty()
116    }
117
118    /// Iterate over records in this pack
119    pub fn iter(&self) -> impl Iterator<Item = &SenMLRecord> {
120        self.records.iter()
121    }
122
123    /// Get a mutable iterator over records
124    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut SenMLRecord> {
125        self.records.iter_mut()
126    }
127
128    /// Validate this pack according to RFC 8428
129    pub fn validate(&self) -> Result<()> {
130        if self.records.is_empty() {
131            return Err(SenMLError::validation("SenML pack cannot be empty"));
132        }
133
134        // Validate each record
135        for (i, record) in self.records.iter().enumerate() {
136            record.validate().map_err(|e| {
137                SenMLError::validation(format!("Invalid record at index {}: {}", i, e))
138            })?;
139        }
140
141        // Validate base values if present
142        let base = self.base_values();
143        if let Some(bt) = base.bt {
144            if !bt.is_finite() {
145                return Err(SenMLError::invalid_field_value("bt", &bt.to_string()));
146            }
147        }
148
149        if let Some(bv) = base.bv {
150            if !bv.is_finite() {
151                return Err(SenMLError::invalid_field_value("bv", &bv.to_string()));
152            }
153        }
154
155        if let Some(bs) = base.bs {
156            if !bs.is_finite() {
157                return Err(SenMLError::invalid_field_value("bs", &bs.to_string()));
158            }
159        }
160
161        Ok(())
162    }
163
164    /// Convert this pack to a normalized form
165    pub fn normalize(&self) -> crate::normalize::NormalizedPack {
166        crate::normalize::NormalizedPack::from_pack(self)
167    }
168
169    /// Extract base values from a record (typically the first one)
170    fn extract_base_values(&self, record: &SenMLRecord) -> BaseValues {
171        BaseValues {
172            bn: record.n.clone(),
173            bt: record.t,
174            bu: record.u.clone(),
175            bv: record.v,
176            bs: record.s,
177            bver: None, // Version not stored in basic record
178        }
179    }
180}
181
182impl Default for SenMLPack {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188impl FromIterator<SenMLRecord> for SenMLPack {
189    fn from_iter<I: IntoIterator<Item = SenMLRecord>>(iter: I) -> Self {
190        Self {
191            records: iter.into_iter().collect(),
192        }
193    }
194}
195
196impl IntoIterator for SenMLPack {
197    type Item = SenMLRecord;
198    type IntoIter = std::vec::IntoIter<SenMLRecord>;
199
200    fn into_iter(self) -> Self::IntoIter {
201        self.records.into_iter()
202    }
203}
204
205impl<'a> IntoIterator for &'a SenMLPack {
206    type Item = &'a SenMLRecord;
207    type IntoIter = std::slice::Iter<'a, SenMLRecord>;
208
209    fn into_iter(self) -> Self::IntoIter {
210        self.records.iter()
211    }
212}
213
214// Convenience methods for serialization
215impl SenMLPack {
216    /// Serialize to JSON string
217    #[cfg(feature = "json")]
218    pub fn to_json(&self) -> Result<String> {
219        serde_json::to_string(self).map_err(|e| SenMLError::serialization(e.to_string()))
220    }
221
222    /// Serialize to pretty JSON string
223    #[cfg(feature = "json")]
224    pub fn to_json_pretty(&self) -> Result<String> {
225        serde_json::to_string_pretty(self).map_err(|e| SenMLError::serialization(e.to_string()))
226    }
227
228    /// Deserialize from JSON string
229    #[cfg(feature = "json")]
230    pub fn from_json(json: &str) -> Result<Self> {
231        serde_json::from_str(json).map_err(|e| SenMLError::deserialization(e.to_string()))
232    }
233
234    /// Serialize to CBOR bytes
235    #[cfg(feature = "cbor")]
236    pub fn to_cbor(&self) -> Result<Vec<u8>> {
237        let mut buffer = Vec::new();
238        ciborium::ser::into_writer(self, &mut buffer)
239            .map_err(|e| SenMLError::serialization(e.to_string()))?;
240        Ok(buffer)
241    }
242
243    /// Deserialize from CBOR bytes
244    #[cfg(feature = "cbor")]
245    pub fn from_cbor(bytes: &[u8]) -> Result<Self> {
246        ciborium::de::from_reader(bytes).map_err(|e| SenMLError::deserialization(e.to_string()))
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use crate::SenMLRecord;
254
255    #[test]
256    fn test_empty_pack_creation() {
257        let pack = SenMLPack::new();
258        assert!(pack.is_empty());
259        assert_eq!(pack.len(), 0);
260    }
261
262    #[test]
263    fn test_pack_with_records() {
264        let mut pack = SenMLPack::new();
265        pack.add_record(SenMLRecord::with_value("temperature", 22.5));
266        pack.add_record(SenMLRecord::with_value("humidity", 45.0));
267
268        assert_eq!(pack.len(), 2);
269        assert!(!pack.is_empty());
270    }
271
272    #[test]
273    fn test_pack_iteration() {
274        let records = vec![
275            SenMLRecord::with_value("temp", 20.0),
276            SenMLRecord::with_value("humidity", 50.0),
277        ];
278        let pack: SenMLPack = records.into_iter().collect();
279
280        let mut count = 0;
281        for record in &pack {
282            count += 1;
283            assert!(record.has_value());
284        }
285        assert_eq!(count, 2);
286    }
287
288    #[test]
289    fn test_pack_validation() {
290        let mut pack = SenMLPack::new();
291        pack.add_record(SenMLRecord::with_value("temp", 25.0));
292
293        assert!(pack.validate().is_ok());
294
295        let empty_pack = SenMLPack::new();
296        assert!(empty_pack.validate().is_err());
297    }
298
299    #[cfg(feature = "json")]
300    #[test]
301    fn test_json_serialization() {
302        let mut pack = SenMLPack::new();
303        pack.add_record(SenMLRecord::with_value("temperature", 22.5));
304
305        let json = pack.to_json().unwrap();
306        let deserialized = SenMLPack::from_json(&json).unwrap();
307
308        assert_eq!(pack, deserialized);
309    }
310
311    #[cfg(feature = "cbor")]
312    #[test]
313    fn test_cbor_serialization() {
314        let mut pack = SenMLPack::new();
315        pack.add_record(SenMLRecord::with_value("temperature", 22.5));
316
317        let cbor = pack.to_cbor().unwrap();
318        let deserialized = SenMLPack::from_cbor(&cbor).unwrap();
319
320        assert_eq!(pack, deserialized);
321    }
322}