Skip to main content

sindit_senml/
lib.rs

1//! # SINDIT SenML
2//!
3//! This library implements the [SenML RFC8428](https://www.rfc-editor.org/rfc/rfc8428.html) proposed standard.
4//!
5//! SenML (Sensor Markup Language) is a format for representing sensor data.
6//!
7//! Only the JSON representation is implemented.
8//!
9//! # Examples:
10//!
11//! ```
12//! use sindit_senml::parse_json;
13//!
14//! let json_str = r#"[{"n": "temperature", "v": 42.0}]"#;
15//! let records = parse_json(json_str, None).unwrap();
16//! assert_eq!(records[0].name, "temperature");
17//! assert_eq!(records[0].get_float_value(), Some(42.0));
18//! ```
19//!
20//! ```
21//! use sindit_senml::SenMLResolvedRecord;
22//!
23//! let record = SenMLResolvedRecord {
24//!     name: "temperature".to_string(),
25//!     unit: Some("Cel".to_string()),
26//!     value: Some(sindit_senml::SenMLValueField::FloatingPoint(42f64)),
27//!     sum: None,
28//!     time: chrono::DateTime::<chrono::Utc>::from_timestamp(1234567890, 0).unwrap(),
29//!     update_time: None,
30//!     base_version: None,
31//!     extra_fields: None,
32//! };
33//! let json = serde_json::to_string(&vec![record]).unwrap();
34//! assert_eq!(
35//!     json,
36//!     r#"[{"n":"temperature","u":"Cel","v":42,"t":1234567890}]"#
37//! );
38//! ```
39//!
40use std::collections::HashMap;
41
42use base64::Engine;
43use chrono::{DateTime, Utc};
44use serde::ser::{SerializeStruct, Serializer};
45use serde::Deserialize;
46use serde::Serialize;
47use thiserror::Error;
48use time::datetime_to_timestamp;
49use validate_name::validate_name;
50
51#[cfg(feature = "stream")]
52mod stream;
53
54pub mod time;
55pub mod validate_name;
56
57/// SINDIT SenML Error.
58///
59/// This represent the common errors that can happen when using this library.
60/// The library is not supposed to panic, but instead return an error.
61#[derive(Error, Debug)]
62pub enum SinditSenMLError {
63    #[error("Invalid JSON")]
64    InvalidJSON(#[from] serde_json::Error),
65    #[error("Invalid name")]
66    InvalidName,
67    #[error("Invalid time")]
68    InvalidTime,
69    #[error("Missing name in record at index {0}")]
70    MissingName(usize),
71    #[error("Invalid name in record named at index {0}")]
72    InvalidNameInRecord(usize),
73    #[error("Invalid time in record at index {0}")]
74    InvalidTimeInRecord(usize),
75    #[error("All records must have the same version number")]
76    DifferentBaseVersion,
77    #[error("Only one kind of value per record at index {0}")]
78    OnlyOneValuePerRecord(usize),
79    #[error("Invalid base64 value in record at index {0}")]
80    InvalidBase64Value(#[from] base64::DecodeError),
81    #[error("Positive version number required")]
82    InvalidVersionNumber,
83}
84
85#[derive(Deserialize, Debug, Clone)]
86struct SenMLRecord {
87    #[serde(rename = "bn")]
88    base_name: Option<String>,
89
90    #[serde(rename = "bt")]
91    base_time: Option<f64>,
92
93    #[serde(rename = "bu")]
94    base_unit: Option<String>,
95
96    #[serde(rename = "bv")]
97    base_value: Option<f64>,
98
99    #[serde(rename = "bs")]
100    base_sum: Option<f64>,
101
102    #[serde(rename = "bver")]
103    base_version: Option<u64>,
104
105    #[serde(rename = "n")]
106    name: Option<String>,
107
108    #[serde(rename = "u")]
109    unit: Option<String>,
110
111    #[serde(rename = "v")]
112    value: Option<f64>,
113
114    #[serde(rename = "vs")]
115    string_value: Option<String>,
116
117    #[serde(rename = "vb")]
118    bool_value: Option<bool>,
119
120    #[serde(rename = "vd")]
121    data_value: Option<String>,
122
123    #[serde(rename = "s")]
124    sum: Option<f64>,
125
126    #[serde(rename = "t")]
127    time: Option<f64>,
128
129    #[serde(rename = "ut")]
130    update_time: Option<f64>,
131
132    #[serde(flatten, default)]
133    extra_fields: Option<HashMap<String, serde_json::Value>>,
134}
135
136/// A SenML Value Field.
137///
138/// SenML can contain multiple types of values:
139/// - Floating point
140/// - Boolean
141/// - String
142/// - Data (binary)
143///
144/// This enum represents all the possible values.
145#[derive(Debug, PartialEq, Clone)]
146pub enum SenMLValueField {
147    BooleanValue(bool),
148    StringValue(String),
149    DataValue(Vec<u8>),
150    FloatingPoint(f64),
151}
152
153impl SenMLValueField {
154    pub fn as_bool(&self) -> Option<&bool> {
155        if let SenMLValueField::BooleanValue(ref value) = *self {
156            Some(value)
157        } else {
158            None
159        }
160    }
161
162    pub fn as_string(&self) -> Option<&String> {
163        if let SenMLValueField::StringValue(ref value) = *self {
164            Some(value)
165        } else {
166            None
167        }
168    }
169
170    pub fn as_data(&self) -> Option<&Vec<u8>> {
171        if let SenMLValueField::DataValue(ref value) = *self {
172            Some(value)
173        } else {
174            None
175        }
176    }
177
178    pub fn as_float(&self) -> Option<&f64> {
179        if let SenMLValueField::FloatingPoint(ref value) = *self {
180            Some(value)
181        } else {
182            None
183        }
184    }
185}
186
187impl serde::ser::Serialize for SenMLValueField {
188    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189    where
190        S: Serializer,
191    {
192        let mut state = serializer.serialize_struct("SenMLValueField", 1)?;
193        match *self {
194            SenMLValueField::BooleanValue(ref value) => state.serialize_field("vb", value)?,
195            SenMLValueField::StringValue(ref value) => state.serialize_field("vs", value)?,
196            SenMLValueField::FloatingPoint(ref value) => {
197                if value.fract() == 0.0 {
198                    state.serialize_field("v", &(*value as i64))?
199                } else {
200                    state.serialize_field("v", value)?
201                }
202            }
203            SenMLValueField::DataValue(ref value) => state.serialize_field(
204                "vd",
205                &base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(value),
206            )?,
207        }
208        state.end()
209    }
210}
211
212fn serialize_datetime<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
213where
214    S: Serializer,
215{
216    let (timestamp, precise_timestamp) = datetime_to_timestamp(date);
217    if let Some(precise_timestamp) = precise_timestamp {
218        return serializer.serialize_f64(precise_timestamp);
219    }
220    serializer.serialize_i64(timestamp)
221}
222
223/// SenML Resolved Record.
224///
225/// A SenML Record that is extracted from a SenML Pack and has all the
226/// fields resolved, meaning that all the base fields are applied to the
227/// record.
228///
229/// This can be serialised to JSON using serde.
230///
231/// Please note that this is not the most compact SenML representation,
232/// but it is a compatible one.
233/// <https://www.rfc-editor.org/rfc/rfc8428#section-4.6>
234#[derive(Serialize, Debug, PartialEq, Clone)]
235pub struct SenMLResolvedRecord {
236    /// The name of the record.
237    ///
238    /// This is the concatenation of the base name and the name.
239    /// The name is always present and cannot be an empty string.
240    #[serde(rename = "n")]
241    pub name: String,
242
243    /// The unit of the record.
244    ///
245    /// The unit is optional and is preferably present.
246    /// It should be a SI unit if possible and use the units
247    /// defined in the SenML unit registries.
248    /// <https://www.rfc-editor.org/rfc/rfc8428.html#section-12.1>
249    /// <https://www.rfc-editor.org/rfc/rfc8798.html>
250    #[serde(rename = "u", skip_serializing_if = "Option::is_none")]
251    pub unit: Option<String>,
252
253    /// The value of the record.
254    ///
255    /// The value is optional as the record can also contain a `sum`.
256    /// The value defaults to 0.0 if both the sum and the value are missing.
257    #[serde(flatten)]
258    pub value: Option<SenMLValueField>,
259
260    /// Integrated sum of the values over time.
261    ///
262    /// This field should have been named "integral" according to the RFC
263    /// but is named "sum" for historical reasons.
264    /// Optional.
265    #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
266    pub sum: Option<f64>,
267
268    /// Time when the value was recorded.
269    ///
270    /// This is a UTC DateTime that is always present.
271    /// It defaults to the current time of the system.
272    #[serde(rename = "t", serialize_with = "serialize_datetime")]
273    pub time: DateTime<Utc>,
274
275    /// Period of time in seconds that represents the maximum time
276    /// before the sensor will provided and updated reading for a measurement.
277    ///
278    /// Optional. This can be used to detect the failure of sensors or
279    /// the communications path from the sensor.
280    #[serde(rename = "ut", skip_serializing_if = "Option::is_none")]
281    pub update_time: Option<f64>,
282
283    /// Version number of the media type format.
284    ///
285    /// This field is an optional positive integer and defaults to 10 if not present.
286    #[serde(rename = "bver", skip_serializing_if = "Option::is_none")]
287    pub base_version: Option<u64>,
288
289    /// Extra fields that are not part of the SenML specification but
290    /// are allowed to be present and were in the JSON records.
291    #[serde(flatten, skip_serializing_if = "Option::is_none")]
292    pub extra_fields: Option<HashMap<String, serde_json::Value>>,
293}
294
295impl SenMLResolvedRecord {
296    pub fn get_bool_value(&self) -> Option<bool> {
297        self.value.as_ref().and_then(|v| v.as_bool().copied())
298    }
299
300    pub fn get_string_value(&self) -> Option<&String> {
301        self.value.as_ref().and_then(|v| v.as_string())
302    }
303
304    pub fn get_data_value(&self) -> Option<&Vec<u8>> {
305        self.value.as_ref().and_then(|v| v.as_data())
306    }
307
308    pub fn get_float_value(&self) -> Option<f64> {
309        self.value.as_ref().and_then(|v| v.as_float().copied())
310    }
311}
312
313fn resolve_value(
314    record: &SenMLRecord,
315    base_value: &Option<f64>,
316    index: usize,
317) -> Result<Option<SenMLValueField>, SinditSenMLError> {
318    match record.value {
319        Some(value) => {
320            if record.string_value.is_some()
321                || record.bool_value.is_some()
322                || record.data_value.is_some()
323            {
324                return Err(SinditSenMLError::OnlyOneValuePerRecord(index));
325            }
326            match base_value {
327                Some(base_value) => Ok(Some(SenMLValueField::FloatingPoint(base_value + value))),
328                None => Ok(Some(SenMLValueField::FloatingPoint(value))),
329            }
330        }
331        None => match record.string_value {
332            Some(ref value) => {
333                if record.bool_value.is_some() || record.data_value.is_some() {
334                    return Err(SinditSenMLError::OnlyOneValuePerRecord(index));
335                }
336                Ok(Some(SenMLValueField::StringValue(value.to_string())))
337            }
338            None => match record.bool_value {
339                Some(ref value) => {
340                    if record.data_value.is_some() {
341                        return Err(SinditSenMLError::OnlyOneValuePerRecord(index));
342                    }
343                    Ok(Some(SenMLValueField::BooleanValue(*value)))
344                }
345                None => match record.data_value {
346                    Some(ref value) => {
347                        match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(value) {
348                            Ok(value) => Ok(Some(SenMLValueField::DataValue(value))),
349                            Err(base64_error) => {
350                                Err(SinditSenMLError::InvalidBase64Value(base64_error))
351                            }
352                        }
353                    }
354                    None => match base_value {
355                        Some(base_value) => Ok(Some(SenMLValueField::FloatingPoint(*base_value))),
356                        None => Ok(None),
357                    },
358                },
359            },
360        },
361    }
362}
363
364fn resolve_records(
365    input_records: &[SenMLRecord],
366    now: DateTime<Utc>,
367) -> Result<Vec<SenMLResolvedRecord>, SinditSenMLError> {
368    let mut base_name: Option<String> = None;
369    let mut base_time: Option<f64> = None;
370    let mut base_unit: Option<String> = None;
371    let mut base_value: Option<f64> = None;
372    let mut base_sum: Option<f64> = None;
373    let mut base_version: Option<u64> = None;
374
375    input_records
376        .iter()
377        .enumerate()
378        .map(|(index, record)| {
379            if let Some(ref record_base_name) = record.base_name {
380                base_name = Some(record_base_name.to_string());
381            }
382
383            if let Some(record_base_time) = record.base_time {
384                base_time = Some(record_base_time);
385            }
386
387            if let Some(ref record_base_unit) = record.base_unit {
388                base_unit = Some(record_base_unit.to_string());
389            }
390
391            if let Some(record_base_value) = record.base_value {
392                base_value = Some(record_base_value);
393            }
394
395            if let Some(record_base_sum) = record.base_sum {
396                base_sum = Some(record_base_sum);
397            }
398
399            match record.base_version {
400                Some(record_base_version) => match base_version {
401                    Some(base_version) => {
402                        if base_version != record_base_version {
403                            return Err(SinditSenMLError::DifferentBaseVersion);
404                        }
405                    }
406                    None => {
407                        if record_base_version == 0 {
408                            return Err(SinditSenMLError::InvalidVersionNumber);
409                        }
410                        base_version = Some(record_base_version);
411                    }
412                },
413                None => {
414                    // We default to 10 if no base version is present.
415                    // This is the default in the RFC.
416                    if base_version.is_none() {
417                        base_version = Some(10);
418                    }
419                }
420            };
421
422            let name = match record.name {
423                Some(ref name) => match base_name {
424                    Some(ref base_name) => base_name.to_string() + name,
425                    None => name.to_string(),
426                },
427                None => match base_name {
428                    Some(ref base_name) => base_name.to_string(),
429                    None => return Err(SinditSenMLError::MissingName(index)),
430                },
431            };
432
433            if !validate_name(&name) {
434                return Err(SinditSenMLError::InvalidNameInRecord(index));
435            }
436
437            let unit: Option<String> = match record.unit {
438                Some(ref unit) => Some(unit.to_string()),
439                None => base_unit.clone(),
440            };
441
442            let mut value = resolve_value(record, &base_value, index)?;
443
444            let time = match record.time {
445                Some(time) => match base_time {
446                    Some(base_time) => base_time + time,
447                    None => time,
448                },
449                None => base_time.unwrap_or(0.0),
450            };
451            let datetime = match time::convert_senml_time(time, now) {
452                Some(datetime) => datetime,
453                None => return Err(SinditSenMLError::InvalidTimeInRecord(index)),
454            };
455
456            let sum = match record.sum {
457                Some(sum) => match base_sum {
458                    Some(base_sum) => Some(base_sum + sum),
459                    None => Some(sum),
460                },
461                None => base_sum,
462            };
463
464            if value.is_none() && sum.is_none() {
465                // return Err(SinditSenMLError::MissingValueOrSum(index));
466                // My understanding of the RFC:
467                // A sum or a value must be present and never at the same time.
468                // Both defaults to 0, but if no base sum or sum are present,
469                // then it has to be a value because it is accepted to not have
470                // a sum value in the RFC.
471                // the default value is 0.
472                value = Some(SenMLValueField::FloatingPoint(0.0));
473            }
474
475            // Version 10 is the default in SenML.
476            // However the RFC says:
477            //   The Base Version field MUST NOT be present in resolved Records if the
478            //   SenML version defined in this document is used; otherwise, it MUST be
479            //   present in all the resolved SenML Records.
480            //
481            // We interpret this as it must be skipped.
482            // let record_base_version = base_version.unwrap_or(10); //
483            let record_base_version = match base_version {
484                Some(base_version) => match base_version {
485                    10 => None,
486                    _ => Some(base_version),
487                },
488                None => None,
489            };
490
491            let update_time = record.update_time;
492
493            // skip extra_fields if the record has empty hashmap or None
494            let extra_fields = match &record.extra_fields {
495                Some(extra_fields) => {
496                    if extra_fields.is_empty() {
497                        None
498                    } else {
499                        Some(extra_fields.clone())
500                    }
501                }
502                None => None,
503            };
504
505            Ok(SenMLResolvedRecord {
506                name,
507                unit,
508                value,
509                sum,
510                time: datetime,
511                update_time,
512                base_version: record_base_version,
513                extra_fields,
514            })
515        })
516        .collect()
517}
518
519/// Parse SenML JSON and return SenMLResolvedRecords.
520///
521/// # Arguments
522/// * `json_str` - The SenML JSON string to parse.
523/// * `now` - The current time. Defaults to current UTC time.
524/// # Returns
525/// * `Result<Vec<SenMLResolvedRecord>, SinditSenMLError>` - The parsed SenML records.
526/// # Examples
527/// ```
528/// use sindit_senml::parse_json;
529///
530/// let json_str = r#"[{"n": "temperature", "v": 42.0}]"#;
531/// let records = parse_json(json_str, None).unwrap();
532/// assert_eq!(records[0].name, "temperature");
533/// assert_eq!(records[0].get_float_value(), Some(42.0));
534/// ```
535///
536pub fn parse_json(
537    json_str: &str,
538    now: Option<DateTime<Utc>>,
539) -> Result<Vec<SenMLResolvedRecord>, SinditSenMLError> {
540    let records: Vec<SenMLRecord> = match serde_json::from_str(json_str) {
541        Ok(records) => records,
542        Err(error) => return Err(SinditSenMLError::InvalidJSON(error)),
543    };
544
545    resolve_records(&records, now.unwrap_or(Utc::now()))
546}
547
548#[cfg(test)]
549mod tests {
550
551    use crate::*;
552
553    static EMPTY_RECORD: SenMLRecord = SenMLRecord {
554        base_name: None,
555        base_time: None,
556        base_unit: None,
557        base_value: None,
558        base_sum: None,
559        base_version: None,
560        name: None,
561        unit: None,
562        value: None,
563        string_value: None,
564        bool_value: None,
565        data_value: None,
566        sum: None,
567        time: None,
568        update_time: None,
569        extra_fields: None,
570    };
571
572    #[test]
573    fn test_resolve_value_simple() {
574        // None value
575
576        assert!(resolve_value(&EMPTY_RECORD, &None, 0).unwrap().is_none());
577
578        // Floating point value
579        let mut record = EMPTY_RECORD.clone();
580        record.value = Some(42.0);
581        assert_eq!(
582            resolve_value(&record, &None, 0,).unwrap().unwrap(),
583            SenMLValueField::FloatingPoint(42.0)
584        );
585
586        // String value
587        let mut record = EMPTY_RECORD.clone();
588        record.string_value = Some("Hello world!".to_string());
589        assert_eq!(
590            resolve_value(&record, &None, 0,).unwrap().unwrap(),
591            SenMLValueField::StringValue("Hello world!".to_string())
592        );
593
594        // Boolean value true
595        let mut record = EMPTY_RECORD.clone();
596        record.bool_value = Some(true);
597        assert_eq!(
598            resolve_value(&record, &None, 0,).unwrap().unwrap(),
599            SenMLValueField::BooleanValue(true)
600        );
601
602        // Boolean value false
603        let mut record = EMPTY_RECORD.clone();
604        record.bool_value = Some(false);
605        assert_eq!(
606            resolve_value(&record, &None, 0,).unwrap().unwrap(),
607            SenMLValueField::BooleanValue(false)
608        );
609
610        // Base64 string
611        let mut record = EMPTY_RECORD.clone();
612        record.data_value = Some("SGVsbG8gd29ybGQh".to_string());
613        assert_eq!(
614            resolve_value(&record, &None, 0,).unwrap().unwrap(),
615            SenMLValueField::DataValue("Hello world!".as_bytes().to_vec())
616        );
617    }
618
619    #[test]
620    fn test_resolve_value_base_value() {
621        // None value
622        let mut record = EMPTY_RECORD.clone();
623        record.base_value = Some(10.0); // Ignored
624        assert!(resolve_value(&record, &None, 0,).unwrap().is_none());
625
626        assert_eq!(
627            resolve_value(
628                &record,
629                &Some(10.0), // This is the one that matters
630                0,
631            )
632            .unwrap()
633            .unwrap(),
634            SenMLValueField::FloatingPoint(10.0)
635        );
636
637        // Floating point value
638        let mut record = EMPTY_RECORD.clone();
639        record.base_value = Some(10.0); // Ignored
640        record.value = Some(42.0);
641        assert_eq!(
642            resolve_value(
643                &record,
644                &Some(32.0), // This is the one that matters
645                0,
646            )
647            .unwrap()
648            .unwrap(),
649            SenMLValueField::FloatingPoint(74.0)
650        );
651
652        // Base value
653    }
654
655    #[test]
656    fn test_resolve_value_failures() {
657        // float and string
658        let mut record = EMPTY_RECORD.clone();
659        record.value = Some(42.0);
660        record.string_value = Some("Hello world!".to_string());
661        assert!(resolve_value(&record, &None, 0).is_err());
662
663        // float and bool
664        let mut record = EMPTY_RECORD.clone();
665        record.value = Some(42.0);
666        record.bool_value = Some(true);
667        assert!(resolve_value(&record, &None, 0,).is_err());
668
669        // string and bool
670        let mut record = EMPTY_RECORD.clone();
671        record.string_value = Some("Hello world!".to_string());
672        record.bool_value = Some(true);
673        assert!(resolve_value(&record, &None, 0,).is_err());
674
675        // float and base64
676        let mut record = EMPTY_RECORD.clone();
677        record.value = Some(42.0);
678        record.data_value = Some("SGVsbG8gd29ybGQh".to_string());
679        assert!(resolve_value(&record, &None, 0,).is_err());
680
681        // bool and base64
682        let mut record = EMPTY_RECORD.clone();
683        record.bool_value = Some(true);
684        record.data_value = Some("SGVsbG8gd29ybGQh".to_string());
685        assert!(resolve_value(&record, &None, 0,).is_err());
686
687        // Invalid base64
688        let mut record = EMPTY_RECORD.clone();
689        record.data_value = Some("    ".to_string());
690        assert!(resolve_value(&record, &None, 0,).is_err());
691    }
692
693    mod test_resolve_records {
694        use std::ops::Add;
695
696        use crate::{tests::EMPTY_RECORD, *};
697        use lazy_static::lazy_static;
698
699        lazy_static! {
700            static ref BASE_RECORD: SenMLRecord = SenMLRecord {
701                base_name: Some(String::from("abcd-")),
702                base_time: Some(1234567890.0),
703                base_unit: Some(String::from("Cel")),
704                base_value: Some(10.0),
705                base_sum: Some(20.0),
706                base_version: Some(10),
707                name: None,
708                unit: None,
709                value: None,
710                string_value: None,
711                bool_value: None,
712                data_value: None,
713                sum: None,
714                time: None,
715                update_time: None,
716                extra_fields: None,
717            };
718            static ref NOW: DateTime<Utc> = Utc::now();
719        }
720
721        #[test]
722        fn test_empty() {
723            assert_eq!(
724                Vec::new() as Vec<SenMLResolvedRecord>,
725                resolve_records(&[], *NOW).unwrap()
726            );
727        }
728
729        #[test]
730        fn test_single_base_recodr() {
731            let data = vec![BASE_RECORD.clone()];
732            let resolved_data = resolve_records(&data, *NOW);
733            assert!(resolved_data.is_ok());
734        }
735
736        // Two identical base records
737        #[test]
738        fn test_two_identical_base_records() {
739            let data = vec![BASE_RECORD.clone(), BASE_RECORD.clone()];
740            let resolved_data = resolve_records(&data, *NOW);
741            assert!(resolved_data.is_ok());
742        }
743
744        // Second record uses a different version
745        #[test]
746        fn test_second_record_uses_different_version() {
747            let mut second_record = BASE_RECORD.clone();
748            second_record.base_version = Some(12);
749            let data = vec![BASE_RECORD.clone(), second_record];
750            let resolved_data = resolve_records(&data, *NOW);
751            assert!(matches!(
752                resolved_data.unwrap_err(),
753                SinditSenMLError::DifferentBaseVersion
754            ));
755        }
756
757        // Name concatenation
758        #[test]
759        fn test_name_concatenation() {
760            let mut second_record = BASE_RECORD.clone();
761            second_record.name = Some("efgh".to_string());
762            let data = vec![BASE_RECORD.clone(), second_record];
763            let resolved_data = resolve_records(&data, *NOW).unwrap();
764            assert_eq!(resolved_data[0].name, "abcd-");
765            assert_eq!(resolved_data[1].name, "abcd-efgh");
766        }
767
768        // Missing name
769        #[test]
770        fn test_missing_name() {
771            let mut first_record = EMPTY_RECORD.clone();
772            first_record.name = Some("efgh".to_string());
773            first_record.value = Some(10.0);
774            let mut second_record = EMPTY_RECORD.clone();
775            second_record.value = Some(10.0);
776            let data = vec![first_record, second_record];
777            let resolved_data = resolve_records(&data, *NOW);
778            assert!(matches!(
779                resolved_data.unwrap_err(),
780                SinditSenMLError::MissingName(1)
781            ));
782        }
783
784        #[test]
785        fn test_invalid_name() {
786            let mut first_record = EMPTY_RECORD.clone();
787            first_record.name = Some("   ".to_string());
788            first_record.value = Some(10.0);
789            let data = vec![first_record];
790            let resolved_data = resolve_records(&data, *NOW);
791            assert!(matches!(
792                resolved_data.unwrap_err(),
793                SinditSenMLError::InvalidNameInRecord(0)
794            ));
795        }
796
797        #[test]
798        fn test_units() {
799            let mut second_record = BASE_RECORD.clone();
800            second_record.unit = Some("F".to_string());
801            let data = vec![BASE_RECORD.clone(), second_record];
802            let resolved_data = resolve_records(&data, *NOW).unwrap();
803            assert_eq!(resolved_data[0].unit, Some("Cel".to_string()));
804            assert_eq!(resolved_data[1].unit, Some("F".to_string()));
805        }
806
807        #[test]
808        fn test_basetime() {
809            let mut first_record = EMPTY_RECORD.clone();
810            first_record.time = Some(1111111111.1);
811            first_record.name = Some("efgh".to_string());
812            first_record.value = Some(10.0);
813            let mut second_record = BASE_RECORD.clone();
814            second_record.base_time = Some(2222222222.2);
815            let mut third_record = EMPTY_RECORD.clone();
816            third_record.time = Some(3333333333.3);
817            let data = vec![first_record, second_record, third_record];
818            let resolved_data = resolve_records(&data, *NOW).unwrap();
819            assert_eq!(resolved_data[0].time.timestamp(), 1111111111);
820            assert_eq!(resolved_data[1].time.timestamp(), 2222222222);
821            assert_eq!(resolved_data[2].time.timestamp(), 5555555555);
822        }
823
824        #[test]
825        fn test_relative_time() {
826            let mut first_record = BASE_RECORD.clone();
827            first_record.base_time = None;
828            let mut second_record = EMPTY_RECORD.clone();
829            second_record.time = Some(12.0);
830            let data = vec![first_record, second_record];
831            let resolved_data = resolve_records(&data, *NOW).unwrap();
832            let now_in_12_seconds = NOW.add(chrono::Duration::seconds(12)).timestamp();
833            assert_eq!(resolved_data[0].time.timestamp(), NOW.timestamp());
834            assert_eq!(resolved_data[1].time.timestamp(), now_in_12_seconds);
835        }
836
837        #[test]
838        fn test_invalid_time() {
839            let mut first_record = EMPTY_RECORD.clone();
840            // NaN time ?
841            first_record.time = Some(f64::NAN);
842            first_record.name = Some("efgh".to_string());
843            first_record.value = Some(10.0);
844            let data = vec![first_record];
845            let resolved_data = resolve_records(&data, *NOW);
846            assert!(matches!(
847                resolved_data.unwrap_err(),
848                SinditSenMLError::InvalidTimeInRecord(0)
849            ));
850        }
851
852        #[test]
853        fn test_sum() {
854            let mut first_record = EMPTY_RECORD.clone();
855            first_record.name = Some("efgh".to_string());
856            first_record.sum = Some(5.0);
857            let mut second_record = BASE_RECORD.clone();
858            second_record.base_sum = Some(10.0);
859            let mut third_record = EMPTY_RECORD.clone();
860            third_record.sum = Some(20.0);
861            let data = vec![first_record, second_record, third_record];
862            let resolved_data = resolve_records(&data, *NOW).unwrap();
863            assert_eq!(resolved_data[0].sum, Some(5.0));
864            assert_eq!(resolved_data[1].sum, Some(10.0));
865            assert_eq!(resolved_data[2].sum, Some(30.0));
866        }
867
868        #[test]
869        fn test_missing_value_or_sum() {
870            let mut record = EMPTY_RECORD.clone();
871            record.name = Some("efgh".to_string());
872            let data = vec![record];
873            let resolved_data = resolve_records(&data, *NOW);
874            assert_eq!(
875                resolved_data.unwrap()[0].value,
876                Some(SenMLValueField::FloatingPoint(0.0))
877            );
878        }
879
880        #[test]
881        fn test_two_value_fields() {
882            let mut record = EMPTY_RECORD.clone();
883            record.name = Some("efgh".to_string());
884            record.value = Some(10.0);
885            record.string_value = Some("Hello world!".to_string());
886            let data = vec![record];
887            let resolved_data = resolve_records(&data, *NOW);
888            assert!(matches!(
889                resolved_data.unwrap_err(),
890                SinditSenMLError::OnlyOneValuePerRecord(0)
891            ));
892        }
893
894        #[test]
895        fn test_no_units_is_fine() {
896            let mut record = EMPTY_RECORD.clone();
897            record.name = Some("efgh".to_string());
898            record.value = Some(10.0);
899            record.unit = None;
900            let data = vec![record];
901            let resolved_data = resolve_records(&data, *NOW);
902            assert!(resolved_data.is_ok());
903        }
904
905        #[test]
906        fn test_extra_fields_are_preserved() {
907            let mut record = BASE_RECORD.clone();
908            record.extra_fields =
909                Some(serde_json::from_str(r#"{"extra_field": "extra_value"}"#).unwrap());
910            let data = vec![record];
911            let resolved_data = resolve_records(&data, *NOW).unwrap();
912            assert_eq!(
913                resolved_data[0].extra_fields,
914                Some(serde_json::from_str(r#"{"extra_field": "extra_value"}"#).unwrap())
915            );
916        }
917
918        #[test]
919        fn test_empty_extra_fields_are_skipped() {
920            let mut record = BASE_RECORD.clone();
921            record.extra_fields = Some(serde_json::from_str(r#"{}"#).unwrap());
922            let data = vec![record];
923            let resolved_data = resolve_records(&data, *NOW).unwrap();
924            assert_eq!(resolved_data[0].extra_fields, None);
925        }
926
927        #[test]
928        fn test_resolver_helpers() {
929            let mut records = resolve_records(std::slice::from_ref(&*BASE_RECORD), *NOW).unwrap();
930            let mut record = records.pop().unwrap();
931            // None, defaults to the float value
932            assert_eq!(record.get_bool_value(), None);
933            assert_eq!(record.get_string_value(), None);
934            assert_eq!(record.get_data_value(), None);
935            assert_eq!(record.get_float_value(), Some(10.0));
936            // Boolean
937            record.value = Some(SenMLValueField::BooleanValue(true));
938            assert_eq!(record.get_bool_value(), Some(true));
939            assert_eq!(record.get_string_value(), None);
940            assert_eq!(record.get_data_value(), None);
941            assert_eq!(record.get_float_value(), None);
942            // String
943            record.value = Some(SenMLValueField::StringValue("Hello world!".to_string()));
944            assert_eq!(record.get_bool_value(), None);
945            assert_eq!(record.get_string_value(), Some(&"Hello world!".to_string()));
946            assert_eq!(record.get_data_value(), None);
947            assert_eq!(record.get_float_value(), None);
948            // Data
949            record.value = Some(SenMLValueField::DataValue(Vec::from(
950                "Hello world!".as_bytes(),
951            )));
952            assert_eq!(record.get_bool_value(), None);
953            assert_eq!(record.get_string_value(), None);
954            assert_eq!(
955                record.get_data_value(),
956                Some(&Vec::from("Hello world!".as_bytes()))
957            );
958            assert_eq!(record.get_float_value(), None);
959            // Float
960            record.value = Some(SenMLValueField::FloatingPoint(10.0));
961            assert_eq!(record.get_bool_value(), None);
962            assert_eq!(record.get_string_value(), None);
963            assert_eq!(record.get_data_value(), None);
964            assert_eq!(record.get_float_value(), Some(10.0));
965        }
966
967        #[test]
968        fn test_zero_version_number() {
969            let mut record = BASE_RECORD.clone();
970            record.base_version = Some(0);
971            let data = vec![record];
972            assert!(matches!(
973                resolve_records(&data, *NOW).unwrap_err(),
974                SinditSenMLError::InvalidVersionNumber
975            ));
976        }
977    }
978
979    mod test_parse_json {
980        use crate::*;
981        use chrono::Utc;
982
983        #[test]
984        fn test_empty() {
985            let data = "[]";
986            let resolved_data = parse_json(data, None);
987            assert!(resolved_data.is_ok());
988            assert_eq!(resolved_data.unwrap(), Vec::new());
989        }
990
991        #[test]
992        fn test_single_record() {
993            let data = r#"[{"n": "abcd", "v": 10.0}]"#;
994            let now = Utc::now();
995            let resolved_data = parse_json(data, Some(now));
996            assert!(resolved_data.is_ok());
997            assert_eq!(
998                resolved_data.unwrap(),
999                vec![SenMLResolvedRecord {
1000                    name: "abcd".to_string(),
1001                    unit: None,
1002                    value: Some(SenMLValueField::FloatingPoint(10.0)),
1003                    sum: None,
1004                    time: now,
1005                    update_time: None,
1006                    base_version: None,
1007                    extra_fields: None,
1008                }]
1009            );
1010        }
1011
1012        #[test]
1013        fn test_multiple_records() {
1014            let data = r#"[{"n": "abcd", "v": 10.0}, {"n": "efgh", "v": 20.0, "t": 1.5}]"#;
1015            let now = Utc::now();
1016            let now_in_1_5_seconds =
1017                now + chrono::Duration::seconds(1) + chrono::Duration::milliseconds(500);
1018            let resolved_data = parse_json(data, Some(now));
1019            assert!(resolved_data.is_ok());
1020            assert_eq!(
1021                resolved_data.unwrap(),
1022                vec![
1023                    SenMLResolvedRecord {
1024                        name: "abcd".to_string(),
1025                        unit: None,
1026                        value: Some(SenMLValueField::FloatingPoint(10.0)),
1027                        sum: None,
1028                        time: now,
1029                        update_time: None,
1030                        base_version: None,
1031                        extra_fields: None,
1032                    },
1033                    SenMLResolvedRecord {
1034                        name: "efgh".to_string(),
1035                        unit: None,
1036                        value: Some(SenMLValueField::FloatingPoint(20.0)),
1037                        sum: None,
1038                        time: now_in_1_5_seconds,
1039                        update_time: None,
1040                        base_version: None,
1041                        extra_fields: None,
1042                    }
1043                ]
1044            );
1045        }
1046
1047        #[test]
1048        fn test_record_with_extra_fields() {
1049            let data = r#"[{"n": "abcd", "v": 10.0, "extra_field": "extra_value"}]"#;
1050            let now = Utc::now();
1051            let resolved_data = parse_json(data, Some(now));
1052            assert!(resolved_data.is_ok());
1053            assert_eq!(
1054                resolved_data.unwrap(),
1055                vec![SenMLResolvedRecord {
1056                    name: "abcd".to_string(),
1057                    unit: None,
1058                    value: Some(SenMLValueField::FloatingPoint(10.0)),
1059                    sum: None,
1060                    time: now,
1061                    update_time: None,
1062                    base_version: None,
1063                    extra_fields: Some(
1064                        serde_json::from_str(r#"{"extra_field": "extra_value"}"#).unwrap()
1065                    ),
1066                }]
1067            );
1068        }
1069
1070        #[test]
1071        fn test_invalid_json() {
1072            let data = r#"[{"n": "abcd", "v": 10.0"#;
1073            let resolved_data = parse_json(data, None);
1074            assert!(matches!(
1075                resolved_data.unwrap_err(),
1076                SinditSenMLError::InvalidJSON(_)
1077            ));
1078        }
1079    }
1080
1081    mod test_serialisation {
1082        use crate::*;
1083        use chrono::Utc;
1084
1085        #[test]
1086        fn test_serialise_empty() {
1087            let data: Vec<SenMLResolvedRecord> = Vec::new();
1088            let serialised_data = serde_json::to_string(&data).unwrap();
1089            assert_eq!(serialised_data, "[]");
1090        }
1091
1092        #[test]
1093        fn test_serialise_single_record() {
1094            let time = DateTime::<Utc>::from_timestamp(1234567890, 1234 * 100_000_u32).unwrap();
1095            let data = vec![SenMLResolvedRecord {
1096                name: "abcd".to_string(),
1097                unit: None,
1098                value: Some(SenMLValueField::FloatingPoint(10.3)),
1099                sum: None,
1100                time,
1101                update_time: None,
1102                base_version: Some(12),
1103                extra_fields: None,
1104            }];
1105            let serialised_data = serde_json::to_string(&data).unwrap();
1106            assert_eq!(
1107                serialised_data,
1108                r#"[{"n":"abcd","v":10.3,"t":1234567890.1234,"bver":12}]"#
1109            );
1110        }
1111
1112        #[test]
1113        fn test_serialise_multiple_records() {
1114            let time = DateTime::<Utc>::from_timestamp(1234567890, 1234 * 100_000_u32).unwrap();
1115            let data = vec![
1116                SenMLResolvedRecord {
1117                    name: "abcd".to_string(),
1118                    unit: None,
1119                    value: Some(SenMLValueField::FloatingPoint(10f64)),
1120                    sum: None,
1121                    time,
1122                    update_time: None,
1123                    base_version: None,
1124                    extra_fields: Some(
1125                        serde_json::from_str(r#"{"extra_field": "extra_value"}"#).unwrap(),
1126                    ),
1127                },
1128                SenMLResolvedRecord {
1129                    name: "efgh".to_string(),
1130                    unit: None,
1131                    value: Some(SenMLValueField::DataValue(Vec::from(
1132                        "Hello world!".as_bytes(),
1133                    ))),
1134                    sum: None,
1135                    time,
1136                    update_time: None,
1137                    base_version: None,
1138                    extra_fields: Some(serde_json::from_str(r#"{"no":false}"#).unwrap()),
1139                },
1140                SenMLResolvedRecord {
1141                    name: "ijkl".to_string(),
1142                    unit: None,
1143                    value: Some(SenMLValueField::BooleanValue(true)),
1144                    sum: None,
1145                    time,
1146                    update_time: None,
1147                    base_version: None,
1148                    extra_fields: None,
1149                },
1150                SenMLResolvedRecord {
1151                    name: "mnop".to_string(),
1152                    unit: None,
1153                    value: Some(SenMLValueField::StringValue("Hello world!".to_string())),
1154                    sum: None,
1155                    time,
1156                    update_time: None,
1157                    base_version: None,
1158                    extra_fields: None,
1159                },
1160            ];
1161            let serialised_data = serde_json::to_string(&data).unwrap();
1162            assert_eq!(
1163                serialised_data,
1164                r#"[{"n":"abcd","v":10,"t":1234567890.1234,"extra_field":"extra_value"},{"n":"efgh","vd":"SGVsbG8gd29ybGQh","t":1234567890.1234,"no":false},{"n":"ijkl","vb":true,"t":1234567890.1234},{"n":"mnop","vs":"Hello world!","t":1234567890.1234}]"#
1165            );
1166        }
1167        #[test]
1168        fn test_base64_urlsafe() {
1169            let data = vec![SenMLResolvedRecord {
1170                name: "abcd".to_string(),
1171                unit: None,
1172                value: Some(SenMLValueField::DataValue(Vec::from(
1173                    "light work".as_bytes(),
1174                ))),
1175                sum: None,
1176                time: DateTime::<Utc>::from_timestamp(1234567890, 1234 * 100_000_u32).unwrap(),
1177                update_time: None,
1178                base_version: Some(11),
1179                extra_fields: None,
1180            }];
1181            let serialised_data = serde_json::to_string(&data).unwrap();
1182            let parsed_data: serde_json::Value = serde_json::from_str(&serialised_data).unwrap();
1183            assert_eq!(
1184                parsed_data[0]["vd"],
1185                serde_json::Value::String("bGlnaHQgd29yaw".to_string())
1186            );
1187            let parsed_records = parse_json(&serialised_data, None).unwrap();
1188            assert_eq!(parsed_records[0].get_data_value().unwrap(), b"light work");
1189
1190            let data = vec![SenMLResolvedRecord {
1191                name: "abcd".to_string(),
1192                unit: None,
1193                value: Some(SenMLValueField::DataValue(b"//\xC2\xBB".to_vec())),
1194                sum: None,
1195                time: DateTime::<Utc>::from_timestamp(1234567890, 1234 * 100_000_u32).unwrap(),
1196                update_time: None,
1197                base_version: Some(11),
1198                extra_fields: None,
1199            }];
1200            let serialised_data = serde_json::to_string(&data).unwrap();
1201            let parsed_data: serde_json::Value = serde_json::from_str(&serialised_data).unwrap();
1202            assert_eq!(
1203                parsed_data[0]["vd"],
1204                serde_json::Value::String("Ly_Cuw".to_string())
1205            );
1206            let parsed_records = parse_json(&serialised_data, None).unwrap();
1207            assert_eq!(parsed_records[0].get_data_value().unwrap(), b"//\xC2\xBB");
1208        }
1209    }
1210
1211    mod test_crate_documentation_examples {
1212        #[test]
1213        fn test_example_parsing() {
1214            use crate::parse_json;
1215
1216            let json_str = r#"[{"n": "temperature", "v": 42.0}]"#;
1217            let records = parse_json(json_str, None).unwrap();
1218            assert_eq!(records[0].name, "temperature");
1219            assert_eq!(records[0].get_float_value(), Some(42.0));
1220        }
1221
1222        #[test]
1223        fn test_example_serialisation() {
1224            use crate::SenMLResolvedRecord;
1225
1226            let record = SenMLResolvedRecord {
1227                name: "temperature".to_string(),
1228                unit: Some("Cel".to_string()),
1229                value: Some(crate::SenMLValueField::FloatingPoint(42f64)),
1230                sum: None,
1231                time: chrono::DateTime::<chrono::Utc>::from_timestamp(1234567890, 0).unwrap(),
1232                update_time: None,
1233                base_version: None,
1234                extra_fields: None,
1235            };
1236            let json = serde_json::to_string(&vec![record]).unwrap();
1237            assert_eq!(
1238                json,
1239                r#"[{"n":"temperature","u":"Cel","v":42,"t":1234567890}]"#
1240            );
1241        }
1242    }
1243}