use std::borrow::Cow;
use std::fmt::Display;
use std::str::FromStr;
use std::time::Duration;
use name::InterfaceName;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
use self::datastream::individual::DatastreamIndividual;
use self::datastream::object::DatastreamObject;
use self::properties::Properties;
use self::validation::VersionChange;
use self::version::InterfaceVersion;
use crate::error::Error;
use crate::mapping::{collection::MappingVec, path::MappingPath};
use crate::schema::{InterfaceJson, Mapping};
pub use crate::schema::{Aggregation, DatabaseRetentionPolicy, InterfaceType, Ownership};
pub mod datastream;
pub mod name;
pub mod properties;
pub mod validation;
pub mod version;
pub const MAX_INTERFACE_MAPPINGS: usize = 1024;
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
#[serde(try_from = "InterfaceJson<std::borrow::Cow<str>>")]
pub struct Interface {
inner: InterfaceTypeAggregation,
}
impl Interface {
pub fn inner(&self) -> &InterfaceTypeAggregation {
&self.inner
}
#[must_use]
pub fn interface_name(&self) -> &str {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
datastream_individual.name()
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
datastream_object.name()
}
InterfaceTypeAggregation::Properties(property) => property.name(),
}
}
#[must_use]
pub fn version(&self) -> InterfaceVersion {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
datastream_individual.version()
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
datastream_object.version()
}
InterfaceTypeAggregation::Properties(property) => property.version(),
}
}
#[must_use]
pub fn version_major(&self) -> i32 {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
datastream_individual.version_major()
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
datastream_object.version_major()
}
InterfaceTypeAggregation::Properties(property) => property.version_major(),
}
}
#[must_use]
pub fn version_minor(&self) -> i32 {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
datastream_individual.version_minor()
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
datastream_object.version_minor()
}
InterfaceTypeAggregation::Properties(property) => property.version_minor(),
}
}
#[must_use]
pub fn interface_type(&self) -> InterfaceType {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(_)
| InterfaceTypeAggregation::DatastreamObject(_) => InterfaceType::Datastream,
InterfaceTypeAggregation::Properties(_) => InterfaceType::Properties,
}
}
#[must_use]
pub fn ownership(&self) -> Ownership {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
datastream_individual.ownership()
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
datastream_object.ownership()
}
InterfaceTypeAggregation::Properties(property) => property.ownership(),
}
}
#[must_use]
pub fn aggregation(&self) -> Aggregation {
match &self.inner {
InterfaceTypeAggregation::Properties(_)
| InterfaceTypeAggregation::DatastreamIndividual(_) => Aggregation::Individual,
InterfaceTypeAggregation::DatastreamObject(_) => Aggregation::Object,
}
}
#[cfg(feature = "doc-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
#[must_use]
pub fn description(&self) -> Option<&str> {
match &self.inner {
InterfaceTypeAggregation::Properties(interface) => interface.description(),
InterfaceTypeAggregation::DatastreamIndividual(interface) => interface.description(),
InterfaceTypeAggregation::DatastreamObject(interface) => interface.description(),
}
}
#[cfg(feature = "doc-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
#[must_use]
pub fn doc(&self) -> Option<&str> {
match &self.inner {
InterfaceTypeAggregation::Properties(interface) => interface.doc(),
InterfaceTypeAggregation::DatastreamIndividual(interface) => interface.doc(),
InterfaceTypeAggregation::DatastreamObject(interface) => interface.doc(),
}
}
pub fn validate_with(&self, prev: &Self) -> Result<&Self, Error> {
if self == prev {
return Ok(self);
}
let name = self.interface_name();
let prev_name = prev.interface_name();
if name != prev_name {
return Err(Error::NameMismatch {
name: name.to_string(),
prev_name: prev_name.to_string(),
});
}
VersionChange::try_new(self, prev)
.map_err(Error::VersionChange)
.map(|change| {
info!("Interface {} version changed: {}", name, change);
self
})
}
#[must_use]
pub fn as_datastream_individual(&self) -> Option<&DatastreamIndividual> {
if let InterfaceTypeAggregation::DatastreamIndividual(v) = &self.inner {
Some(v)
} else {
None
}
}
#[must_use]
pub fn as_datastream_object(&self) -> Option<&DatastreamObject> {
if let InterfaceTypeAggregation::DatastreamObject(v) = &self.inner {
Some(v)
} else {
None
}
}
#[must_use]
pub fn as_properties(&self) -> Option<&Properties> {
if let InterfaceTypeAggregation::Properties(v) = &self.inner {
Some(v)
} else {
None
}
}
#[must_use]
pub fn is_datastream_individual(&self) -> bool {
matches!(
self.inner,
InterfaceTypeAggregation::DatastreamIndividual(..)
)
}
#[must_use]
pub fn is_datastream_object(&self) -> bool {
matches!(self.inner, InterfaceTypeAggregation::DatastreamObject(..))
}
#[must_use]
pub fn is_properties(&self) -> bool {
matches!(self.inner, InterfaceTypeAggregation::Properties(..))
}
}
impl<T> TryFrom<InterfaceJson<T>> for Interface
where
T: AsRef<str> + Into<String>,
{
type Error = Error;
fn try_from(value: InterfaceJson<T>) -> Result<Self, Self::Error> {
let inner = match (value.interface_type, value.aggregation.unwrap_or_default()) {
(InterfaceType::Datastream, Aggregation::Individual) => {
let interface = DatastreamIndividual::try_from(value)?;
InterfaceTypeAggregation::DatastreamIndividual(interface)
}
(InterfaceType::Datastream, Aggregation::Object) => {
let interface = DatastreamObject::try_from(value)?;
InterfaceTypeAggregation::DatastreamObject(interface)
}
(InterfaceType::Properties, Aggregation::Individual) => {
let interface = Properties::try_from(value)?;
InterfaceTypeAggregation::Properties(interface)
}
(InterfaceType::Properties, Aggregation::Object) => return Err(Error::PropertyObject),
};
Ok(Interface { inner })
}
}
impl<'a> From<&'a Interface> for InterfaceJson<Cow<'a, str>> {
fn from(value: &'a Interface) -> Self {
match &value.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
Self::from(datastream_individual)
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
Self::from(datastream_object)
}
InterfaceTypeAggregation::Properties(properties) => Self::from(properties),
}
}
}
impl Serialize for Interface {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
datastream_individual.serialize(serializer)
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
datastream_object.serialize(serializer)
}
InterfaceTypeAggregation::Properties(properties) => properties.serialize(serializer),
}
}
}
impl FromStr for Interface {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let interface: InterfaceJson<Cow<str>> = serde_json::from_str(s)?;
Interface::try_from(interface)
}
}
impl Display for Interface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner {
InterfaceTypeAggregation::DatastreamIndividual(datastream_individual) => {
write!(f, "{datastream_individual}")
}
InterfaceTypeAggregation::DatastreamObject(datastream_object) => {
write!(f, "{datastream_object}")
}
InterfaceTypeAggregation::Properties(property) => {
write!(f, "{property}")
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum InterfaceTypeAggregation {
DatastreamIndividual(DatastreamIndividual),
DatastreamObject(DatastreamObject),
Properties(Properties),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum Retention {
#[default]
Discard,
Volatile {
expiry: Option<Duration>,
},
Stored {
expiry: Option<Duration>,
},
}
impl Retention {
#[must_use]
pub const fn is_stored(&self) -> bool {
matches!(self, Self::Stored { .. })
}
#[must_use]
pub const fn as_expiry(&self) -> Option<&Duration> {
match self {
Retention::Discard => None,
Retention::Volatile { expiry } | Retention::Stored { expiry } => expiry.as_ref(),
}
}
#[must_use]
pub fn as_expiry_seconds(&self) -> Option<i64> {
self.as_expiry().map(|duration| {
i64::try_from(duration.as_secs())
.inspect_err(|err| warn!(%err, "expiry conversion error"))
.unwrap_or(i64::MAX)
})
}
#[must_use]
pub const fn is_volatile(&self) -> bool {
matches!(self, Self::Volatile { .. })
}
#[must_use]
pub const fn is_discard(&self) -> bool {
matches!(self, Self::Discard)
}
}
impl From<Retention> for crate::schema::Retention {
fn from(value: Retention) -> Self {
match value {
Retention::Discard => crate::schema::Retention::Discard,
Retention::Volatile { .. } => crate::schema::Retention::Volatile,
Retention::Stored { .. } => crate::schema::Retention::Stored,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum DatabaseRetention {
#[default]
NoTtl,
UseTtl {
ttl: Duration,
},
}
impl DatabaseRetention {
#[must_use]
pub fn is_no_ttl(&self) -> bool {
matches!(self, Self::NoTtl)
}
#[must_use]
pub fn is_use_ttl(&self) -> bool {
matches!(self, Self::UseTtl { .. })
}
#[must_use]
pub fn as_ttl(&self) -> Option<&Duration> {
match self {
DatabaseRetention::NoTtl => None,
DatabaseRetention::UseTtl { ttl } => Some(ttl),
}
}
#[must_use]
pub fn as_ttl_secs(&self) -> Option<i64> {
self.as_ttl().map(|ttl| {
i64::try_from(ttl.as_secs())
.inspect_err(|err| warn!(%err, "ttl conversion error"))
.unwrap_or(i64::MAX)
})
}
}
impl From<DatabaseRetention> for DatabaseRetentionPolicy {
fn from(value: DatabaseRetention) -> Self {
match value {
DatabaseRetention::NoTtl => DatabaseRetentionPolicy::NoTtl,
DatabaseRetention::UseTtl { .. } => DatabaseRetentionPolicy::UseTtl,
}
}
}
pub trait Schema {
type Mapping: Sized;
fn name(&self) -> &str;
fn interface_name(&self) -> &InterfaceName;
fn version_major(&self) -> i32;
fn version_minor(&self) -> i32;
fn version(&self) -> InterfaceVersion;
fn interface_type(&self) -> InterfaceType;
fn ownership(&self) -> Ownership;
fn aggregation(&self) -> Aggregation;
#[cfg(feature = "doc-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
fn description(&self) -> Option<&str>;
#[cfg(feature = "doc-fields")]
#[cfg_attr(docsrs, doc(cfg(feature = "doc-fields")))]
fn doc(&self) -> Option<&str>;
fn iter_mappings(&self) -> impl Iterator<Item = &Self::Mapping>;
fn mappings_len(&self) -> usize;
fn iter_interface_mappings(&self) -> impl Iterator<Item = Mapping<Cow<'_, str>>>;
}
pub trait AggregationIndividual: Schema {
fn mapping(&self, path: &MappingPath) -> Option<&Self::Mapping>;
}
impl<'a, T> From<&'a T> for InterfaceJson<Cow<'a, str>>
where
T: Schema,
{
fn from(value: &'a T) -> Self {
InterfaceJson {
interface_name: value.interface_name().as_str().into(),
version_major: value.version_major(),
version_minor: value.version_minor(),
interface_type: value.interface_type(),
ownership: value.ownership(),
aggregation: Some(value.aggregation()),
#[cfg(feature = "doc-fields")]
description: value.description().map(Cow::from),
#[cfg(feature = "doc-fields")]
doc: value.doc().map(Cow::from),
#[cfg(not(feature = "doc-fields"))]
description: None,
#[cfg(not(feature = "doc-fields"))]
doc: None,
mappings: value.iter_interface_mappings().collect(),
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use std::str::FromStr;
use pretty_assertions::assert_eq;
use super::*;
use crate::{
mapping::{InterfaceMapping, MappingError},
schema::{InterfaceType, MappingType, Ownership, Reliability},
DatastreamIndividual, DatastreamIndividualMapping, Endpoint, Interface, MappingPath,
Schema,
};
const E2E_DEVICE_PROPERTY: &str =
include_str!("../../interfaces/org.astarte-platform.rust.e2etest.DeviceProperty.json");
const E2E_DEVICE_PROPERTY_NAME: &str = "org.astarte-platform.rust.e2etest.DeviceProperty";
pub(crate) const E2E_DEVICE_AGGREGATE: &str =
include_str!("../../interfaces/org.astarte-platform.rust.e2etest.DeviceAggregate.json");
const E2E_DEVICE_AGGREGATE_NAME: &str = "org.astarte-platform.rust.e2etest.DeviceAggregate";
const E2E_DEVICE_DATASTREAM: &str =
include_str!("../../interfaces/org.astarte-platform.rust.e2etest.DeviceDatastream.json");
const E2E_DEVICE_DATASTREAM_NAME: &str = "org.astarte-platform.rust.e2etest.DeviceDatastream";
#[cfg(feature = "doc-fields")]
const INTERFACE_JSON: &str = r#"{
"interface_name": "org.astarte-platform.genericsensors.Values",
"version_major": 1,
"version_minor": 0,
"type": "datastream",
"ownership": "device",
"description": "Interface description",
"doc": "Interface doc",
"mappings": [
{
"endpoint": "/%{sensor_id}/otherValue",
"type": "longinteger",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
},
{
"endpoint": "/%{sensor_id}/value",
"type": "double",
"explicit_timestamp": true,
"description": "Mapping description",
"doc": "Mapping doc"
}
]
}"#;
#[cfg(not(feature = "doc-fields"))]
const INTERFACE_JSON: &str = r#"{
"interface_name": "org.astarte-platform.genericsensors.Values",
"version_major": 1,
"version_minor": 0,
"type": "datastream",
"ownership": "device",
"mappings": [
{
"endpoint": "/%{sensor_id}/otherValue",
"type": "longinteger",
"explicit_timestamp": true
},
{
"endpoint": "/%{sensor_id}/value",
"type": "double",
"explicit_timestamp": true
}
]
}"#;
const PROPERTIES_JSON: &str = r#"{
"interface_name": "org.astarte-platform.genericproperties.Values",
"version_major": 1,
"version_minor": 0,
"type": "properties",
"ownership": "server",
"description": "Interface description",
"doc": "Interface doc",
"mappings": [
{
"endpoint": "/%{sensor_id}/aaaa",
"type": "longinteger",
"allow_unset": true
},
{
"endpoint": "/%{sensor_id}/bbbb",
"type": "double",
"allow_unset": false
}
]
}"#;
#[test]
fn datastream_interface_deserialization() {
let value_mapping = DatastreamIndividualMapping {
endpoint: Endpoint::try_from("/%{sensor_id}/value").unwrap(),
mapping_type: MappingType::Double,
reliability: Reliability::default(),
retention: Retention::default(),
#[cfg(feature = "server-fields")]
database_retention: DatabaseRetention::default(),
explicit_timestamp: true,
#[cfg(feature = "doc-fields")]
description: Some("Mapping description".to_string()),
#[cfg(feature = "doc-fields")]
doc: Some("Mapping doc".to_string()),
};
let other_value_mapping = DatastreamIndividualMapping {
endpoint: Endpoint::try_from("/%{sensor_id}/otherValue").unwrap(),
mapping_type: MappingType::LongInteger,
reliability: Reliability::default(),
retention: Retention::default(),
#[cfg(feature = "server-fields")]
database_retention: DatabaseRetention::default(),
explicit_timestamp: true,
#[cfg(feature = "doc-fields")]
description: Some("Mapping description".to_string()),
#[cfg(feature = "doc-fields")]
doc: Some("Mapping doc".to_string()),
};
let interface_name =
InterfaceName::try_from("org.astarte-platform.genericsensors.Values").unwrap();
let version = InterfaceVersion::try_from((1, 0)).unwrap();
let ownership = Ownership::Device;
#[cfg(feature = "doc-fields")]
let description = Some("Interface description".to_owned());
#[cfg(feature = "doc-fields")]
let doc = Some("Interface doc".to_owned());
let mappings = MappingVec::try_from([other_value_mapping, value_mapping].to_vec()).unwrap();
let datastream_individual = DatastreamIndividual {
name: interface_name.into_string(),
version,
ownership,
#[cfg(feature = "doc-fields")]
description,
#[cfg(feature = "doc-fields")]
doc,
mappings,
};
let interface = Interface {
inner: InterfaceTypeAggregation::DatastreamIndividual(datastream_individual),
};
let deser_interface = Interface::from_str(INTERFACE_JSON).unwrap();
assert_eq!(interface, deser_interface);
}
#[test]
fn must_have_one_mapping() {
let json = r#"{
"interface_name": "org.astarte-platform.genericproperties.Values",
"version_major": 1,
"version_minor": 0,
"type": "properties",
"ownership": "server",
"description": "Interface description",
"doc": "Interface doc",
"mappings": []
}"#;
let interface = Interface::from_str(json);
let err = interface.unwrap_err();
assert!(matches!(err, Error::Mapping(MappingError::Empty)));
}
#[test]
fn test_properties() {
let interface = Interface::from_str(PROPERTIES_JSON).unwrap();
let exp = Properties::from_str(PROPERTIES_JSON).unwrap();
assert_eq!(
*interface.inner(),
InterfaceTypeAggregation::Properties(exp)
);
assert_eq!(interface.interface_type(), InterfaceType::Properties);
let exp = InterfaceVersion::try_new(1, 0).unwrap();
assert_eq!(interface.version(), exp);
assert_eq!(interface.version_major(), 1);
assert_eq!(interface.version_minor(), 0);
let InterfaceTypeAggregation::Properties(interface) = interface.inner else {
panic!()
};
let paths: Vec<_> = interface.iter_mappings().collect();
assert_eq!(paths.len(), 2);
assert_eq!(paths[0].endpoint().to_string(), "/%{sensor_id}/aaaa");
assert_eq!(paths[1].endpoint().to_string(), "/%{sensor_id}/bbbb");
let path = MappingPath::try_from("/1/aaaa").unwrap();
let f = interface.mapping(&path).unwrap();
assert_eq!(f.mapping_type(), MappingType::LongInteger);
assert!(f.allow_unset());
}
#[test]
fn test_iter_mappings() {
let value_mapping = DatastreamIndividualMapping {
endpoint: Endpoint::try_from("/%{sensor_id}/value").unwrap(),
mapping_type: MappingType::Double,
#[cfg(feature = "doc-fields")]
description: Some("Mapping description".to_string()),
#[cfg(feature = "doc-fields")]
doc: Some("Mapping doc".to_string()),
reliability: Reliability::default(),
retention: Retention::default(),
#[cfg(feature = "server-fields")]
database_retention: DatabaseRetention::default(),
explicit_timestamp: true,
};
let other_value_mapping = DatastreamIndividualMapping {
endpoint: Endpoint::try_from("/%{sensor_id}/otherValue").unwrap(),
mapping_type: MappingType::LongInteger,
#[cfg(feature = "doc-fields")]
description: Some("Mapping description".to_string()),
#[cfg(feature = "doc-fields")]
doc: Some("Mapping doc".to_string()),
reliability: Reliability::default(),
retention: Retention::default(),
#[cfg(feature = "server-fields")]
database_retention: DatabaseRetention::default(),
explicit_timestamp: true,
};
let interface = Interface::from_str(INTERFACE_JSON).unwrap();
let interface = interface.as_datastream_individual().unwrap();
let mut mappings = interface.iter_mappings();
assert_eq!(mappings.next(), Some(&other_value_mapping));
assert_eq!(mappings.next(), Some(&value_mapping));
assert_eq!(mappings.next(), None);
}
#[test]
fn methods_test() {
let interface = Interface::from_str(INTERFACE_JSON).unwrap();
assert_eq!(
interface.interface_name(),
"org.astarte-platform.genericsensors.Values"
);
assert_eq!(interface.version_major(), 1);
assert_eq!(interface.version_minor(), 0);
assert_eq!(interface.ownership(), Ownership::Device);
#[cfg(feature = "doc-fields")]
assert_eq!(interface.description(), Some("Interface description"));
assert_eq!(interface.aggregation(), Aggregation::Individual);
assert_eq!(interface.interface_type(), InterfaceType::Datastream);
#[cfg(feature = "doc-fields")]
assert_eq!(interface.doc(), Some("Interface doc"));
}
#[test]
fn serialize_and_deserialize() {
let interface = Interface::from_str(INTERFACE_JSON).unwrap();
let serialized = serde_json::to_string(&interface).unwrap();
let deserialized: Interface = serde_json::from_str(&serialized).unwrap();
assert_eq!(interface, deserialized);
let value = serde_json::Value::from_str(&serialized).unwrap();
let expected = serde_json::Value::from_str(INTERFACE_JSON).unwrap();
assert_eq!(value, expected);
}
#[test]
fn check_as_prop() {
let interface = Interface::from_str(PROPERTIES_JSON).unwrap();
interface.as_properties().expect("interface is a property");
let interface = Interface::from_str(INTERFACE_JSON).unwrap();
assert_eq!(interface.as_properties(), None);
}
#[cfg(feature = "doc-fields")]
#[test]
fn test_with_escaped_descriptions() {
let json = r#"{
"interface_name": "org.astarte-platform.genericproperties.Values",
"version_major": 1,
"version_minor": 0,
"type": "properties",
"ownership": "server",
"description": "Interface description \"escaped\"",
"doc": "Interface doc \"escaped\"",
"mappings": [{
"endpoint": "/double_endpoint",
"type": "double",
"doc": "Mapping doc \"escaped\""
}]
}"#;
let interface = Interface::from_str(json).unwrap();
let interface = interface.as_properties().unwrap();
assert_eq!(
interface.description().unwrap(),
r#"Interface description "escaped""#
);
assert_eq!(interface.doc().unwrap(), r#"Interface doc "escaped""#);
let mapping_doc = interface
.mapping(&MappingPath::try_from("/double_endpoint").unwrap())
.unwrap()
.doc()
.unwrap();
assert_eq!(mapping_doc, r#"Mapping doc "escaped""#);
}
#[test]
fn should_convert_into_inner() {
let interface = Interface::from_str(E2E_DEVICE_PROPERTY).unwrap();
assert!(interface.as_properties().is_some());
assert!(interface.as_datastream_object().is_none());
assert!(interface.as_datastream_individual().is_none());
assert!(interface.is_properties());
assert!(!interface.is_datastream_object());
assert!(!interface.is_datastream_object());
let interface = interface.as_properties().unwrap();
assert_eq!(interface.mappings_len(), 14);
let interface = Interface::from_str(E2E_DEVICE_AGGREGATE).unwrap();
assert!(interface.as_properties().is_none());
assert!(interface.as_datastream_object().is_some());
assert!(interface.as_datastream_individual().is_none());
assert!(!interface.is_properties());
assert!(interface.is_datastream_object());
assert!(!interface.is_datastream_individual());
let interface = interface.as_datastream_object().unwrap();
assert_eq!(interface.mappings_len(), 14);
let interface = Interface::from_str(E2E_DEVICE_DATASTREAM).unwrap();
assert!(interface.as_properties().is_none());
assert!(interface.as_datastream_object().is_none());
assert!(interface.as_datastream_individual().is_some());
assert!(!interface.is_properties());
assert!(!interface.is_datastream_object());
assert!(interface.is_datastream_individual());
let interface = interface.as_datastream_individual().unwrap();
assert_eq!(interface.mappings_len(), 14);
}
#[test]
fn test_interface_getters() {
let version = InterfaceVersion::try_new(0, 1).unwrap();
let interface = Interface::from_str(E2E_DEVICE_DATASTREAM).unwrap();
assert_eq!(interface.interface_name(), E2E_DEVICE_DATASTREAM_NAME);
assert_eq!(interface.version(), version);
assert_eq!(interface.version_major(), 0);
assert_eq!(interface.version_minor(), 1);
assert_eq!(interface.interface_type(), InterfaceType::Datastream);
assert_eq!(interface.ownership(), Ownership::Device);
assert_eq!(interface.aggregation(), Aggregation::Individual);
#[cfg(feature = "doc-fields")]
assert_eq!(interface.description(), Some("Test datastream interface."));
#[cfg(feature = "doc-fields")]
assert_eq!(
interface.doc(),
Some("Test interface used to test datastream.")
);
let interface = Interface::from_str(E2E_DEVICE_AGGREGATE).unwrap();
assert_eq!(interface.interface_name(), E2E_DEVICE_AGGREGATE_NAME);
assert_eq!(interface.version(), version);
assert_eq!(interface.version_major(), 0);
assert_eq!(interface.version_minor(), 1);
assert_eq!(interface.interface_type(), InterfaceType::Datastream);
assert_eq!(interface.ownership(), Ownership::Device);
assert_eq!(interface.aggregation(), Aggregation::Object);
#[cfg(feature = "doc-fields")]
assert_eq!(interface.description(), Some("Test aggregate interface."));
#[cfg(feature = "doc-fields")]
assert_eq!(
interface.doc(),
Some("Test interface used to test aggregates.")
);
let interface = Interface::from_str(E2E_DEVICE_PROPERTY).unwrap();
assert_eq!(interface.interface_name(), E2E_DEVICE_PROPERTY_NAME);
assert_eq!(interface.version(), version);
assert_eq!(interface.version_major(), 0);
assert_eq!(interface.version_minor(), 1);
assert_eq!(interface.interface_type(), InterfaceType::Properties);
assert_eq!(interface.ownership(), Ownership::Device);
assert_eq!(interface.aggregation(), Aggregation::Individual);
#[cfg(feature = "doc-fields")]
assert_eq!(interface.description(), Some("Test properties interface."));
#[cfg(feature = "doc-fields")]
assert_eq!(
interface.doc(),
Some("Test interface used to test properties.")
);
}
}