summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlbin Suresh <albin.suresh@softwareag.com>2021-12-22 15:02:24 +0530
committerGitHub <noreply@github.com>2021-12-22 15:02:24 +0530
commit0d61811849ce9931b91b65a7e94865d99f4bb394 (patch)
tree5b6e2b4d3d44be2f1256842fb4b9d16c9e7ca2df
parent888a4e70ca44d9b41d645148b7a24d89a984da7c (diff)
Closes #667 Thin Edge JSON alarm support (#726)
* Closes #667 Thin Edge JSON alarm support
-rw-r--r--Cargo.lock15
-rw-r--r--crates/common/clock/Cargo.toml2
-rw-r--r--crates/common/clock/src/lib.rs14
-rw-r--r--crates/core/c8y_smartrest/Cargo.toml2
-rw-r--r--crates/core/c8y_smartrest/src/alarm.rs137
-rw-r--r--crates/core/c8y_smartrest/src/error.rs3
-rw-r--r--crates/core/c8y_smartrest/src/lib.rs1
-rw-r--r--crates/core/c8y_translator/src/json.rs2
-rw-r--r--crates/core/tedge_mapper/src/c8y_converter.rs52
-rw-r--r--crates/core/tedge_mapper/src/error.rs6
-rw-r--r--crates/core/thin_edge_json/Cargo.toml6
-rw-r--r--crates/core/thin_edge_json/src/alarm.rs217
-rw-r--r--crates/core/thin_edge_json/src/lib.rs1
13 files changed, 444 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 34791af1..c40eb405 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -328,7 +328,9 @@ dependencies = [
"serde",
"serde_json",
"test-case",
+ "thin_edge_json",
"thiserror",
+ "time 0.3.5",
]
[[package]]
@@ -434,6 +436,8 @@ version = "0.5.0"
dependencies = [
"chrono",
"mockall",
+ "serde",
+ "time 0.3.5",
]
[[package]]
@@ -2799,6 +2803,7 @@ name = "thin_edge_json"
version = "0.5.0"
dependencies = [
"anyhow",
+ "assert_matches",
"chrono",
"clock",
"criterion",
@@ -2808,7 +2813,9 @@ dependencies = [
"serde",
"serde_json",
"stats_alloc",
+ "test-case",
"thiserror",
+ "time 0.3.5",
"walkdir",
]
@@ -2859,9 +2866,17 @@ checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
dependencies = [
"itoa",
"libc",
+ "serde",
+ "time-macros",
]
[[package]]
+name = "time-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
+
+[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/common/clock/Cargo.toml b/crates/common/clock/Cargo.toml
index e3b433d6..93d3f1be 100644
--- a/crates/common/clock/Cargo.toml
+++ b/crates/common/clock/Cargo.toml
@@ -5,5 +5,7 @@ authors = ["thin-edge.io team <info@thin-edge.io>"]
edition = "2018"
[dependencies]
+time = { version = "0.3", features = ["serde-human-readable"] }
chrono = "0.4"
+serde = { version = "1.0", features = ["derive"] }
mockall = "0.10"
diff --git a/crates/common/clock/src/lib.rs b/crates/common/clock/src/lib.rs
index c110e240..8ae44bcd 100644
--- a/crates/common/clock/src/lib.rs
+++ b/crates/common/clock/src/lib.rs
@@ -1,5 +1,7 @@
use chrono::{DateTime, FixedOffset, Local};
use mockall::automock;
+use serde::{Deserialize, Deserializer};
+use time::{format_description::well_known::Rfc3339, OffsetDateTime};
pub type Timestamp = DateTime<FixedOffset>;
@@ -17,3 +19,15 @@ impl Clock for WallClock {
local_time_now.with_timezone(local_time_now.offset())
}
}
+
+pub fn deserialize_iso8601_timestamp<'de, D>(
+ deserializer: D,
+) -> Result<Option<OffsetDateTime>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let timestamp = String::deserialize(deserializer)?;
+ OffsetDateTime::parse(timestamp.as_str(), &Rfc3339)
+ .map_err(serde::de::Error::custom)
+ .map(|val| Some(val))
+}
diff --git a/crates/core/c8y_smartrest/Cargo.toml b/crates/core/c8y_smartrest/Cargo.toml
index 8b91157b..f98f0d34 100644
--- a/crates/core/c8y_smartrest/Cargo.toml
+++ b/crates/core/c8y_smartrest/Cargo.toml
@@ -5,6 +5,8 @@ authors = ["thin-edge.io team <info@thin-edge.io>"]
edition = "2018"
[dependencies]
+thin_edge_json = { path = "../thin_edge_json" }
+time = { version = "0.3", features = ["macros"] }
chrono = { version = "0.4", features = ["serde"] }
csv = "1.1"
json_sm = { path = "../json_sm" }
diff --git a/crates/core/c8y_smartrest/src/alarm.rs b/crates/core/c8y_smartrest/src/alarm.rs
new file mode 100644
index 00000000..630debe9
--- /dev/null
+++ b/crates/core/c8y_smartrest/src/alarm.rs
@@ -0,0 +1,137 @@
+use thin_edge_json::alarm::{AlarmSeverity, ThinEdgeAlarm};
+use time::{format_description::well_known::Rfc3339, OffsetDateTime};
+
+use crate::error::SmartRestSerializerError;
+
+/// Converts from thin-edge alarm to C8Y alarm SmartREST message
+pub fn serialize_alarm(alarm: ThinEdgeAlarm) -> Result<String, SmartRestSerializerError> {
+ match alarm.data {
+ None => Ok(format!("306,{}", alarm.name)),
+ Some(alarm_data) => {
+ let smartrest_code = match alarm.severity {
+ AlarmSeverity::Critical => 301,
+ AlarmSeverity::Major => 302,
+ AlarmSeverity::Minor => 303,
+ AlarmSeverity::Warning => 304,
+ };
+
+ let current_timestamp = OffsetDateTime::now_utc();
+
+ let smartrest_message = format!(
+ "{},{},\"{}\",{}",
+ smartrest_code,
+ alarm.name,
+ alarm_data.message.unwrap_or_default(),
+ alarm_data.time.map_or_else(
+ || current_timestamp.format(&Rfc3339),
+ |timestamp| timestamp.format(&Rfc3339)
+ )?
+ );
+
+ Ok(smartrest_message)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use assert_matches::assert_matches;
+ use serde::Deserialize;
+ use test_case::test_case;
+ use thin_edge_json::alarm::ThinEdgeAlarmData;
+ use time::macros::datetime;
+
+ #[test_case(
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Critical,
+ data: Some(ThinEdgeAlarmData {
+ message: Some("I raised it".into()),
+ time: Some(datetime!(2021-04-23 19:00:00 +05:00)),
+ }),
+ },
+ "301,temperature_alarm,\"I raised it\",2021-04-23T19:00:00+05:00"
+ ;"critical alarm translation"
+ )]
+ #[test_case(
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Major,
+ data: Some(ThinEdgeAlarmData {
+ message: Some("I raised it".into()),
+ time: Some(datetime!(2021-04-23 19:00:00 +05:00)),
+ }),
+ },
+ "302,temperature_alarm,\"I raised it\",2021-04-23T19:00:00+05:00"
+ ;"major alarm translation"
+ )]
+ #[test_case(
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Minor,
+ data: Some(ThinEdgeAlarmData {
+ message: None,
+ time: Some(datetime!(2021-04-23 19:00:00 +05:00)),
+ }),
+ },
+ "303,temperature_alarm,\"\",2021-04-23T19:00:00+05:00"
+ ;"minor alarm translation without message"
+ )]
+ #[test_case(
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Warning,
+ data: Some(ThinEdgeAlarmData {
+ message: Some("I, raised, it".into()),
+ time: Some(datetime!(2021-04-23 19:00:00 +05:00)),
+ }),
+ },
+ "304,temperature_alarm,\"I, raised, it\",2021-04-23T19:00:00+05:00"
+ ;"warning alarm translation with commas in message"
+ )]
+ #[test_case(
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Minor,
+ data: None,
+ },
+ "306,temperature_alarm"
+ ;"clear alarm translation"
+ )]
+ fn check_alarm_translation(alarm: ThinEdgeAlarm, expected_smartrest_msg: &str) {
+ let result = serialize_alarm(alarm);
+
+ assert_eq!(result.unwrap(), expected_smartrest_msg);
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct SmartRestAlarm {
+ pub code: i32,
+ pub name: String,
+ pub message: Option<String>,
+ pub time: Option<OffsetDateTime>,
+ }
+
+ #[test]
+ fn alarm_translation_empty_json_payload_generates_timestamp() {
+ let alarm = ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Warning,
+ data: Some(ThinEdgeAlarmData {
+ message: Some("I raised it".into()),
+ time: None,
+ }),
+ };
+
+ let smartrest_message = serialize_alarm(alarm).unwrap();
+ let mut reader = csv::Reader::from_reader(smartrest_message.as_bytes());
+ for result in reader.deserialize() {
+ let smartrest_alarm: SmartRestAlarm = result.unwrap();
+ assert_eq!(smartrest_alarm.code, 301);
+ assert_eq!(smartrest_alarm.name, "empty_alarm".to_string());
+ assert_eq!(smartrest_alarm.message, None);
+ assert_matches!(smartrest_alarm.time, Some(_))
+ }
+ }
+}
diff --git a/crates/core/c8y_smartrest/src/error.rs b/crates/core/c8y_smartrest/src/error.rs
index 4539ac02..d7463c1a 100644
--- a/crates/core/c8y_smartrest/src/error.rs
+++ b/crates/core/c8y_smartrest/src/error.rs
@@ -13,6 +13,9 @@ pub enum SmartRestSerializerError {
#[error(transparent)]
FromUtf8Error(#[from] std::string::FromUtf8Error),
+
+ #[error(transparent)]
+ FromTimeFormatError(#[from] time::error::Format),
}
#[derive(thiserror::Error, Debug)]
diff --git a/crates/core/c8y_smartrest/src/lib.rs b/crates/core/c8y_smartrest/src/lib.rs
index 596af9be..4479f304 100644
--- a/crates/core/c8y_smartrest/src/lib.rs
+++ b/crates/core/c8y_smartrest/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod alarm;
pub mod error;
pub mod smartrest_deserializer;
pub mod smartrest_serializer;
diff --git a/crates/core/c8y_translator/src/json.rs b/crates/core/c8y_translator/src/json.rs
index 5794513f..8c9a2b5b 100644
--- a/crates/core/c8y_translator/src/json.rs
+++ b/crates/core/c8y_translator/src/json.rs
@@ -27,7 +27,7 @@ pub enum CumulocityJsonError {
ThinEdgeJsonParserError(#[from] ThinEdgeJsonParserError),
}
-/// Converts from thin-edge Json to c8y_json
+/// Converts from thin-edge measurement JSON to C8Y measurement JSON
pub fn from_thin_edge_json(input: &str) -> Result<String, CumulocityJsonError> {
let timestamp = WallClock.now();
let c8y_vec = from_thin_edge_json_with_timestamp(input, timestamp, None)?;
diff --git a/crates/core/tedge_mapper/src/c8y_converter.rs b/crates/core/tedge_mapper/src/c8y_converter.rs
index 23d6020f..6fb9997a 100644
--- a/crates/core/tedge_mapper/src/c8y_converter.rs
+++ b/crates/core/tedge_mapper/src/c8y_converter.rs
@@ -1,10 +1,12 @@
use crate::error::*;
use crate::size_threshold::SizeThreshold;
use crate::{converter::*, operations::Operations};
+use c8y_smartrest::alarm;
use c8y_smartrest::smartrest_serializer::{SmartRestSerializer, SmartRestSetSupportedOperations};
use c8y_translator::json;
use mqtt_client::{Message, Topic};
use std::collections::HashSet;
+use thin_edge_json::alarm::ThinEdgeAlarm;
const SMARTREST_PUBLISH_TOPIC: &str = "c8y/s/us";
@@ -19,7 +21,10 @@ impl CumulocityConverter {
let mut topic_fiter = make_valid_topic_filter_or_panic("tedge/measurements");
let () = topic_fiter
.add("tedge/measurements/+")
- .expect("invalid topic filter");
+ .expect("invalid measurement topic filter");
+ let () = topic_fiter
+ .add("tedge/alarms/+/+")
+ .expect("invalid alarm topic filter");
let mapper_config = MapperConfig {
in_topic_filter: topic_fiter,
@@ -34,18 +39,11 @@ impl CumulocityConverter {
mapper_config,
}
}
-}
-
-impl Converter for CumulocityConverter {
- type Error = ConversionError;
-
- fn get_mapper_config(&self) -> &MapperConfig {
- &self.mapper_config
- }
-
- fn try_convert(&mut self, input: &Message) -> Result<Vec<Message>, ConversionError> {
- let () = self.size_threshold.validate(input.payload_str()?)?;
+ fn try_convert_measurement(
+ &mut self,
+ input: &Message,
+ ) -> Result<Vec<Message>, ConversionError> {
let mut vec: Vec<Message> = Vec::new();
let maybe_child_id = get_child_id_from_topic(&input.topic.name)?;
@@ -79,6 +77,36 @@ impl Converter for CumulocityConverter {
Ok(vec)
}
+ fn try_convert_alarm(&self, input: &Message) -> Result<Vec<Message>, ConversionError> {
+ let c8y_alarm_topic = Topic::new_unchecked(SMARTREST_PUBLISH_TOPIC);
+ let mut vec: Vec<Message> = Vec::new();
+
+ let tedge_alarm = ThinEdgeAlarm::try_from(input.topic.name.as_str(), input.payload_str()?)?;
+ let smartrest_alarm = alarm::serialize_alarm(tedge_alarm)?;
+ vec.push(Message::new(&c8y_alarm_topic, smartrest_alarm));
+
+ Ok(vec)
+ }
+}
+
+impl Converter for CumulocityConverter {
+ type Error = ConversionError;
+
+ fn get_mapper_config(&self) -> &MapperConfig {
+ &self.mapper_config
+ }
+
+ fn try_convert(&mut self, input: &Message) -> Result<Vec<Message>, ConversionError> {
+ let () = self.size_threshold.validate(input.payload_str()?)?;
+ if input.topic.name.starts_with("tedge/measurement") {
+ self.try_convert_measurement(input)
+ } else if input.topic.name.starts_with("tedge/alarms") {
+ self.try_convert_alarm(input)
+ } else {
+ return Err(ConversionError::UnsupportedTopic(input.topic.name.clone()));
+ }
+ }
+
fn try_init_messages(&self) -> Result<Vec<Message>, ConversionError> {
let ops = Operations::try_new("/etc/tedge/operations")?;
let ops = ops.get_operations_list("c8y");
diff --git a/crates/core/tedge_mapper/src/error.rs b/crates/core/tedge_mapper/src/error.rs
index 21ec16df..b1aa90b5 100644
--- a/crates/core/tedge_mapper/src/error.rs
+++ b/crates/core/tedge_mapper/src/error.rs
@@ -33,6 +33,9 @@ pub enum ConversionError {
FromThinEdgeJsonSerialization(#[from] ThinEdgeJsonSerializationError),
#[error(transparent)]
+ FromThinEdgeJsonDeserialization(#[from] thin_edge_json::alarm::ThinEdgeJsonDeserializerError),
+
+ #[error(transparent)]
FromThinEdgeJsonParser(#[from] thin_edge_json::parser::ThinEdgeJsonParserError),
#[error(transparent)]
@@ -49,6 +52,9 @@ pub enum ConversionError {
#[error(transparent)]
FromSmartRestSerializerError(#[from] c8y_smartrest::error::SmartRestSerializerError),
+
+ #[error("Unsupported topic: {0}")]
+ UnsupportedTopic(String),
}
#[derive(Debug, thiserror::Error)]
diff --git a/crates/core/thin_edge_json/Cargo.toml b/crates/core/thin_edge_json/Cargo.toml
index 30ef3ff1..c55e5610 100644
--- a/crates/core/thin_edge_json/Cargo.toml
+++ b/crates/core/thin_edge_json/Cargo.toml
@@ -7,9 +7,11 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+time = { version = "0.3", features = ["macros"] }
+clock = { path = "../../common/clock" }
chrono = "0.4"
json-writer = { path = "../../common/json_writer" }
-serde = "1"
+serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
thiserror = "1.0"
@@ -21,6 +23,8 @@ mockall = "0.10"
proptest = "1.0"
stats_alloc = "0.1"
walkdir = "2"
+assert_matches = "1.5"
+test-case = "1.2"
[[bench]]
name = "parsing"
diff --git a/crates/core/thin_edge_json/src/alarm.rs b/crates/core/thin_edge_json/src/alarm.rs
new file mode 100644
index 00000000..1ace73d7
--- /dev/null
+++ b/crates/core/thin_edge_json/src/alarm.rs
@@ -0,0 +1,217 @@
+use std::convert::{TryFrom, TryInto};
+
+use serde::Deserialize;
+use time::OffsetDateTime;
+
+/// In-memory representation of ThinEdge JSON alarm.
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct ThinEdgeAlarm {
+ pub name: String,
+ pub severity: AlarmSeverity,
+ pub data: Option<ThinEdgeAlarmData>,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub enum AlarmSeverity {
+ Critical,
+ Major,
+ Minor,
+ Warning,
+}
+
+/// In-memory representation of ThinEdge JSON alarm payload
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct ThinEdgeAlarmData {
+ pub message: Option<String>,
+ #[serde(default)]
+ #[serde(deserialize_with = "clock::deserialize_iso8601_timestamp")]
+ pub time: Option<OffsetDateTime>,
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum ThinEdgeJsonDeserializerError {
+ #[error("Unsupported topic: {0}")]
+ UnsupportedTopic(String),
+
+ #[error("Unsupported alarm severity in topic: {0}")]
+ UnsupportedAlarmSeverity(String),
+
+ #[error(transparent)]
+ SerdeJsonError(#[from] serde_json::error::Error),
+}
+
+impl TryFrom<&str> for AlarmSeverity {
+ type Error = ThinEdgeJsonDeserializerError;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ match value {
+ "critical" => Ok(AlarmSeverity::Critical),
+ "major" => Ok(AlarmSeverity::Major),
+ "minor" => Ok(AlarmSeverity::Minor),
+ "warning" => Ok(AlarmSeverity::Warning),
+ invalid => Err(ThinEdgeJsonDeserializerError::UnsupportedAlarmSeverity(
+ invalid.into(),
+ ))?,
+ }
+ }
+}
+
+impl ThinEdgeAlarm {
+ pub fn try_from(
+ mqtt_topic: &str,
+ mqtt_payload: &str,
+ ) -> Result<Self, ThinEdgeJsonDeserializerError> {
+ let topic_split: Vec<&str> = mqtt_topic.split('/').collect();
+ if topic_split.len() == 4 {
+ let alarm_name = topic_split[3];
+ if alarm_name.is_empty() {
+ return Err(ThinEdgeJsonDeserializerError::UnsupportedTopic(
+ mqtt_topic.into(),
+ ));
+ }
+
+ let alarm_severity = topic_split[2];
+
+ let alarm_data = if mqtt_payload.is_empty() {
+ None
+ } else {
+ Some(serde_json::from_str(mqtt_payload)?)
+ };
+
+ Ok(Self {
+ name: alarm_name.into(),
+ severity: alarm_severity.try_into()?,
+ data: alarm_data,
+ })
+ } else {
+ return Err(ThinEdgeJsonDeserializerError::UnsupportedTopic(
+ mqtt_topic.into(),
+ ));
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use assert_matches::assert_matches;
+ use serde_json::{json, Value};
+ use test_case::test_case;
+ use time::macros::datetime;
+
+ #[test_case(
+ "tedge/alarms/critical/temperature_alarm",
+ json!({
+ "message": "I raised it",
+ "time": "2021-04-23T19:00:00+05:00",
+ }),
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Critical,
+ data: Some(ThinEdgeAlarmData {
+ message: Some("I raised it".into()),
+ time: Some(datetime!(2021-04-23 19:00:00 +05:00)),
+ }),
+ };
+ "critical alarm parsing"
+ )]
+ #[test_case(
+ "tedge/alarms/major/temperature_alarm",
+ json!({
+ "message": "I raised it",
+ }),
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Major,
+ data: Some(ThinEdgeAlarmData {
+ message: Some("I raised it".into()),
+ time: None,
+ }),
+ };
+ "major alarm parsing without timestamp"
+ )]
+ #[test_case(
+ "tedge/alarms/minor/temperature_alarm",
+ json!({
+ "time": "2021-04-23T19:00:00+05:00",
+ }),
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Minor,
+ data: Some(ThinEdgeAlarmData {
+ message: None,
+ time: Some(datetime!(2021-04-23 19:00:00 +05:00)),
+ }),
+ };
+ "minor alarm parsing without message"
+ )]
+ #[test_case(
+ "tedge/alarms/warning/temperature_alarm",
+ json!({}),
+ ThinEdgeAlarm {
+ name: "temperature_alarm".into(),
+ severity: AlarmSeverity::Warning,
+ data: Some(ThinEdgeAlarmData {
+ message: None,
+ time: None,
+ }),
+ };
+ "warning alarm parsing without message or timestamp"
+ )]
+ fn parse_thin_edge_alarm_json(
+ alarm_topic: &str,
+ alarm_payload: Value,
+ expected_alarm: ThinEdgeAlarm,
+ ) {
+ let alarm =
+ ThinEdgeAlarm::try_from(alarm_topic, alarm_payload.to_string().as_str()).unwrap();
+
+ assert_eq!(alarm, expected_alarm);
+ }
+
+ #[test]
+ fn alarm_translation_empty_alarm_name() {
+ let result = ThinEdgeAlarm::try_from("tedge/alarms/critical/", "{}");
+
+ assert_matches!(
+ result,
+ Err(ThinEdgeJsonDeserializerError::UnsupportedTopic(_))
+ );
+ }
+
+ #[test]
+ fn alarm_translation_empty_severity() {
+ let result = ThinEdgeAlarm::try_from("tedge/alarms//some_alarm", "{}");
+
+ assert_matches!(
+ result,
+ Err(ThinEdgeJsonDeserializerError::UnsupportedAlarmSeverity(_))
+ );
+ }
+
+ #[test]
+ fn alarm_translation_empty_severity_and_name() {
+ let result = ThinEdgeAlarm::try_from("tedge/alarms//", "{}");
+
+ assert_matches!(
+ result,
+ Err(ThinEdgeJsonDeserializerError::UnsupportedTopic(_))
+ );
+ }
+
+ #[test]
+ fn alarm_translation_invalid_severity() {
+ let result = ThinEdgeAlarm::try_from("tedge/alarms/invalid_severity/foo", "{}");
+
+ assert_matches!(
+ result,
+ Err(ThinEdgeJsonDeserializerError::UnsupportedAlarmSeverity(_))
+ );
+ }
+
+ #[test]
+ fn alarm_translation_clear_alarm_with_empty_payload() {
+ let result = ThinEdgeAlarm::try_from("tedge/alarms/critical/temperature_high_alarm", "");
+ assert_matches!(result.unwrap().data, None);
+ }
+}
diff --git a/crates/core/thin_edge_json/src/lib.rs b/crates/core/thin_edge_json/src/lib.rs
index 56664969..441b5441 100644
--- a/crates/core/thin_edge_json/src/lib.rs
+++ b/crates/core/thin_edge_json/src/lib.rs
@@ -1,6 +1,7 @@
//! A library to create [ThinEdgeJson][1] from bytes of json data by validating it.
//! [1]: https://github.com/thin-edge/thin-edge.io/blob/main/docs/src/architecture/thin-edge-json.md
+pub mod alarm;
pub mod builder;
pub mod data;
pub mod group;