Skip to content

Commit

Permalink
Merge pull request #3139 from didier-wenzek/refactor/default-entity-s…
Browse files Browse the repository at this point in the history
…cheme

Refactor: Simplify entity auto-registration
  • Loading branch information
didier-wenzek authored Sep 27, 2024
2 parents af73e0c + 7b71d09 commit 084fa38
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 79 deletions.
79 changes: 27 additions & 52 deletions crates/core/tedge_api/src/entity_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// TODO: move entity business logic to its own module

use crate::entity_store;
use crate::mqtt_topics::default_topic_schema;
use crate::mqtt_topics::Channel;
use crate::mqtt_topics::EntityTopicId;
use crate::mqtt_topics::MqttSchema;
Expand All @@ -21,7 +22,6 @@ use log::error;
use log::info;
use log::warn;
use mqtt_channel::MqttMessage;
use serde_json::json;
use serde_json::Map;
use serde_json::Value as JsonValue;
use std::collections::hash_map::Entry;
Expand Down Expand Up @@ -602,59 +602,34 @@ impl EntityStore {
&mut self,
entity_topic_id: &EntityTopicId,
) -> Result<Vec<EntityRegistrationMessage>, entity_store::Error> {
if entity_topic_id.matches_default_topic_scheme() {
if entity_topic_id.is_default_main_device() {
return Ok(vec![]); // Do nothing as the main device is always pre-registered
}

let mut register_messages = vec![];

let parent_device_id = entity_topic_id
.default_source_device_identifier()
.expect("device id must be present as the topic id follows the default scheme");

if !parent_device_id.is_default_main_device() && self.get(&parent_device_id).is_none() {
let device_local_id = entity_topic_id.default_device_name().unwrap();
let device_external_id =
(self.external_id_mapper)(&parent_device_id, &self.main_device_external_id());

let device_register_message = EntityRegistrationMessage {
topic_id: parent_device_id.clone(),
external_id: Some(device_external_id),
r#type: EntityType::ChildDevice,
parent: None,
other: json!({ "name": device_local_id })
.as_object()
.unwrap()
.to_owned(),
};
register_messages.push(device_register_message.clone());
self.update(device_register_message)?;
}
let auto_entities = default_topic_schema::parse(entity_topic_id);
if auto_entities.is_empty() {
return Err(Error::NonDefaultTopicScheme(entity_topic_id.clone()));
};

// if the entity is a service, register the service as well
if let Some(service_id) = entity_topic_id.default_service_name() {
let service_external_id =
(self.external_id_mapper)(entity_topic_id, &self.main_device_external_id());
let mut register_messages = vec![];
for mut auto_entity in auto_entities {
// Skip any already registered entity
if auto_entity.r#type != EntityType::MainDevice
&& self.get(&auto_entity.topic_id).is_none()
{
if auto_entity.r#type == EntityType::Service {
auto_entity
.other
.insert("type".to_string(), self.default_service_type.clone().into());
}

let service_register_message = EntityRegistrationMessage {
topic_id: entity_topic_id.clone(),
external_id: Some(service_external_id),
r#type: EntityType::Service,
parent: Some(parent_device_id),
other: json!({ "name": service_id, "type": self.default_service_type })
.as_object()
.unwrap()
.to_owned(),
};
register_messages.push(service_register_message.clone());
self.update(service_register_message)?;
let external_id = (self.external_id_mapper)(
&auto_entity.topic_id,
&self.main_device_external_id(),
);
auto_entity.external_id = Some(external_id);
register_messages.push(auto_entity.clone());
self.update(auto_entity)?;
}

Ok(register_messages)
} else {
Err(Error::NonDefaultTopicScheme(entity_topic_id.clone()))
}

Ok(register_messages)
}

/// Updates the entity twin data with the provided fragment data.
Expand Down Expand Up @@ -1401,7 +1376,7 @@ mod tests {
topic_id: EntityTopicId::from_str("device/child1//").unwrap(),
r#type: EntityType::ChildDevice,
external_id: Some("device:child1".into()),
parent: None,
parent: Some(EntityTopicId::from_str("device/main//").unwrap()),
other: json!({ "name": "child1" }).as_object().unwrap().to_owned(),
},
EntityRegistrationMessage {
Expand Down Expand Up @@ -1432,7 +1407,7 @@ mod tests {
topic_id: EntityTopicId::from_str("device/child2//").unwrap(),
r#type: EntityType::ChildDevice,
external_id: Some("device:child2".into()),
parent: None,
parent: Some(EntityTopicId::from_str("device/main//").unwrap()),
other: json!({ "name": "child2" }).as_object().unwrap().to_owned(),
},]
);
Expand Down
81 changes: 54 additions & 27 deletions crates/core/tedge_api/src/mqtt_topics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,7 @@ impl EntityTopicId {
///
/// Returns false otherwise
pub fn matches_default_topic_scheme(&self) -> bool {
self.default_device_name()
.or(self.default_service_name())
.is_some()
}

/// Returns `true` if it's the topic identifier of the child device in default topic scheme.
pub fn is_default_child_device(&self) -> bool {
matches!(self.segments(), ["device", device_name, "", ""] if device_name != "main" && !device_name.is_empty())
!default_topic_schema::parse(self).is_empty()
}

/// Returns the device name when the entity topic identifier is using the `device/+/service/+` pattern.
Expand All @@ -395,20 +388,6 @@ impl EntityTopicId {
}
}

