Skip to main content

astarte_device_sdk/types/
mod.rs

1// This file is part of Astarte.
2//
3// Copyright 2021-2026 SECO Mind Srl
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// SPDX-License-Identifier: Apache-2.0
18
19//! Provides Astarte specific types to be used by the [Client][crate::DeviceClient] to
20//! transmit/receive data to/from the Astarte cluster.
21
22use 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
46// we implement From<T> and PartialEq<T> from all the base types to AstarteData, using this macro
47macro_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
75// we implement TryFrom<AstarteData> to all the base types, using this macro
76macro_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/// Astarte type conversion errors.
95#[non_exhaustive]
96#[derive(Debug, Clone, thiserror::Error)]
97pub enum TypeError {
98    /// Invalid floating point value
99    #[error("forbidden floating point number, Nan, Infinite or subnormal numbers are invalid")]
100    Float,
101    /// Conversion error
102    #[error("couldn't convert value {ctx}")]
103    Conversion {
104        /// Context of the failed conversion
105        ctx: String,
106    },
107    /// Failed to convert from Bson value
108    #[error("error converting from Bson to AstarteData ({0})")]
109    FromBsonError(String),
110    /// Failed to convert from Bson array
111    #[error("type mismatch in bson array from astarte")]
112    FromBsonArrayError,
113    /// Invalid type convert between the BSON and [`AstarteData`]
114    #[error("type mismatch for bson and mapping")]
115    InvalidType,
116}
117
118impl TypeError {
119    /// Error with context for the conversion
120    pub(crate) const fn conversion(ctx: String) -> Self {
121        Self::Conversion { ctx }
122    }
123}
124
125/// Types supported by the Astarte device.
126///
127/// An implementation of the [From] or [TryFrom] trait is provided for the encapsulated base types.
128///
129/// ```
130/// use astarte_device_sdk::types::AstarteData;
131/// use std::convert::TryInto;
132///
133/// let b_type: bool = true;
134/// let as_type: AstarteData = AstarteData::from(b_type);
135/// assert_eq!(AstarteData::Boolean(true), as_type);
136/// let b_type: bool = as_type.try_into().unwrap();
137///
138/// let d_type: f64 = 42.4;
139/// let as_type: AstarteData = AstarteData::try_from(d_type).unwrap();
140/// assert_eq!(as_type, AstarteData::Double(42.4.try_into().unwrap()));
141/// let d_type: f64 = as_type.try_into().unwrap();
142/// ```
143///
144/// For more information about the types supported by Astarte see the
145/// [documentation](https://docs.astarte-platform.org/latest/080-mqtt-v1-protocol.html#astarte-data-types-to-bson-types)
146#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize)]
147#[serde(into = "Bson")]
148pub enum AstarteData {
149    /// Double value.
150    ///
151    /// This is guaranteed not to be a [subnormal](https://en.wikipedia.org/wiki/Subnormal_number) or
152    /// `+inf`, `Nan`, etc...
153    Double(Double),
154    /// Singed integer value.
155    Integer(i32),
156    /// Boolean value.
157    Boolean(bool),
158    /// Long integer value.
159    ///
160    /// During transport this can be received from Astarte as a `i32` to save space.
161    LongInteger(i64),
162    /// String value.
163    String(String),
164    /// Binary value.
165    BinaryBlob(Vec<u8>),
166    /// Date time value.
167    ///
168    /// UTC date time.
169    DateTime(Timestamp),
170    /// Double array value.
171    DoubleArray(Vec<Double>),
172    /// Integer array value.
173    IntegerArray(Vec<i32>),
174    /// Boolean array value.
175    BooleanArray(Vec<bool>),
176    /// Long integer array value.
177    ///
178    /// During transport this can be received from Astarte as a mixed `i32` and `i64` array to save
179    /// space.
180    LongIntegerArray(Vec<i64>),
181    /// String array value.
182    StringArray(Vec<String>),
183    /// Binary array value.
184    BinaryBlobArray(Vec<Vec<u8>>),
185    /// Date time array value.
186    ///
187    /// UTC date time.
188    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    /// Convert a non empty BSON array to astarte array with all the elements of the same type.
218    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                    // Astarte can send different size integer
233                    b.as_i64().or_else(|| b.as_i32().map(i64::from))
234                })
235                .map(AstarteData::from)
236            }
237            ArrayType::String => {
238                // Take the same string and don't use as_str
239                bson_array(array, |b| match b {
240                    Bson::String(s) => Some(s),
241                    _ => None,
242                })
243                .map(AstarteData::from)
244            }
245            ArrayType::BinaryBlob => {
246                // Take the same buf allocation
247                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                // Manually convert to chrono
255                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/// A valid Astarte float.
419///
420/// It cannot be Nan, Infinite or subnormal.
421#[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
436// NOTE: do not implement conversions from f32 into Float, since it looses precision. So it's better
437//       handled by the final user.
438impl 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}