1use std::borrow::Cow;
22use std::fmt::Display;
23use std::str::FromStr;
24use std::time::Duration;
25
26use name::InterfaceName;
27use serde::{Deserialize, Serialize};
28use tracing::{info, warn};
29
30use self::datastream::individual::DatastreamIndividual;
31use self::datastream::object::DatastreamObject;
32use self::properties::Properties;
33use self::validation::VersionChange;
34use self::version::InterfaceVersion;
35use crate::error::Error;
36use crate::mapping::{collection::MappingVec, path::MappingPath};
37use crate::schema::{InterfaceJson, Mapping};
38
39pub use crate::schema::{Aggregation, DatabaseRetentionPolicy, InterfaceType, Ownership};
41
42pub mod datastream;
43pub mod name;
44pub mod properties;
45pub mod validation;
46pub mod version;
47
48pub const MAX_INTERFACE_MAPPINGS: usize = 1024;
52
53#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
57#[serde(try_from = "InterfaceJson<std::borrow::Cow<str>>")]
58pub struct Interface {
59 inner: InterfaceTypeAggregation,
60}
61
62impl Interface {
63 pub fn inner(&self) -> &InterfaceTypeAggregation {
65 &self.inner
66 }
67
68 #[must_use]
70 pub fn interface_name(&self) -> &str {
71 match &self.inner {
72 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
73 datastream_individual.name()
74 }
75 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
76 datastream_object.name()
77 }
78 InterfaceTypeAggregation::Properties(property) => property.name(),
79 }
80 }
81
82 #[must_use]
84 pub fn version(&self) -> InterfaceVersion {
85 match &self.inner {
86 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
87 datastream_individual.version()
88 }
89 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
90 datastream_object.version()
91 }
92 InterfaceTypeAggregation::Properties(property) => property.version(),
93 }
94 }
95
96 #[must_use]
98 pub fn version_major(&self) -> i32 {
99 match &self.inner {
100 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
101 datastream_individual.version_major()
102 }
103 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
104 datastream_object.version_major()
105 }
106 InterfaceTypeAggregation::Properties(property) => property.version_major(),
107 }
108 }
109
110 #[must_use]
112 pub fn version_minor(&self) -> i32 {
113 match &self.inner {
114 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
115 datastream_individual.version_minor()
116 }
117 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
118 datastream_object.version_minor()
119 }
120 InterfaceTypeAggregation::Properties(property) => property.version_minor(),
121 }
122 }
123
124 #[must_use]
126 pub fn interface_type(&self) -> InterfaceType {
127 match &self.inner {
128 InterfaceTypeAggregation::DatastreamIndividual(_)
129 | InterfaceTypeAggregation::DatastreamObject(_) => InterfaceType::Datastream,
130 InterfaceTypeAggregation::Properties(_) => InterfaceType::Properties,
131 }
132 }
133
134 #[must_use]
136 pub fn ownership(&self) -> Ownership {
137 match &self.inner {
138 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
139 datastream_individual.ownership()
140 }
141 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
142 datastream_object.ownership()
143 }
144 InterfaceTypeAggregation::Properties(property) => property.ownership(),
145 }
146 }
147
148 #[must_use]
150 pub fn aggregation(&self) -> Aggregation {
151 match &self.inner {
152 InterfaceTypeAggregation::Properties(_)
153 | InterfaceTypeAggregation::DatastreamIndividual(_) => Aggregation::Individual,
154 InterfaceTypeAggregation::DatastreamObject(_) => Aggregation::Object,
155 }
156 }
157
158 #[cfg(feature = "doc-fields")]
159 #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
160 #[must_use]
162 pub fn description(&self) -> Option<&str> {
163 match &self.inner {
164 InterfaceTypeAggregation::Properties(interface) => interface.description(),
165 InterfaceTypeAggregation::DatastreamIndividual(interface) => interface.description(),
166 InterfaceTypeAggregation::DatastreamObject(interface) => interface.description(),
167 }
168 }
169
170 #[cfg(feature = "doc-fields")]
171 #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
172 #[must_use]
174 pub fn doc(&self) -> Option<&str> {
175 match &self.inner {
176 InterfaceTypeAggregation::Properties(interface) => interface.doc(),
177 InterfaceTypeAggregation::DatastreamIndividual(interface) => interface.doc(),
178 InterfaceTypeAggregation::DatastreamObject(interface) => interface.doc(),
179 }
180 }
181
182 pub fn validate_with(&self, prev: &Self) -> Result<&Self, Error> {
190 if self == prev {
192 return Ok(self);
193 }
194
195 let name = self.interface_name();
197 let prev_name = prev.interface_name();
198 if name != prev_name {
199 return Err(Error::NameMismatch {
200 name: name.to_string(),
201 prev_name: prev_name.to_string(),
202 });
203 }
204
205 VersionChange::try_new(self, prev)
207 .map_err(Error::VersionChange)
208 .map(|change| {
209 info!("Interface {} version changed: {}", name, change);
210
211 self
212 })
213 }
214
215 #[must_use]
217 pub fn as_datastream_individual(&self) -> Option<&DatastreamIndividual> {
218 if let InterfaceTypeAggregation::DatastreamIndividual(v) = &self.inner {
219 Some(v)
220 } else {
221 None
222 }
223 }
224
225 #[must_use]
227 pub fn as_datastream_object(&self) -> Option<&DatastreamObject> {
228 if let InterfaceTypeAggregation::DatastreamObject(v) = &self.inner {
229 Some(v)
230 } else {
231 None
232 }
233 }
234
235 #[must_use]
237 pub fn as_properties(&self) -> Option<&Properties> {
238 if let InterfaceTypeAggregation::Properties(v) = &self.inner {
239 Some(v)
240 } else {
241 None
242 }
243 }
244
245 #[must_use]
249 pub fn is_datastream_individual(&self) -> bool {
250 matches!(
251 self.inner,
252 InterfaceTypeAggregation::DatastreamIndividual(..)
253 )
254 }
255
256 #[must_use]
260 pub fn is_datastream_object(&self) -> bool {
261 matches!(self.inner, InterfaceTypeAggregation::DatastreamObject(..))
262 }
263
264 #[must_use]
268 pub fn is_properties(&self) -> bool {
269 matches!(self.inner, InterfaceTypeAggregation::Properties(..))
270 }
271}
272
273impl<T> TryFrom<InterfaceJson<T>> for Interface
274where
275 T: AsRef<str> + Into<String>,
276{
277 type Error = Error;
278
279 fn try_from(value: InterfaceJson<T>) -> Result<Self, Self::Error> {
280 let inner = match (value.interface_type, value.aggregation.unwrap_or_default()) {
281 (InterfaceType::Datastream, Aggregation::Individual) => {
282 let interface = DatastreamIndividual::try_from(value)?;
283
284 InterfaceTypeAggregation::DatastreamIndividual(interface)
285 }
286 (InterfaceType::Datastream, Aggregation::Object) => {
287 let interface = DatastreamObject::try_from(value)?;
288
289 InterfaceTypeAggregation::DatastreamObject(interface)
290 }
291 (InterfaceType::Properties, Aggregation::Individual) => {
292 let interface = Properties::try_from(value)?;
293
294 InterfaceTypeAggregation::Properties(interface)
295 }
296 (InterfaceType::Properties, Aggregation::Object) => return Err(Error::PropertyObject),
297 };
298
299 Ok(Interface { inner })
300 }
301}
302
303impl<'a> From<&'a Interface> for InterfaceJson<Cow<'a, str>> {
304 fn from(value: &'a Interface) -> Self {
305 match &value.inner {
306 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
307 Self::from(datastream_individual)
308 }
309 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
310 Self::from(datastream_object)
311 }
312 InterfaceTypeAggregation::Properties(properties) => Self::from(properties),
313 }
314 }
315}
316
317impl Serialize for Interface {
318 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
319 match &self.inner {
320 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
321 datastream_individual.serialize(serializer)
322 }
323 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
324 datastream_object.serialize(serializer)
325 }
326 InterfaceTypeAggregation::Properties(properties) => properties.serialize(serializer),
327 }
328 }
329}
330
331impl FromStr for Interface {
332 type Err = Error;
333
334 fn from_str(s: &str) -> Result<Self, Self::Err> {
335 let interface: InterfaceJson<Cow<str>> = serde_json::from_str(s)?;
336
337 Interface::try_from(interface)
338 }
339}
340
341impl Display for Interface {
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 match &self.inner {
344 InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
345 write!(f, "{datastream_individual}")
346 }
347 InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
348 write!(f, "{datastream_object}")
349 }
350 InterfaceTypeAggregation::Properties(property) => {
351 write!(f, "{property}")
352 }
353 }
354 }
355}
356
357#[derive(Debug, PartialEq, Eq, Clone)]
362pub enum InterfaceTypeAggregation {
363 DatastreamIndividual(DatastreamIndividual),
365 DatastreamObject(DatastreamObject),
367 Properties(Properties),
369}
370
371#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
378pub enum Retention {
379 #[default]
381 Discard,
382 Volatile {
384 expiry: Option<Duration>,
388 },
389 Stored {
391 expiry: Option<Duration>,
395 },
396}
397
398impl Retention {
399 #[must_use]
403 pub const fn is_stored(&self) -> bool {
404 matches!(self, Self::Stored { .. })
405 }
406
407 #[must_use]
413 pub const fn as_expiry(&self) -> Option<&Duration> {
414 match self {
415 Retention::Discard => None,
416 Retention::Volatile { expiry } | Retention::Stored { expiry } => expiry.as_ref(),
418 }
419 }
420
421 #[must_use]
427 pub fn as_expiry_seconds(&self) -> Option<i64> {
428 self.as_expiry().map(|duration| {
429 i64::try_from(duration.as_secs())
431 .inspect_err(|err| warn!(%err, "expiry conversion error"))
432 .unwrap_or(i64::MAX)
433 })
434 }
435
436 #[must_use]
440 pub const fn is_volatile(&self) -> bool {
441 matches!(self, Self::Volatile { .. })
442 }
443
444 #[must_use]
448 pub const fn is_discard(&self) -> bool {
449 matches!(self, Self::Discard)
450 }
451}
452
453impl From<Retention> for crate::schema::Retention {
454 fn from(value: Retention) -> Self {
455 match value {
456 Retention::Discard => crate::schema::Retention::Discard,
457 Retention::Volatile { .. } => crate::schema::Retention::Volatile,
458 Retention::Stored { .. } => crate::schema::Retention::Stored,
459 }
460 }
461}
462
463#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
468pub enum DatabaseRetention {
469 #[default]
471 NoTtl,
472 UseTtl {
474 ttl: Duration,
476 },
477}
478
479impl DatabaseRetention {
480 #[must_use]
484 pub fn is_no_ttl(&self) -> bool {
485 matches!(self, Self::NoTtl)
486 }
487
488 #[must_use]
492 pub fn is_use_ttl(&self) -> bool {
493 matches!(self, Self::UseTtl { .. })
494 }
495
496 #[must_use]
500 pub fn as_ttl(&self) -> Option<&Duration> {
501 match self {
502 DatabaseRetention::NoTtl => None,
503 DatabaseRetention::UseTtl { ttl } => Some(ttl),
504 }
505 }
506
507 #[must_use]
511 pub fn as_ttl_secs(&self) -> Option<i64> {
512 self.as_ttl().map(|ttl| {
513 i64::try_from(ttl.as_secs())
515 .inspect_err(|err| warn!(%err, "ttl conversion error"))
516 .unwrap_or(i64::MAX)
517 })
518 }
519}
520
521impl From<DatabaseRetention> for DatabaseRetentionPolicy {
522 fn from(value: DatabaseRetention) -> Self {
523 match value {
524 DatabaseRetention::NoTtl => DatabaseRetentionPolicy::NoTtl,
525 DatabaseRetention::UseTtl { .. } => DatabaseRetentionPolicy::UseTtl,
526 }
527 }
528}
529
530pub trait Schema {
532 type Mapping: Sized;
534
535 fn name(&self) -> &str;
537 fn interface_name(&self) -> &InterfaceName;
539 fn version_major(&self) -> i32;
541 fn version_minor(&self) -> i32;
543 fn version(&self) -> InterfaceVersion;
545 fn interface_type(&self) -> InterfaceType;
547 fn ownership(&self) -> Ownership;
549 fn aggregation(&self) -> Aggregation;
551
552 #[cfg(feature = "doc-fields")]
553 #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
554 fn description(&self) -> Option<&str>;
556
557 #[cfg(feature = "doc-fields")]
558 #[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
559 fn doc(&self) -> Option<&str>;
561
562 fn iter_mappings(&self) -> impl Iterator<Item = &Self::Mapping>;
564
565 fn mappings_len(&self) -> usize;
567
568 fn iter_interface_mappings(&self) -> impl Iterator<Item = Mapping<Cow<'_, str>>>;
570}
571
572pub trait AggregationIndividual: Schema {
574 fn mapping(&self, path: &MappingPath) -> Option<&Self::Mapping>;
576}
577
578impl<'a, T> From<&'a T> for InterfaceJson<Cow<'a, str>>
579where
580 T: Schema,
581{
582 fn from(value: &'a T) -> Self {
583 InterfaceJson {
584 interface_name: value.interface_name().as_str().into(),
585 version_major: value.version_major(),
586 version_minor: value.version_minor(),
587 interface_type: value.interface_type(),
588 ownership: value.ownership(),
589 aggregation: Some(value.aggregation()),
590 #[cfg(feature = "doc-fields")]
591 description: value.description().map(Cow::from),
592 #[cfg(feature = "doc-fields")]
593 doc: value.doc().map(Cow::from),
594 #[cfg(not(feature = "doc-fields"))]
595 description: None,
596 #[cfg(not(feature = "doc-fields"))]
597 doc: None,
598 mappings: value.iter_interface_mappings().collect(),
599 }
600 }
601}
602
603#[cfg(test)]
604pub(crate) mod tests {
605 use std::str::FromStr;
606
607 use pretty_assertions::assert_eq;
608
609 use super::*;
610 use crate::{
611 mapping::{InterfaceMapping, MappingError},
612 schema::{InterfaceType, MappingType, Ownership, Reliability},
613 DatastreamIndividual, DatastreamIndividualMapping, Endpoint, Interface, MappingPath,
614 Schema,
615 };
616
617 const E2E_DEVICE_PROPERTY: &str =
618 include_str!("../../interfaces/org.astarte-platform.rust.e2etest.DeviceProperty.json");
619 const E2E_DEVICE_PROPERTY_NAME: &str = "org.astarte-platform.rust.e2etest.DeviceProperty";
620 pub(crate) const E2E_DEVICE_AGGREGATE: &str =
621 include_str!("../../interfaces/org.astarte-platform.rust.e2etest.DeviceAggregate.json");
622 const E2E_DEVICE_AGGREGATE_NAME: &str = "org.astarte-platform.rust.e2etest.DeviceAggregate";
623 const E2E_DEVICE_DATASTREAM: &str =
624 include_str!("../../interfaces/org.astarte-platform.rust.e2etest.DeviceDatastream.json");
625 const E2E_DEVICE_DATASTREAM_NAME: &str = "org.astarte-platform.rust.e2etest.DeviceDatastream";
626
627 #[cfg(feature = "doc-fields")]
629 const INTERFACE_JSON: &str = r#"{
630 "interface_name": "org.astarte-platform.genericsensors.Values",
631 "version_major": 1,
632 "version_minor": 0,
633 "type": "datastream",
634 "ownership": "device",
635 "description": "Interface description",
636 "doc": "Interface doc",
637 "mappings": [
638 {
639 "endpoint": "/%{sensor_id}/otherValue",
640 "type": "longinteger",
641 "explicit_timestamp": true,
642 "description": "Mapping description",
643 "doc": "Mapping doc"
644 },
645 {
646 "endpoint": "/%{sensor_id}/value",
647 "type": "double",
648 "explicit_timestamp": true,
649 "description": "Mapping description",
650 "doc": "Mapping doc"
651 }
652 ]
653 }"#;
654
655 #[cfg(not(feature = "doc-fields"))]
656 const INTERFACE_JSON: &str = r#"{
657 "interface_name": "org.astarte-platform.genericsensors.Values",
658 "version_major": 1,
659 "version_minor": 0,
660 "type": "datastream",
661 "ownership": "device",
662 "mappings": [
663 {
664 "endpoint": "/%{sensor_id}/otherValue",
665 "type": "longinteger",
666 "explicit_timestamp": true
667 },
668 {
669 "endpoint": "/%{sensor_id}/value",
670 "type": "double",
671 "explicit_timestamp": true
672 }
673 ]
674 }"#;
675
676 const PROPERTIES_JSON: &str = r#"{
678 "interface_name": "org.astarte-platform.genericproperties.Values",
679 "version_major": 1,
680 "version_minor": 0,
681 "type": "properties",
682 "ownership": "server",
683 "description": "Interface description",
684 "doc": "Interface doc",
685 "mappings": [
686 {
687 "endpoint": "/%{sensor_id}/aaaa",
688 "type": "longinteger",
689 "allow_unset": true
690 },
691 {
692 "endpoint": "/%{sensor_id}/bbbb",
693 "type": "double",
694 "allow_unset": false
695 }
696 ]
697 }"#;
698
699 #[test]
700 fn datastream_interface_deserialization() {
701 let value_mapping = DatastreamIndividualMapping {
702 endpoint: Endpoint::try_from("/%{sensor_id}/value").unwrap(),
703 mapping_type: MappingType::Double,
704 reliability: Reliability::default(),
705 retention: Retention::default(),
706 #[cfg(feature = "server-fields")]
707 database_retention: DatabaseRetention::default(),
708 explicit_timestamp: true,
709 #[cfg(feature = "doc-fields")]
710 description: Some("Mapping description".to_string()),
711 #[cfg(feature = "doc-fields")]
712 doc: Some("Mapping doc".to_string()),
713 };
714
715 let other_value_mapping = DatastreamIndividualMapping {
716 endpoint: Endpoint::try_from("/%{sensor_id}/otherValue").unwrap(),
717 mapping_type: MappingType::LongInteger,
718 reliability: Reliability::default(),
719 retention: Retention::default(),
720 #[cfg(feature = "server-fields")]
721 database_retention: DatabaseRetention::default(),
722 explicit_timestamp: true,
723 #[cfg(feature = "doc-fields")]
724 description: Some("Mapping description".to_string()),
725 #[cfg(feature = "doc-fields")]
726 doc: Some("Mapping doc".to_string()),
727 };
728
729 let interface_name =
730 InterfaceName::try_from("org.astarte-platform.genericsensors.Values").unwrap();
731 let version = InterfaceVersion::try_from((1, 0)).unwrap();
732 let ownership = Ownership::Device;
733 #[cfg(feature = "doc-fields")]
734 let description = Some("Interface description".to_owned());
735 #[cfg(feature = "doc-fields")]
736 let doc = Some("Interface doc".to_owned());
737
738 let mappings = MappingVec::try_from([other_value_mapping, value_mapping].to_vec()).unwrap();
739
740 let datastream_individual = DatastreamIndividual {
741 name: interface_name.into_string(),
742 version,
743 ownership,
744 #[cfg(feature = "doc-fields")]
745 description,
746 #[cfg(feature = "doc-fields")]
747 doc,
748 mappings,
749 };
750
751 let interface = Interface {
752 inner: InterfaceTypeAggregation::DatastreamIndividual(datastream_individual),
753 };
754
755 let deser_interface = Interface::from_str(INTERFACE_JSON).unwrap();
756
757 assert_eq!(interface, deser_interface);
758 }
759
760 #[test]
761 fn must_have_one_mapping() {
762 let json = r#"{
763 "interface_name": "org.astarte-platform.genericproperties.Values",
764 "version_major": 1,
765 "version_minor": 0,
766 "type": "properties",
767 "ownership": "server",
768 "description": "Interface description",
769 "doc": "Interface doc",
770 "mappings": []
771 }"#;
772
773 let interface = Interface::from_str(json);
774
775 let err = interface.unwrap_err();
776 assert!(matches!(err, Error::Mapping(MappingError::Empty)));
777 }
778
779 #[test]
780 fn test_properties() {
781 let interface = Interface::from_str(PROPERTIES_JSON).unwrap();
782
783 let exp = Properties::from_str(PROPERTIES_JSON).unwrap();
784 assert_eq!(
785 *interface.inner(),
786 InterfaceTypeAggregation::Properties(exp)
787 );
788 assert_eq!(interface.interface_type(), InterfaceType::Properties);
789
790 let exp = InterfaceVersion::try_new(1, 0).unwrap();
791 assert_eq!(interface.version(), exp);
792 assert_eq!(interface.version_major(), 1);
793 assert_eq!(interface.version_minor(), 0);
794
795 let InterfaceTypeAggregation::Properties(interface) = interface.inner else {
796 panic!()
797 };
798
799 let paths: Vec<_> = interface.iter_mappings().collect();
800
801 assert_eq!(paths.len(), 2);
802 assert_eq!(paths[0].endpoint().to_string(), "/%{sensor_id}/aaaa");
803 assert_eq!(paths[1].endpoint().to_string(), "/%{sensor_id}/bbbb");
804
805 let path = MappingPath::try_from("/1/aaaa").unwrap();
806
807 let f = interface.mapping(&path).unwrap();
808
809 assert_eq!(f.mapping_type(), MappingType::LongInteger);
810 assert!(f.allow_unset());
811 }
812
813 #[test]
814 fn test_iter_mappings() {
815 let value_mapping = DatastreamIndividualMapping {
816 endpoint: Endpoint::try_from("/%{sensor_id}/value").unwrap(),
817 mapping_type: MappingType::Double,
818 #[cfg(feature = "doc-fields")]
819 description: Some("Mapping description".to_string()),
820 #[cfg(feature = "doc-fields")]
821 doc: Some("Mapping doc".to_string()),
822 reliability: Reliability::default(),
823 retention: Retention::default(),
824 #[cfg(feature = "server-fields")]
825 database_retention: DatabaseRetention::default(),
826 explicit_timestamp: true,
827 };
828
829 let other_value_mapping = DatastreamIndividualMapping {
830 endpoint: Endpoint::try_from("/%{sensor_id}/otherValue").unwrap(),
831 mapping_type: MappingType::LongInteger,
832 #[cfg(feature = "doc-fields")]
833 description: Some("Mapping description".to_string()),
834 #[cfg(feature = "doc-fields")]
835 doc: Some("Mapping doc".to_string()),
836 reliability: Reliability::default(),
837 retention: Retention::default(),
838 #[cfg(feature = "server-fields")]
839 database_retention: DatabaseRetention::default(),
840 explicit_timestamp: true,
841 };
842
843 let interface = Interface::from_str(INTERFACE_JSON).unwrap();
844 let interface = interface.as_datastream_individual().unwrap();
845
846 let mut mappings = interface.iter_mappings();
847
848 assert_eq!(mappings.next(), Some(&other_value_mapping));
849 assert_eq!(mappings.next(), Some(&value_mapping));
850 assert_eq!(mappings.next(), None);
851 }
852
853 #[test]
854 fn methods_test() {
855 let interface = Interface::from_str(INTERFACE_JSON).unwrap();
856
857 assert_eq!(
858 interface.interface_name(),
859 "org.astarte-platform.genericsensors.Values"
860 );
861 assert_eq!(interface.version_major(), 1);
862 assert_eq!(interface.version_minor(), 0);
863 assert_eq!(interface.ownership(), Ownership::Device);
864 #[cfg(feature = "doc-fields")]
865 assert_eq!(interface.description(), Some("Interface description"));
866 assert_eq!(interface.aggregation(), Aggregation::Individual);
867 assert_eq!(interface.interface_type(), InterfaceType::Datastream);
868 #[cfg(feature = "doc-fields")]
869 assert_eq!(interface.doc(), Some("Interface doc"));
870 }
871
872 #[test]
873 fn serialize_and_deserialize() {
874 let interface = Interface::from_str(INTERFACE_JSON).unwrap();
875 let serialized = serde_json::to_string(&interface).unwrap();
876 let deserialized: Interface = serde_json::from_str(&serialized).unwrap();
877
878 assert_eq!(interface, deserialized);
879
880 let value = serde_json::Value::from_str(&serialized).unwrap();
881 let expected = serde_json::Value::from_str(INTERFACE_JSON).unwrap();
882 assert_eq!(value, expected);
883 }
884
885 #[test]
886 fn check_as_prop() {
887 let interface = Interface::from_str(PROPERTIES_JSON).unwrap();
888
889 interface.as_properties().expect("interface is a property");
890
891 let interface = Interface::from_str(INTERFACE_JSON).unwrap();
892
893 assert_eq!(interface.as_properties(), None);
894 }
895
896 #[cfg(feature = "doc-fields")]
897 #[test]
898 fn test_with_escaped_descriptions() {
899 let json = r#"{
900 "interface_name": "org.astarte-platform.genericproperties.Values",
901 "version_major": 1,
902 "version_minor": 0,
903 "type": "properties",
904 "ownership": "server",
905 "description": "Interface description \"escaped\"",
906 "doc": "Interface doc \"escaped\"",
907 "mappings": [{
908 "endpoint": "/double_endpoint",
909 "type": "double",
910 "doc": "Mapping doc \"escaped\""
911 }]
912 }"#;
913
914 let interface = Interface::from_str(json).unwrap();
915 let interface = interface.as_properties().unwrap();
916
917 assert_eq!(
918 interface.description().unwrap(),
919 r#"Interface description "escaped""#
920 );
921 assert_eq!(interface.doc().unwrap(), r#"Interface doc "escaped""#);
922 let mapping_doc = interface
923 .mapping(&MappingPath::try_from("/double_endpoint").unwrap())
924 .unwrap()
925 .doc()
926 .unwrap();
927 assert_eq!(mapping_doc, r#"Mapping doc "escaped""#);
928 }
929
930 #[test]
931 fn should_convert_into_inner() {
932 let interface = Interface::from_str(E2E_DEVICE_PROPERTY).unwrap();
933
934 assert!(interface.as_properties().is_some());
935 assert!(interface.as_datastream_object().is_none());
936 assert!(interface.as_datastream_individual().is_none());
937 assert!(interface.is_properties());
938 assert!(!interface.is_datastream_object());
939 assert!(!interface.is_datastream_object());
940
941 let interface = interface.as_properties().unwrap();
942 assert_eq!(interface.mappings_len(), 14);
943
944 let interface = Interface::from_str(E2E_DEVICE_AGGREGATE).unwrap();
945
946 assert!(interface.as_properties().is_none());
947 assert!(interface.as_datastream_object().is_some());
948 assert!(interface.as_datastream_individual().is_none());
949 assert!(!interface.is_properties());
950 assert!(interface.is_datastream_object());
951 assert!(!interface.is_datastream_individual());
952
953 let interface = interface.as_datastream_object().unwrap();
954 assert_eq!(interface.mappings_len(), 14);
955
956 let interface = Interface::from_str(E2E_DEVICE_DATASTREAM).unwrap();
957
958 assert!(interface.as_properties().is_none());
959 assert!(interface.as_datastream_object().is_none());
960 assert!(interface.as_datastream_individual().is_some());
961 assert!(!interface.is_properties());
962 assert!(!interface.is_datastream_object());
963 assert!(interface.is_datastream_individual());
964
965 let interface = interface.as_datastream_individual().unwrap();
966 assert_eq!(interface.mappings_len(), 14);
967 }
968
969 #[test]
970 fn test_interface_getters() {
971 let version = InterfaceVersion::try_new(0, 1).unwrap();
972
973 let interface = Interface::from_str(E2E_DEVICE_DATASTREAM).unwrap();
974
975 assert_eq!(interface.interface_name(), E2E_DEVICE_DATASTREAM_NAME);
976 assert_eq!(interface.version(), version);
977 assert_eq!(interface.version_major(), 0);
978 assert_eq!(interface.version_minor(), 1);
979 assert_eq!(interface.interface_type(), InterfaceType::Datastream);
980 assert_eq!(interface.ownership(), Ownership::Device);
981 assert_eq!(interface.aggregation(), Aggregation::Individual);
982 #[cfg(feature = "doc-fields")]
983 assert_eq!(interface.description(), Some("Test datastream interface."));
984 #[cfg(feature = "doc-fields")]
985 assert_eq!(
986 interface.doc(),
987 Some("Test interface used to test datastream.")
988 );
989
990 let interface = Interface::from_str(E2E_DEVICE_AGGREGATE).unwrap();
991
992 assert_eq!(interface.interface_name(), E2E_DEVICE_AGGREGATE_NAME);
993 assert_eq!(interface.version(), version);
994 assert_eq!(interface.version_major(), 0);
995 assert_eq!(interface.version_minor(), 1);
996 assert_eq!(interface.interface_type(), InterfaceType::Datastream);
997 assert_eq!(interface.ownership(), Ownership::Device);
998 assert_eq!(interface.aggregation(), Aggregation::Object);
999 #[cfg(feature = "doc-fields")]
1000 assert_eq!(interface.description(), Some("Test aggregate interface."));
1001 #[cfg(feature = "doc-fields")]
1002 assert_eq!(
1003 interface.doc(),
1004 Some("Test interface used to test aggregates.")
1005 );
1006
1007 let interface = Interface::from_str(E2E_DEVICE_PROPERTY).unwrap();
1008
1009 assert_eq!(interface.interface_name(), E2E_DEVICE_PROPERTY_NAME);
1010 assert_eq!(interface.version(), version);
1011 assert_eq!(interface.version_major(), 0);
1012 assert_eq!(interface.version_minor(), 1);
1013 assert_eq!(interface.interface_type(), InterfaceType::Properties);
1014 assert_eq!(interface.ownership(), Ownership::Device);
1015 assert_eq!(interface.aggregation(), Aggregation::Individual);
1016 #[cfg(feature = "doc-fields")]
1017 assert_eq!(interface.description(), Some("Test properties interface."));
1018 #[cfg(feature = "doc-fields")]
1019 assert_eq!(
1020 interface.doc(),
1021 Some("Test interface used to test properties.")
1022 );
1023 }
1024}