pub mod def;
pub mod error;
pub mod mapping;
pub mod reference;
pub(crate) mod validation;
use log::info;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::fmt::Display;
use std::str::FromStr;
use std::time::Duration;
pub(crate) use self::def::{
Aggregation, InterfaceTypeDef, Mapping, MappingType, Ownership, Reliability,
};
use self::error::InterfaceError;
use self::mapping::vec::Item;
use self::mapping::InterfaceMapping;
use self::reference::{MappingRef, ObjectRef, PropertyRef};
use self::{
def::{DatabaseRetentionPolicyDef, InterfaceDef, RetentionDef},
mapping::{
iter::{IndividualMappingIter, MappingIter, ObjectMappingIter, PropertiesMappingIter},
path::MappingPath,
vec::MappingVec,
BaseMapping, DatastreamIndividualMapping, PropertiesMapping,
},
validation::VersionChange,
};
pub(crate) type MappingSet<T> = BTreeSet<Item<T>>;
pub const MAX_INTERFACE_MAPPINGS: usize = 1024;
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(try_from = "InterfaceDef<std::borrow::Cow<str>>")]
pub struct Interface {
interface_name: String,
version_major: i32,
version_minor: i32,
ownership: Ownership,
#[cfg(feature = "interface-doc")]
description: Option<String>,
#[cfg(feature = "interface-doc")]
doc: Option<String>,
pub(crate) inner: InterfaceType,
}
impl Interface {
pub fn interface_name(&self) -> &str {
&self.interface_name
}
pub fn version_major(&self) -> i32 {
self.version_major
}
pub fn version_minor(&self) -> i32 {
self.version_minor
}
fn version(&self) -> (i32, i32) {
(self.version_major, self.version_minor)
}
pub fn interface_type(&self) -> InterfaceTypeDef {
match &self.inner {
InterfaceType::DatastreamIndividual(_) | InterfaceType::DatastreamObject(_) => {
InterfaceTypeDef::Datastream
}
InterfaceType::Properties(_) => InterfaceTypeDef::Properties,
}
}
pub fn ownership(&self) -> Ownership {
self.ownership
}
pub fn aggregation(&self) -> Aggregation {
match &self.inner {
InterfaceType::Properties(_) | InterfaceType::DatastreamIndividual(_) => {
Aggregation::Individual
}
InterfaceType::DatastreamObject(_) => Aggregation::Object,
}
}
#[cfg(feature = "interface-doc")]
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
#[cfg(feature = "interface-doc")]
pub fn doc(&self) -> Option<&str> {
self.doc.as_deref()
}
pub fn iter_mappings(&self) -> MappingIter {
MappingIter::new(&self.inner)
}
pub(crate) fn mapping(&self, path: &MappingPath) -> Option<Mapping<&str>> {
match &self.inner {
InterfaceType::DatastreamIndividual(individual) => individual.mapping(path),
InterfaceType::DatastreamObject(object) => object.mapping(path),
InterfaceType::Properties(properties) => properties.mapping(path),
}
}
pub fn mappings_len(&self) -> usize {
match &self.inner {
InterfaceType::DatastreamIndividual(datastream) => datastream.mappings.len(),
InterfaceType::DatastreamObject(datastream) => datastream.mappings.len(),
InterfaceType::Properties(properties) => properties.mappings.len(),
}
}
pub(crate) fn as_mapping_ref<'a>(
&'a self,
path: &'a MappingPath,
) -> Option<MappingRef<&Interface>> {
MappingRef::new(self, path)
}
pub fn is_property(&self) -> bool {
matches!(self.inner, InterfaceType::Properties(_))
}
pub(crate) fn as_prop_ref(&self) -> Option<PropertyRef> {
self.is_property().then_some(PropertyRef(self))
}
pub fn is_object(&self) -> bool {
matches!(self.inner, InterfaceType::DatastreamObject(_))
}
pub(crate) fn as_object_ref(&self) -> Option<ObjectRef> {
ObjectRef::new(self)
}
pub fn validate(&self) -> Result<(), InterfaceError> {
if self.version() == (0, 0) {
return Err(InterfaceError::MajorMinor);
}
if self.mappings_len() == 0 {
return Err(InterfaceError::EmptyMappings);
}
if self.mappings_len() > MAX_INTERFACE_MAPPINGS {
return Err(InterfaceError::TooManyMappings(self.mappings_len()));
}
Ok(())
}
pub fn validate_with(&self, prev: &Self) -> Result<&Self, InterfaceError> {
if self == prev {
return Ok(self);
}
let name = self.interface_name();
let prev_name = prev.interface_name();
if name != prev_name {
return Err(InterfaceError::NameMismatch {
name: name.to_string(),
prev_name: prev_name.to_string(),
});
}
VersionChange::try_new(self, prev)
.map_err(InterfaceError::Version)
.map(|change| {
info!("Interface {} version changed: {}", name, change);
self
})
}
pub fn as_prop(&self) -> Option<PropertyRef> {
match self.inner {
InterfaceType::DatastreamIndividual(_) | InterfaceType::DatastreamObject(_) => None,
InterfaceType::Properties(_) => Some(PropertyRef(self)),
}
}
pub fn as_inner(&self) -> &InterfaceType {
&self.inner
}
pub fn into_inner(self) -> InterfaceType {
self.inner
}
}
impl Serialize for Interface {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let interface_def = InterfaceDef::from(self);
interface_def.serialize(serializer)
}
}
impl FromStr for Interface {
type Err = InterfaceError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s).map_err(Self::Err::from)
}
}
impl Display for Interface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}",
self.interface_name, self.version_major, self.version_minor
)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum InterfaceType {
DatastreamIndividual(DatastreamIndividual),
DatastreamObject(DatastreamObject),
Properties(Properties),
}
impl InterfaceType {
pub fn as_datastream_individual(&self) -> Option<&DatastreamIndividual> {
if let Self::DatastreamIndividual(v) = self {
Some(v)
} else {
None
}
}
pub fn as_datastream_object(&self) -> Option<&DatastreamObject> {
if let Self::DatastreamObject(v) = self {
Some(v)
} else {
None
}
}
pub fn as_properties(&self) -> Option<&Properties> {
if let Self::Properties(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub fn is_datastream_individual(&self) -> bool {
matches!(self, Self::DatastreamIndividual(..))
}
#[must_use]
pub fn is_datastream_object(&self) -> bool {
matches!(self, Self::DatastreamObject(..))
}
#[must_use]
pub fn is_properties(&self) -> bool {
matches!(self, Self::Properties(..))
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DatastreamIndividual {
mappings: MappingVec<DatastreamIndividualMapping>,
}
impl DatastreamIndividual {
pub fn iter_mappings(&self) -> IndividualMappingIter {
IndividualMappingIter::new(&self.mappings)
}
pub(crate) fn add_mapping<T>(
tree: &mut MappingSet<DatastreamIndividualMapping>,
mapping: &Mapping<T>,
) -> Result<(), InterfaceError>
where
T: AsRef<str> + Into<String>,
{
let individual = Item::new(DatastreamIndividualMapping::try_from(mapping)?);
if let Some(existing) = tree.get(&individual) {
return Err(InterfaceError::DuplicateMapping {
endpoint: existing.endpoint().to_string(),
duplicate: mapping.endpoint().as_ref().into(),
});
}
tree.insert(individual);
Ok(())
}
}
impl MappingAccess for DatastreamIndividual {
type Mapping = DatastreamIndividualMapping;
fn get(&self, path: &MappingPath) -> Option<&Self::Mapping> {
self.mappings.get(path)
}
fn len(&self) -> usize {
self.mappings.len()
}
}
impl<'a> IntoIterator for &'a DatastreamIndividual {
type Item = Mapping<&'a str>;
type IntoIter = IndividualMappingIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mappings()
}
}
impl<'a, T> TryFrom<&'a InterfaceDef<T>> for DatastreamIndividual
where
T: AsRef<str> + Into<String>,
{
type Error = InterfaceError;
fn try_from(value: &InterfaceDef<T>) -> Result<Self, Self::Error> {
let mut btree = MappingSet::new();
for mapping in value.mappings.iter() {
Self::add_mapping(&mut btree, mapping)?;
}
Ok(Self {
mappings: MappingVec::from(btree),
})
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DatastreamObject {
reliability: Reliability,
explicit_timestamp: bool,
retention: Retention,
database_retention: DatabaseRetention,
mappings: MappingVec<BaseMapping>,
}
impl DatastreamObject {
pub(crate) fn apply<'a>(&self, base_mapping: &'a BaseMapping) -> Mapping<&'a str> {
let mut mapping = Mapping::from(base_mapping);
mapping.reliability = self.reliability;
mapping.explicit_timestamp = self.explicit_timestamp;
self.retention.apply(&mut mapping);
self.database_retention.apply(&mut mapping);
mapping
}
pub fn iter_mappings(&self) -> ObjectMappingIter {
ObjectMappingIter::new(self)
}
pub(crate) fn is_compatible<T>(&self, mapping: &Mapping<T>) -> bool {
mapping.reliability() == self.reliability
&& mapping.explicit_timestamp() == self.explicit_timestamp
&& mapping.retention() == self.retention
&& mapping.database_retention() == self.database_retention
}
pub(crate) fn add_mapping<T>(
&self,
btree: &mut MappingSet<BaseMapping>,
mapping: &Mapping<T>,
) -> Result<(), InterfaceError>
where
T: AsRef<str> + Into<String>,
{
if !self.is_compatible(mapping) {
return Err(InterfaceError::InconsistentMapping);
}
let mapping = Item::new(BaseMapping::try_from(mapping)?);
if mapping.endpoint().len() < 2 {
return Err(InterfaceError::ObjectEndpointTooShort(
mapping.endpoint().to_string(),
));
}
if let Some(entry) = self.mappings.iter().next() {
if !entry.endpoint().is_same_object(mapping.endpoint()) {
return Err(InterfaceError::InconsistentEndpoints);
}
}
if let Some(existing) = btree.get(&mapping) {
return Err(InterfaceError::DuplicateMapping {
endpoint: existing.endpoint().to_string(),
duplicate: mapping.endpoint().to_string(),
});
}
btree.insert(mapping);
Ok(())
}
pub(crate) fn get_field(&self, base: &MappingPath, field: &str) -> Option<&BaseMapping> {
self.mappings.get(&(base, field))
}
pub fn reliability(&self) -> Reliability {
self.reliability
}
pub fn explicit_timestamp(&self) -> bool {
self.explicit_timestamp
}
}
impl MappingAccess for DatastreamObject {
type Mapping = BaseMapping;
fn get(&self, path: &MappingPath) -> Option<&Self::Mapping> {
self.mappings.get(path)
}
fn len(&self) -> usize {
self.mappings.len()
}
}
impl<'a> IntoIterator for &'a DatastreamObject {
type Item = Mapping<&'a str>;
type IntoIter = ObjectMappingIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mappings()
}
}
impl<T> TryFrom<&InterfaceDef<T>> for DatastreamObject
where
T: AsRef<str> + Into<String>,
{
type Error = InterfaceError;
fn try_from(value: &InterfaceDef<T>) -> Result<Self, Self::Error> {
let mut mappings_iter = value.mappings.iter();
let mut btree = MappingSet::new();
let first = mappings_iter.next().ok_or(InterfaceError::EmptyMappings)?;
let first_base = BaseMapping::try_from(first)?;
btree.insert(Item::new(first_base));
let mut object = Self {
reliability: first.reliability(),
explicit_timestamp: first.explicit_timestamp(),
retention: first.retention(),
database_retention: first.database_retention(),
mappings: MappingVec::new(),
};
for mapping in mappings_iter {
object.add_mapping(&mut btree, mapping)?;
}
object.mappings = MappingVec::from(btree);
Ok(object)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Properties {
mappings: MappingVec<PropertiesMapping>,
}
impl Properties {
pub fn iter_mappings(&self) -> PropertiesMappingIter {
PropertiesMappingIter::new(&self.mappings)
}
pub(crate) fn add_mapping<T>(
btree: &mut MappingSet<PropertiesMapping>,
mapping: &Mapping<T>,
) -> Result<(), InterfaceError>
where
T: AsRef<str> + Into<String>,
{
let property = Item::new(PropertiesMapping::try_from(mapping)?);
if let Some(existing) = btree.get(&property) {
return Err(InterfaceError::DuplicateMapping {
endpoint: existing.endpoint().to_string(),
duplicate: mapping.endpoint().as_ref().into(),
});
}
btree.insert(property);
Ok(())
}
}
impl MappingAccess for Properties {
type Mapping = PropertiesMapping;
fn get(&self, path: &MappingPath) -> Option<&Self::Mapping> {
self.mappings.get(path)
}
fn len(&self) -> usize {
self.mappings.len()
}
}
impl<'a> IntoIterator for &'a Properties {
type Item = Mapping<&'a str>;
type IntoIter = PropertiesMappingIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mappings()
}
}
impl<T> TryFrom<&InterfaceDef<T>> for Properties
where
T: AsRef<str> + Into<String>,
{
type Error = InterfaceError;
fn try_from(value: &InterfaceDef<T>) -> Result<Self, Self::Error> {
let mut btree = MappingSet::new();
for mapping in value.mappings.iter() {
Self::add_mapping(&mut btree, mapping)?;
}
Ok(Self {
mappings: MappingVec::from(btree),
})
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Retention {
Discard,
Volatile {
expiry: Option<Duration>,
},
Stored {
expiry: Option<Duration>,
},
}
impl Retention {
pub(self) fn apply<T>(&self, mapping: &mut Mapping<T>) {
match self {
Retention::Discard => {
mapping.retention = RetentionDef::Discard;
mapping.expiry = 0;
}
Retention::Volatile { expiry } => {
mapping.retention = RetentionDef::Volatile;
mapping.expiry = expiry
.map(|t| t.as_secs().try_into().unwrap_or(i64::MAX))
.unwrap_or(0);
}
Retention::Stored { expiry } => {
mapping.retention = RetentionDef::Stored;
mapping.expiry = expiry
.map(|t| t.as_secs().try_into().unwrap_or(i64::MAX))
.unwrap_or(0);
}
}
}
}
impl Default for Retention {
fn default() -> Self {
Self::Discard
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum DatabaseRetention {
NoTtl,
UseTtl {
ttl: Duration,
},
}
impl DatabaseRetention {
pub(self) fn apply<T>(&self, mapping: &mut Mapping<T>) {
match self {
DatabaseRetention::NoTtl => {
mapping.database_retention_policy = DatabaseRetentionPolicyDef::NoTtl;
mapping.database_retention_ttl = None;
}
DatabaseRetention::UseTtl { ttl } => {
mapping.database_retention_policy = DatabaseRetentionPolicyDef::UseTtl;
mapping.database_retention_ttl = Some(ttl.as_secs().try_into().unwrap_or(i64::MAX));
}
}
}
}
impl Default for DatabaseRetention {
fn default() -> Self {
Self::NoTtl
}
}
pub trait MappingAccess
where
for<'a> &'a Self: IntoIterator<Item = Mapping<&'a str>>,
for<'a> &'a Self::Mapping: Into<Mapping<&'a str>>,
{
type Mapping: InterfaceMapping;
fn get(&self, path: &MappingPath) -> Option<&Self::Mapping>;
fn len(&self) -> usize;
fn mapping(&self, path: &MappingPath) -> Option<Mapping<&str>> {
self.get(path).map(Into::into)
}
fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::{
interface::{
def::{DatabaseRetentionPolicyDef, RetentionDef},
mapping::{
path::MappingPath,
vec::{Item, MappingVec},
BaseMapping, DatastreamIndividualMapping,
},
Aggregation, DatabaseRetention, DatastreamIndividual, InterfaceType, InterfaceTypeDef,
Mapping, MappingAccess, MappingSet, MappingType, Ownership, Reliability, Retention,
},
test::{E2E_DEVICE_AGGREGATE, E2E_DEVICE_DATASTREAM, E2E_DEVICE_PROPERTY},
Interface,
};
#[cfg(feature = "interface-doc")]
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 = "interface-doc"))]
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 {
mapping: BaseMapping {
endpoint: "/%{sensor_id}/value".try_into().unwrap(),
mapping_type: MappingType::Double,
#[cfg(feature = "interface-doc")]
description: Some("Mapping description".to_string()),
#[cfg(feature = "interface-doc")]
doc: Some("Mapping doc".to_string()),
},
reliability: Reliability::default(),
retention: Retention::default(),
database_retention: DatabaseRetention::default(),
explicit_timestamp: true,
};
let other_value_mapping = DatastreamIndividualMapping {
mapping: BaseMapping {
endpoint: "/%{sensor_id}/otherValue".try_into().unwrap(),
mapping_type: MappingType::LongInteger,
#[cfg(feature = "interface-doc")]
description: Some("Mapping description".to_string()),
#[cfg(feature = "interface-doc")]
doc: Some("Mapping doc".to_string()),
},
reliability: Reliability::default(),
retention: Retention::default(),
database_retention: DatabaseRetention::default(),
explicit_timestamp: true,
};
let interface_name = "org.astarte-platform.genericsensors.Values".to_owned();
let version_major = 1;
let version_minor = 0;
let ownership = Ownership::Device;
#[cfg(feature = "interface-doc")]
let description = Some("Interface description".to_owned());
#[cfg(feature = "interface-doc")]
let doc = Some("Interface doc".to_owned());
let btree = MappingSet::from_iter(
[value_mapping, other_value_mapping]
.into_iter()
.map(Item::new),
);
let datastream_individual = DatastreamIndividual {
mappings: MappingVec::from(btree),
};
let interface = Interface {
interface_name,
version_major,
version_minor,
ownership,
#[cfg(feature = "interface-doc")]
description,
#[cfg(feature = "interface-doc")]
doc,
inner: InterfaceType::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);
assert!(interface.is_err());
let err = format!("{:?}", interface.unwrap_err());
assert!(err.contains("no mappings"), "Unexpected error: {}", err);
}
#[test]
fn test_properties() {
let interface = Interface::from_str(PROPERTIES_JSON).unwrap();
assert!(interface.is_property(), "Properties interface not found");
assert_eq!(interface.version(), (1, 0));
assert_eq!(interface.version_major(), 1);
assert_eq!(interface.version_minor(), 0);
let paths: Vec<_> = interface.iter_mappings().collect();
assert_eq!(paths.len(), 2);
assert_eq!(*paths[0].endpoint(), "/%{sensor_id}/aaaa");
assert_eq!(*paths[1].endpoint(), "/%{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 = Mapping {
endpoint: "/%{sensor_id}/value",
mapping_type: MappingType::Double,
#[cfg(feature = "interface-doc")]
description: Some("Mapping description"),
#[cfg(feature = "interface-doc")]
doc: Some("Mapping doc"),
#[cfg(not(feature = "interface-doc"))]
description: (),
#[cfg(not(feature = "interface-doc"))]
doc: (),
reliability: Reliability::default(),
retention: RetentionDef::default(),
database_retention_policy: DatabaseRetentionPolicyDef::default(),
database_retention_ttl: None,
allow_unset: false,
expiry: 0,
explicit_timestamp: true,
};
let other_value_mapping = Mapping {
endpoint: "/%{sensor_id}/otherValue",
mapping_type: MappingType::LongInteger,
#[cfg(feature = "interface-doc")]
description: Some("Mapping description"),
#[cfg(feature = "interface-doc")]
doc: Some("Mapping doc"),
#[cfg(not(feature = "interface-doc"))]
description: (),
#[cfg(not(feature = "interface-doc"))]
doc: (),
reliability: Reliability::default(),
retention: RetentionDef::default(),
database_retention_policy: DatabaseRetentionPolicyDef::default(),
database_retention_ttl: None,
allow_unset: false,
expiry: 0,
explicit_timestamp: true,
};
let interface = Interface::from_str(INTERFACE_JSON).unwrap();
let mut mappings_iter = interface.iter_mappings();
assert_eq!(mappings_iter.len(), 2);
assert_eq!(mappings_iter.next(), Some(other_value_mapping));
assert_eq!(mappings_iter.next(), Some(value_mapping));
}
#[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 = "interface-doc")]
assert_eq!(interface.description(), Some("Interface description"));
assert_eq!(interface.aggregation(), Aggregation::Individual);
assert_eq!(interface.interface_type(), InterfaceTypeDef::Datastream);
#[cfg(feature = "interface-doc")]
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();
let prop = interface.as_prop().expect("interface is a property");
assert!(std::ptr::eq(prop.0, &interface));
let interface = Interface::from_str(INTERFACE_JSON).unwrap();
assert_eq!(interface.as_prop(), None);
}
#[cfg(feature = "interface-doc")]
#[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();
assert_eq!(
interface.description().unwrap(),
r#"Interface description "escaped""#
);
assert_eq!(interface.doc().unwrap(), r#"Interface doc "escaped""#);
assert_eq!(
*interface
.mapping(&crate::interface::mapping::path::tests::mapping(
"/double_endpoint"
))
.unwrap()
.doc()
.unwrap(),
r#"Mapping doc "escaped""#
);
}
#[test]
fn should_convert_into_inner() {
let interface = Interface::from_str(E2E_DEVICE_PROPERTY).unwrap();
assert!(interface.as_inner().as_properties().is_some());
assert!(interface.as_inner().as_datastream_object().is_none());
assert!(interface.as_inner().as_datastream_individual().is_none());
assert!(interface.as_inner().is_properties());
assert!(!interface.as_inner().is_datastream_object());
assert!(!interface.as_inner().is_datastream_object());
let inner = interface.into_inner();
let interface = inner.as_properties().unwrap();
assert_eq!(interface.len(), 14);
assert!(!interface.is_empty());
let interface = Interface::from_str(E2E_DEVICE_AGGREGATE).unwrap();
assert!(interface.as_inner().as_properties().is_none());
assert!(interface.as_inner().as_datastream_object().is_some());
assert!(interface.as_inner().as_datastream_individual().is_none());
assert!(!interface.as_inner().is_properties());
assert!(interface.as_inner().is_datastream_object());
assert!(!interface.as_inner().is_datastream_individual());
let inner = interface.into_inner();
let interface = inner.as_datastream_object().unwrap();
assert_eq!(interface.len(), 14);
assert!(!interface.is_empty());
let interface = Interface::from_str(E2E_DEVICE_DATASTREAM).unwrap();
assert!(interface.as_inner().as_properties().is_none());
assert!(interface.as_inner().as_datastream_object().is_none());
assert!(interface.as_inner().as_datastream_individual().is_some());
assert!(!interface.as_inner().is_properties());
assert!(!interface.as_inner().is_datastream_object());
assert!(interface.as_inner().is_datastream_individual());
let inner = interface.into_inner();
let interface = inner.as_datastream_individual().unwrap();
assert_eq!(interface.len(), 14);
assert!(!interface.is_empty());
}
}