Skip to content

Commit

Permalink
fixup! feat: REST API to update entity twin data
Browse files Browse the repository at this point in the history
  • Loading branch information
albinsuresh committed Mar 3, 2025
1 parent e885219 commit 6e7c4c6
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 13 deletions.
19 changes: 11 additions & 8 deletions crates/core/tedge_agent/src/entity_manager/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl EntityTwinData {
) -> Result<Self, InvalidTwinData> {
for key in twin_data.keys() {
if key.starts_with('@') {
return Err(InvalidTwinData);
return Err(InvalidTwinData(key.clone()));
}
}
Ok(Self {
Expand All @@ -67,8 +67,8 @@ impl EntityTwinData {
}

#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
#[error("Fragment keys starting with '@' are not allowed as twin data")]
pub struct InvalidTwinData;
#[error("Invalid key: '{0}', as fragment keys starting with '@' are not allowed as twin data")]
pub struct InvalidTwinData(String);

pub struct EntityStoreServer {
entity_store: EntityStore,
Expand Down Expand Up @@ -258,11 +258,14 @@ impl EntityStoreServer {

async fn patch_entity(&mut self, twin_data: EntityTwinData) -> Result<(), entity_store::Error> {
for (fragment_key, fragment_value) in twin_data.fragments.into_iter() {
self.entity_store.update_twin_data(EntityTwinMessage::new(
twin_data.topic_id.clone(),
fragment_key,
fragment_value,
))?;
let twin_message =
EntityTwinMessage::new(twin_data.topic_id.clone(), fragment_key, fragment_value);
let updated = self.entity_store.update_twin_data(twin_message.clone())?;

if updated {
let message = twin_message.to_mqtt_message(&self.mqtt_schema);
self.publish_message(message).await;
}
}

Ok(())
Expand Down
35 changes: 35 additions & 0 deletions crates/core/tedge_agent/src/entity_manager/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::entity_manager::server::EntityStoreResponse;
use crate::entity_manager::server::EntityTwinData;
use crate::entity_manager::tests::model::Action;
use crate::entity_manager::tests::model::Action::AddDevice;
use crate::entity_manager::tests::model::Action::AddService;
Expand All @@ -7,9 +8,12 @@ use crate::entity_manager::tests::model::Commands;
use crate::entity_manager::tests::model::Protocol::HTTP;
use crate::entity_manager::tests::model::Protocol::MQTT;
use proptest::proptest;
use serde_json::json;
use std::collections::HashSet;
use tedge_actors::Server;
use tedge_api::entity::EntityMetadata;
use tedge_api::mqtt_topics::EntityTopicId;
use tedge_mqtt_ext::test_helpers::assert_received_contains_str;

#[tokio::test]
async fn new_entity_store() {
Expand Down Expand Up @@ -57,6 +61,23 @@ async fn removing_a_child_using_mqtt() {
check_registrations(Commands(registrations)).await
}

#[tokio::test]
async fn patched_twin_fragments_published_to_mqtt() {
let (mut entity_store, mut mqtt_box) = entity::server("device-under-test");
let twin_data = EntityTwinData::try_new(
EntityTopicId::default_main_device(),
json!({"x": 9, "y": true, "z": "foo"})
.as_object()
.unwrap()
.clone(),
)
.unwrap();
entity::patch(&mut entity_store, twin_data).await.unwrap();
assert_received_contains_str(&mut mqtt_box, [("te/device/main///twin/x", "9")]).await;
assert_received_contains_str(&mut mqtt_box, [("te/device/main///twin/y", "true")]).await;
assert_received_contains_str(&mut mqtt_box, [("te/device/main///twin/z", "foo")]).await;
}

proptest! {
//#![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
#[test]
Expand Down Expand Up @@ -165,6 +186,7 @@ mod entity {
use crate::entity_manager::server::EntityStoreRequest;
use crate::entity_manager::server::EntityStoreResponse;
use crate::entity_manager::server::EntityStoreServer;
use crate::entity_manager::server::EntityTwinData;
use std::str::FromStr;
use tedge_actors::Builder;
use tedge_actors::NoMessage;
Expand Down Expand Up @@ -192,6 +214,19 @@ mod entity {
None
}

pub async fn patch(
entity_store: &mut EntityStoreServer,
twin_data: EntityTwinData,
) -> Result<(), anyhow::Error> {
if let EntityStoreResponse::Patch(result) = entity_store
.handle(EntityStoreRequest::Patch(twin_data))
.await
{
return result.map_err(Into::into);
};
anyhow::bail!("Unexpected response");
}

pub fn server(
device_id: &str,
) -> (EntityStoreServer, SimpleMessageBox<MqttMessage, NoMessage>) {
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tedge_agent/src/http_server/entity_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ mod tests {
let entity: Value = serde_json::from_slice(&body).unwrap();
assert_json_eq!(
entity,
json!({"error":"Fragment keys starting with '@' are not allowed as twin data"})
json!({"error":"Invalid key: '@id', as fragment keys starting with '@' are not allowed as twin data"})
);
}

Expand Down
11 changes: 7 additions & 4 deletions docs/src/operate/registration/register.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,26 +445,29 @@ PATCH /v1/entities/{topic-id}

**Payload**

Update existing fragment: `name`, add new fragment: `hardware` and remove existing fragment: `maintenanceMode` with a `null` value:
Any fragments to be inserted/updated are specified with their desired values.
Fragments to be removed are specified with a `null` value.

```json
{
"name": "Child 01",
"new-fragment": {
"new-key": "new-value"
},
"extra-fragment": null
"fragment-to-update": "updated-value",
"fragment-to-delete": null
}
```

**Example**

Update existing fragment: `name`, add new fragment: `hardware` and remove existing fragment: `maintenanceMode` with a `null` value:

```shell
curl http://localhost:8000/tedge/entity-store/v1/entities/device/child01 \
-X PATCH \
-H "Content-Type: application/json" \
-d '{
"type": "Raspberry Pi 4",
"Child": "Child 01",
"hardware": {
"serialNo": "98761234"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ CRUD apis
Should Be Equal
... ${patch}
... {"@topic-id":"device/child01//","@parent":"device/main//","@type":"child-device","maintenance_mode":true,"maintenance_window":5}
Should Have MQTT Messages
... te/device/child01///twin/maintenance_mode
... message_contains=true
Should Have MQTT Messages
... te/device/child01///twin/maintenance_window
... message_contains=5

${get}= Execute Command curl http://localhost:8000/tedge/entity-store/v1/entities/device/child01//
Should Be Equal
Expand Down

0 comments on commit 6e7c4c6

Please sign in to comment.