/// Returns the topic identifier of the source device of an entity,
/// - for a service, this is the parent entity
/// - for a device, this is the device itself
///
/// Returns None if the pattern doesn't apply.
pub fn default_source_device_identifier(&self) -> Option<Self> {
match self.0.split('/').collect::<Vec<&str>>()[..] {
["device", parent_id, "", ""] => Some(parent_id),
["device", parent_id, "service", _] => Some(parent_id),
_ => None,
}
.map(|parent_id| EntityTopicId(format!("device/{parent_id}//")))
}

/// Returns the topic identifier of the parent of a service,
/// assuming `self` is the topic identifier of a service `device/+/service/+`
///
Expand All @@ -426,11 +405,6 @@ impl EntityTopicId {
self == &Self::default_main_device()
}

/// Returns true if the current topic identifier matches that of the service
pub fn is_default_service(&self) -> bool {
self.default_service_name().is_some()
}

/// If `self` is a device topic id, return a service topic id under this
/// device.
///
Expand All @@ -456,6 +430,56 @@ impl EntityTopicId {
}
}

pub mod default_topic_schema {
use crate::entity_store::EntityRegistrationMessage;
use crate::entity_store::EntityType;
use crate::mqtt_topics::EntityTopicId;
use serde_json::json;

/// Parse an entity topic id into registration messages matching auto-registration logic.
///
/// These registration messages are derived from the topic after the default topic schema
/// - `device/main//` -> registration of the main device
/// - `device/{child}//` -> registration of the child device
/// - `device/{device}/service/{service}` -> registrations of the child device and of the service
///
/// Return no registration messages if the entity topic id is not built after the default topic schema
pub fn parse(topic_id: &EntityTopicId) -> Vec<EntityRegistrationMessage> {
match topic_id.segments() {
["device", "main", "", ""] => vec![EntityRegistrationMessage {
topic_id: topic_id.clone(),
external_id: None,
r#type: EntityType::MainDevice,
parent: None,
other: Default::default(),
}],
["device", child, "", ""] if !child.is_empty() => vec![EntityRegistrationMessage {
topic_id: topic_id.clone(),
external_id: None,
r#type: EntityType::ChildDevice,
parent: Some(EntityTopicId::default_main_device()),
other: json!({ "name": child }).as_object().unwrap().to_owned(),
}],
["device", device, "service", service] if !device.is_empty() && !service.is_empty() => {
// The device of a service has to be registered fist
let device_topic_id = EntityTopicId::default_child_device(device).unwrap();
let mut registrations = parse(&device_topic_id);

// Then the service can be registered
registrations.push(EntityRegistrationMessage {
topic_id: topic_id.clone(),
external_id: None,
r#type: EntityType::Service,
parent: Some(device_topic_id),
other: json!({ "name": service }).as_object().unwrap().to_owned(),
});
registrations
}
_ => vec![],
}
}
}

/// Contains a topic id of the service itself and the associated device.
pub struct Service {
pub service_topic_id: ServiceTopicId,
Expand Down Expand Up @@ -916,9 +940,12 @@ mod tests {

#[test_case("device/main//", true)]
#[test_case("device/child//", true)]
#[test_case("device///", false)]
#[test_case("device/main/service/foo", true)]
#[test_case("device/child/service/foo", true)]
#[test_case("device/main//foo", false)]
#[test_case("device/child/service/", false)]
#[test_case("device//service/foo", false)]
#[test_case("custom///", false)]
#[test_case("custom/main//", false)]
#[test_case("custom/child//", false)]
Expand Down

0 comments on commit 084fa38

Please sign in to comment.