1use 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#[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#[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#[derive(Serialize, Debug, PartialEq, Clone)]
235pub struct SenMLResolvedRecord {
236 #[serde(rename = "n")]
241 pub name: String,
242
243 #[serde(rename = "u", skip_serializing_if = "Option::is_none")]
251 pub unit: Option<String>,
252
253 #[serde(flatten)]
258 pub value: Option<SenMLValueField>,
259
260 #[serde(rename = "s", skip_serializing_if = "Option::is_none")]
266 pub sum: Option<f64>,
267
268 #[serde(rename = "t", serialize_with = "serialize_datetime")]
273 pub time: DateTime<Utc>,
274
275 #[serde(rename = "ut", skip_serializing_if = "Option::is_none")]
281 pub update_time: Option<f64>,
282
283 #[serde(rename = "bver", skip_serializing_if = "Option::is_none")]
287 pub base_version: Option<u64>,
288
289 #[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 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 value = Some(SenMLValueField::FloatingPoint(0.0));
473 }
474
475 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 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
519pub 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 assert!(resolve_value(&EMPTY_RECORD, &None, 0).unwrap().is_none());
577
578 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 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 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 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 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 let mut record = EMPTY_RECORD.clone();
623 record.base_value = Some(10.0); assert!(resolve_value(&record, &None, 0,).unwrap().is_none());
625
626 assert_eq!(
627 resolve_value(
628 &record,
629 &Some(10.0), 0,
631 )
632 .unwrap()
633 .unwrap(),
634 SenMLValueField::FloatingPoint(10.0)
635 );
636
637 let mut record = EMPTY_RECORD.clone();
639 record.base_value = Some(10.0); record.value = Some(42.0);
641 assert_eq!(
642 resolve_value(
643 &record,
644 &Some(32.0), 0,
646 )
647 .unwrap()
648 .unwrap(),
649 SenMLValueField::FloatingPoint(74.0)
650 );
651
652 }
654
655 #[test]
656 fn test_resolve_value_failures() {
657 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 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 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 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 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 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 #[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 #[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 #[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 #[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 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 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 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 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 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 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}