1use std::borrow::Borrow;
23use std::fmt::Display;
24use std::ops::Deref;
25
26use astarte_interfaces::schema::MappingType;
27use bson::Bson;
28use serde::Serialize;
29
30use crate::Timestamp;
31
32use self::de::{ArrayType, bson_array};
33
34pub(crate) mod de;
35mod display;
36
37macro_rules! check_astype_match {
38 ( $self:ident, $other:ident, {$( $variant:tt ,)*}) => {
39 match ($self, $other) {
40 $((AstarteData::$variant(_), ::astarte_interfaces::schema::MappingType::$variant) => true,)*
41 _ => false,
42 }
43 };
44}
45
46macro_rules! impl_type_conversion_traits {
48 ( {$( ($typ:ty, $variant:tt) ,)*}) => {
49 $(
50 impl From<$typ> for AstarteData {
51 fn from(d: $typ) -> Self {
52 AstarteData::$variant(d.into())
53 }
54 }
55
56 impl PartialEq<$typ> for AstarteData {
57 fn eq(&self, other: &$typ) -> bool {
58 let AstarteData::$variant(value) = self else {
59 return false;
60 };
61
62 value == other
63 }
64 }
65
66 impl PartialEq<AstarteData> for $typ {
67 fn eq(&self, other: &AstarteData) -> bool {
68 other.eq(self)
69 }
70 }
71 )*
72 };
73}
74
75macro_rules! impl_reverse_type_conversion_traits {
77 ($(($variant:tt, $typ:ty),)*) => {
78 $(
79 impl std::convert::TryFrom<AstarteData> for $typ {
80 type Error = $crate::types::TypeError;
81
82 fn try_from(var: AstarteData) -> Result<Self, Self::Error> {
83 if let AstarteData::$variant(val) = var {
84 Ok(val)
85 } else {
86 Err(Self::Error::conversion(format!("from {} into $typ", var.display_type())))
87 }
88 }
89 }
90 )*
91 }
92}
93
94#[non_exhaustive]
96#[derive(Debug, Clone, thiserror::Error)]
97pub enum TypeError {
98 #[error("forbidden floating point number, Nan, Infinite or subnormal numbers are invalid")]
100 Float,
101 #[error("couldn't convert value {ctx}")]
103 Conversion {
104 ctx: String,
106 },
107 #[error("error converting from Bson to AstarteData ({0})")]
109 FromBsonError(String),
110 #[error("type mismatch in bson array from astarte")]
112 FromBsonArrayError,
113 #[error("type mismatch for bson and mapping")]
115 InvalidType,
116}
117
118impl TypeError {
119 pub(crate) const fn conversion(ctx: String) -> Self {
121 Self::Conversion { ctx }
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize)]
147#[serde(into = "Bson")]
148pub enum AstarteData {
149 Double(Double),
154 Integer(i32),
156 Boolean(bool),
158 LongInteger(i64),
162 String(String),
164 BinaryBlob(Vec<u8>),
166 DateTime(Timestamp),
170 DoubleArray(Vec<Double>),
172 IntegerArray(Vec<i32>),
174 BooleanArray(Vec<bool>),
176 LongIntegerArray(Vec<i64>),
181 StringArray(Vec<String>),
183 BinaryBlobArray(Vec<Vec<u8>>),
185 DateTimeArray(Vec<Timestamp>),
189}
190
191impl AstarteData {
192 pub(crate) fn eq_mapping_type(&self, other: MappingType) -> bool {
193 if (other == MappingType::LongInteger || other == MappingType::Double)
194 && let AstarteData::Integer(_) = self
195 {
196 return true;
197 }
198
199 check_astype_match!(self, other, {
200 Double,
201 Integer,
202 Boolean,
203 LongInteger,
204 String,
205 BinaryBlob,
206 DateTime,
207 DoubleArray,
208 IntegerArray,
209 BooleanArray,
210 LongIntegerArray,
211 StringArray,
212 BinaryBlobArray,
213 DateTimeArray,
214 })
215 }
216
217 pub(crate) fn try_from_array(
219 array: Vec<Bson>,
220 item_type: ArrayType,
221 ) -> Result<Self, TypeError> {
222 if array.is_empty() {
223 return Ok(item_type.as_empty());
224 }
225
226 match item_type {
227 ArrayType::Double => bson_array(array, |b| b.as_f64()).and_then(AstarteData::try_from),
228 ArrayType::Integer => bson_array(array, |b| b.as_i32()).map(AstarteData::from),
229 ArrayType::Boolean => bson_array(array, |b| b.as_bool()).map(AstarteData::from),
230 ArrayType::LongInteger => {
231 bson_array(array, |b| {
232 b.as_i64().or_else(|| b.as_i32().map(i64::from))
234 })
235 .map(AstarteData::from)
236 }
237 ArrayType::String => {
238 bson_array(array, |b| match b {
240 Bson::String(s) => Some(s),
241 _ => None,
242 })
243 .map(AstarteData::from)
244 }
245 ArrayType::BinaryBlob => {
246 bson_array(array, |b| match b {
248 Bson::Binary(b) => Some(b.bytes),
249 _ => None,
250 })
251 .map(AstarteData::from)
252 }
253 ArrayType::DateTime => {
254 bson_array(array, |b| match b {
256 Bson::DateTime(d) => Some(d.to_chrono()),
257 _ => None,
258 })
259 .map(AstarteData::from)
260 }
261 }
262 }
263
264 pub(crate) fn display_type(&self) -> &'static str {
265 match self {
266 AstarteData::Double(_) => "double",
267 AstarteData::Integer(_) => "integer",
268 AstarteData::Boolean(_) => "boolean",
269 AstarteData::LongInteger(_) => "long integer",
270 AstarteData::String(_) => "string",
271 AstarteData::BinaryBlob(_) => "binary blob",
272 AstarteData::DateTime(_) => "datetime",
273 AstarteData::DoubleArray(_) => "double array",
274 AstarteData::IntegerArray(_) => "integer array",
275 AstarteData::BooleanArray(_) => "boolean array",
276 AstarteData::LongIntegerArray(_) => "long integer array",
277 AstarteData::StringArray(_) => "string array",
278 AstarteData::BinaryBlobArray(_) => "binary blob array",
279 AstarteData::DateTimeArray(_) => "datetime array",
280 }
281 }
282}
283
284impl PartialEq<f64> for AstarteData {
285 fn eq(&self, other: &f64) -> bool {
286 if let AstarteData::Double(dself) = self {
287 dself == other
288 } else {
289 false
290 }
291 }
292}
293
294impl PartialEq<AstarteData> for f64 {
295 fn eq(&self, other: &AstarteData) -> bool {
296 other.eq(self)
297 }
298}
299
300impl TryFrom<f64> for AstarteData {
301 type Error = TypeError;
302
303 fn try_from(value: f64) -> Result<Self, Self::Error> {
304 Double::try_from(value).map(AstarteData::Double)
305 }
306}
307
308impl TryFrom<Vec<f64>> for AstarteData {
309 type Error = TypeError;
310
311 fn try_from(value: Vec<f64>) -> Result<Self, Self::Error> {
312 value
313 .into_iter()
314 .map(Double::try_from)
315 .collect::<Result<Vec<Double>, Self::Error>>()
316 .map(AstarteData::DoubleArray)
317 }
318}
319
320impl TryFrom<AstarteData> for Vec<f64> {
321 type Error = TypeError;
322
323 fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
324 let AstarteData::DoubleArray(value) = value else {
325 return Err(TypeError::conversion(format!(
326 "from {} into Vec<f64>",
327 value.display_type()
328 )));
329 };
330
331 let vec = value.into_iter().map(Double::into).collect();
332
333 Ok(vec)
334 }
335}
336
337impl PartialEq<Vec<f64>> for AstarteData {
338 fn eq(&self, other: &Vec<f64>) -> bool {
339 let AstarteData::DoubleArray(this) = self else {
340 return false;
341 };
342
343 if this.len() != other.len() {
344 return false;
345 }
346
347 this.iter().zip(other).all(|(x, y)| x == y)
348 }
349}
350
351impl PartialEq<AstarteData> for Vec<f64> {
352 fn eq(&self, other: &AstarteData) -> bool {
353 other.eq(self)
354 }
355}
356
357impl TryFrom<AstarteData> for f64 {
358 type Error = TypeError;
359
360 fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
361 match value {
362 AstarteData::Double(val) => Ok(val.into()),
363 _ => Err(TypeError::conversion(format!(
364 "from {} into f64",
365 value.display_type()
366 ))),
367 }
368 }
369}
370
371impl TryFrom<AstarteData> for i64 {
372 type Error = TypeError;
373 fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
374 match value {
375 AstarteData::LongInteger(val) => Ok(val),
376 AstarteData::Integer(val) => Ok(val.into()),
377 _ => Err(TypeError::conversion(format!(
378 "from {} into i64",
379 value.display_type()
380 ))),
381 }
382 }
383}
384
385impl_type_conversion_traits!({
386 (i32, Integer),
387 (i64, LongInteger),
388 (&str, String),
389 (String, String),
390 (bool, Boolean),
391 (Double, Double),
392 (Vec<u8>, BinaryBlob),
393 (chrono::DateTime<chrono::Utc>, DateTime),
394 (Vec<i32>, IntegerArray),
395 (Vec<i64>, LongIntegerArray),
396 (Vec<bool>, BooleanArray),
397 (Vec<String>, StringArray),
398 (Vec<Vec<u8>>, BinaryBlobArray),
399 (Vec<chrono::DateTime<chrono::Utc>>, DateTimeArray),
400 (Vec<Double>, DoubleArray),
401});
402
403impl_reverse_type_conversion_traits!(
404 (Double, Double),
405 (Integer, i32),
406 (Boolean, bool),
407 (String, String),
408 (BinaryBlob, Vec<u8>),
409 (DateTime, Timestamp),
410 (IntegerArray, Vec<i32>),
411 (BooleanArray, Vec<bool>),
412 (LongIntegerArray, Vec<i64>),
413 (StringArray, Vec<String>),
414 (BinaryBlobArray, Vec<Vec<u8>>),
415 (DateTimeArray, Vec<Timestamp>),
416);
417
418#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
422#[repr(transparent)]
423pub struct Double(f64);
424
425impl TryFrom<f64> for Double {
426 type Error = TypeError;
427 fn try_from(value: f64) -> Result<Self, Self::Error> {
428 if value.is_nan() || value.is_infinite() || value.is_subnormal() {
429 return Err(Self::Error::Float);
430 }
431
432 Ok(Self(value))
433 }
434}
435
436impl From<Double> for f64 {
439 fn from(value: Double) -> Self {
440 value.0
441 }
442}
443
444impl Display for Double {
445 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446 self.0.fmt(f)
447 }
448}
449
450impl Borrow<f64> for Double {
451 fn borrow(&self) -> &f64 {
452 &self.0
453 }
454}
455
456impl Deref for Double {
457 type Target = f64;
458
459 fn deref(&self) -> &Self::Target {
460 &self.0
461 }
462}
463
464impl PartialEq<f64> for Double {
465 fn eq(&self, other: &f64) -> bool {
466 self.0.eq(other)
467 }
468}
469
470impl PartialEq<Double> for f64 {
471 fn eq(&self, other: &Double) -> bool {
472 self.eq(&other.0)
473 }
474}
475
476#[cfg(test)]
477pub(crate) mod test {
478 use chrono::{DateTime, TimeZone, Utc};
479 use pretty_assertions::assert_eq;
480
481 use super::*;
482
483 pub(crate) fn all_astarte_types() -> Vec<AstarteData> {
484 vec![
485 AstarteData::Double(12.21.try_into().unwrap()),
486 AstarteData::Integer(12),
487 AstarteData::Boolean(false),
488 AstarteData::LongInteger(42),
489 AstarteData::String("hello".to_string()),
490 AstarteData::BinaryBlob(vec![1, 2, 3, 4]),
491 AstarteData::DateTime(TimeZone::timestamp_opt(&Utc, 1627580808, 0).unwrap()),
492 AstarteData::DoubleArray(
493 [1.3, 2.6, 3.1, 4.0]
494 .map(|v| Double::try_from(v).unwrap())
495 .to_vec(),
496 ),
497 AstarteData::IntegerArray(vec![1, 2, 3, 4]),
498 AstarteData::BooleanArray(vec![true, false, true, true]),
499 AstarteData::LongIntegerArray(vec![32, 11, 33, 1]),
500 AstarteData::StringArray(vec!["Hello".to_string(), " world!".to_string()]),
501 AstarteData::BinaryBlobArray(vec![vec![1, 2, 3, 4], vec![4, 4, 1, 4]]),
502 AstarteData::DateTimeArray(vec![
503 TimeZone::timestamp_opt(&Utc, 1627580808, 0).unwrap(),
504 TimeZone::timestamp_opt(&Utc, 1611580808, 0).unwrap(),
505 ]),
506 ]
507 }
508
509 #[test]
510 fn test_eq() {
511 for case in all_astarte_types() {
512 match case.clone() {
513 AstarteData::Double(value) => {
514 assert_eq!(case, value);
515 assert_eq!(value, case);
516 let f = f64::from(value);
517 assert_eq!(case, f);
518 assert_eq!(f, case);
519 assert_ne!(case, false);
520 }
521 AstarteData::Integer(value) => {
522 assert_eq!(case, value);
523 assert_eq!(value, case);
524 assert_ne!(case, false);
525 }
526 AstarteData::Boolean(value) => {
527 assert_eq!(case, value);
528 assert_eq!(value, case);
529 assert_ne!(case, 42f64);
530 }
531 AstarteData::LongInteger(value) => {
532 assert_eq!(case, value);
533 assert_eq!(value, case);
534 assert_ne!(case, false);
535 }
536 AstarteData::String(value) => {
537 assert_eq!(case, value);
538 assert_eq!(value, case);
539 assert_ne!(case, false);
540 }
541 AstarteData::BinaryBlob(value) => {
542 assert_eq!(case, value);
543 assert_eq!(value, case);
544 assert_ne!(case, false);
545 }
546 AstarteData::DateTime(value) => {
547 assert_eq!(case, value);
548 assert_eq!(value, case);
549 assert_ne!(case, false);
550 }
551 AstarteData::DoubleArray(value) => {
552 let value: Vec<f64> = value.into_iter().map(f64::from).collect();
553
554 assert_eq!(case, value);
555 assert_eq!(value, case);
556 assert_ne!(case, false);
557 }
558 AstarteData::IntegerArray(value) => {
559 assert_eq!(case, value);
560 assert_eq!(value, case);
561 assert_ne!(case, false);
562 }
563 AstarteData::BooleanArray(value) => {
564 assert_eq!(case, value);
565 assert_eq!(value, case);
566 assert_ne!(case, false);
567 }
568 AstarteData::LongIntegerArray(value) => {
569 assert_eq!(case, value);
570 assert_eq!(value, case);
571 assert_ne!(case, false);
572 }
573 AstarteData::StringArray(value) => {
574 assert_eq!(case, value);
575 assert_eq!(value, case);
576 assert_ne!(case, false);
577 }
578 AstarteData::BinaryBlobArray(value) => {
579 assert_eq!(case, value);
580 assert_eq!(value, case);
581 assert_ne!(case, false);
582 }
583 AstarteData::DateTimeArray(value) => {
584 assert_eq!(case, value);
585 assert_eq!(value, case);
586 assert_ne!(case, false);
587 }
588 }
589 }
590 }
591
592 #[test]
593 fn test_conversion_to_astarte_type() {
594 for case in all_astarte_types() {
595 match case.clone() {
596 AstarteData::Double(value) => {
597 let inv: Double = case.clone().try_into().unwrap();
598 assert_eq!(inv, value);
599 let conv = AstarteData::from(value);
600 assert_eq!(conv, case);
601 bool::try_from(case).unwrap_err();
602 }
603 AstarteData::Integer(value) => {
604 let inv: i32 = case.clone().try_into().unwrap();
605 assert_eq!(inv, value);
606 let conv = AstarteData::from(value);
607 assert_eq!(conv, case);
608 bool::try_from(case).unwrap_err();
609 }
610 AstarteData::Boolean(value) => {
611 let inv: bool = case.clone().try_into().unwrap();
612 assert_eq!(inv, value);
613 let conv = AstarteData::from(value);
614 assert_eq!(conv, case);
615 f64::try_from(case).unwrap_err();
616 }
617 AstarteData::LongInteger(value) => {
618 let inv: i64 = case.clone().try_into().unwrap();
619 assert_eq!(inv, value);
620 let conv = AstarteData::from(value);
621 assert_eq!(conv, case);
622 bool::try_from(case).unwrap_err();
623 }
624 AstarteData::String(value) => {
625 let inv: String = case.clone().try_into().unwrap();
626 assert_eq!(inv, value);
627 let conv = AstarteData::from(value);
628 assert_eq!(conv, case);
629 bool::try_from(case).unwrap_err();
630 }
631 AstarteData::BinaryBlob(value) => {
632 let inv: Vec<u8> = case.clone().try_into().unwrap();
633 assert_eq!(inv, value);
634 let conv = AstarteData::from(value);
635 assert_eq!(conv, case);
636 bool::try_from(case).unwrap_err();
637 }
638 AstarteData::DateTime(value) => {
639 let inv: DateTime<Utc> = case.clone().try_into().unwrap();
640 assert_eq!(inv, value);
641 let conv = AstarteData::from(value);
642 assert_eq!(conv, case);
643 bool::try_from(case).unwrap_err();
644 }
645 AstarteData::DoubleArray(value) => {
646 let inv: Vec<f64> = case.clone().try_into().unwrap();
647 assert_eq!(inv, value);
648 let conv = AstarteData::from(value);
649 assert_eq!(conv, case);
650 bool::try_from(case).unwrap_err();
651 }
652 AstarteData::IntegerArray(value) => {
653 let inv: Vec<i32> = case.clone().try_into().unwrap();
654 assert_eq!(inv, value);
655 let conv = AstarteData::from(value);
656 assert_eq!(conv, case);
657 bool::try_from(case).unwrap_err();
658 }
659 AstarteData::BooleanArray(value) => {
660 let inv: Vec<bool> = case.clone().try_into().unwrap();
661 assert_eq!(inv, value);
662 let conv = AstarteData::from(value);
663 assert_eq!(conv, case);
664 bool::try_from(case).unwrap_err();
665 }
666 AstarteData::LongIntegerArray(value) => {
667 let inv: Vec<i64> = case.clone().try_into().unwrap();
668 assert_eq!(inv, value);
669 let conv = AstarteData::from(value);
670 assert_eq!(conv, case);
671 bool::try_from(case).unwrap_err();
672 }
673 AstarteData::StringArray(value) => {
674 let inv: Vec<String> = case.clone().try_into().unwrap();
675 assert_eq!(inv, value);
676 let conv = AstarteData::from(value);
677 assert_eq!(conv, case);
678 bool::try_from(case).unwrap_err();
679 }
680 AstarteData::BinaryBlobArray(value) => {
681 let inv: Vec<Vec<u8>> = case.clone().try_into().unwrap();
682 assert_eq!(inv, value);
683 let conv = AstarteData::from(value);
684 assert_eq!(conv, case);
685 bool::try_from(case).unwrap_err();
686 }
687 AstarteData::DateTimeArray(value) => {
688 let inv: Vec<DateTime<Utc>> = case.clone().try_into().unwrap();
689 assert_eq!(inv, value);
690 let conv = AstarteData::from(value);
691 assert_eq!(conv, case);
692 bool::try_from(case).unwrap_err();
693 }
694 }
695 }
696 }
697
698 #[test]
699 fn test_eq_astarte_type_with_mapping_type() {
700 assert!(AstarteData::Double(0.0.try_into().unwrap()).eq_mapping_type(MappingType::Double));
701 assert!(AstarteData::Integer(0).eq_mapping_type(MappingType::Double));
702
703 assert!(AstarteData::Integer(0).eq_mapping_type(MappingType::Integer));
704 assert!(
705 !AstarteData::Double(0.0.try_into().unwrap()).eq_mapping_type(MappingType::Integer)
706 );
707 assert!(AstarteData::Integer(0).eq_mapping_type(MappingType::LongInteger));
708
709 assert!(AstarteData::LongInteger(0).eq_mapping_type(MappingType::LongInteger));
710 }
711
712 #[test]
713 fn test_conversion_from_astarte_integer_to_f64() {
714 let value = AstarteData::Integer(5);
715
716 f64::try_from(value).unwrap_err();
717 }
718
719 #[test]
720 fn test_conversion_from_astarte_integer_and_long_integer_to_i64() {
721 let value: i64 = AstarteData::Integer(5).try_into().unwrap();
722
723 assert_eq!(value, 5);
724
725 let value: i64 = AstarteData::LongInteger(5).try_into().unwrap();
726
727 assert_eq!(value, 5);
728
729 i64::try_from(AstarteData::Boolean(false)).unwrap_err();
730 }
731
732 #[test]
733 fn tesat_float_validation() {
734 assert_eq!(
735 AstarteData::try_from(54.4).unwrap(),
736 AstarteData::Double(54.4.try_into().unwrap())
737 );
738 AstarteData::try_from(f64::NAN).unwrap_err();
739 AstarteData::try_from(vec![1.0, 2.0, f64::NAN, 4.0]).unwrap_err();
740 }
741}