summaryrefslogtreecommitdiffstats
path: root/crates/core/c8y_translator/src/json.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/core/c8y_translator/src/json.rs')
-rw-r--r--crates/core/c8y_translator/src/json.rs280
1 files changed, 280 insertions, 0 deletions
diff --git a/crates/core/c8y_translator/src/json.rs b/crates/core/c8y_translator/src/json.rs
new file mode 100644
index 00000000..5794513f
--- /dev/null
+++ b/crates/core/c8y_translator/src/json.rs
@@ -0,0 +1,280 @@
+//! A library to translate the ThinEdgeJson into C8yJson
+//! Takes thin_edge_json bytes and returns c8y json bytes
+//!
+//! # Examples
+//!
+//! ```
+//! use c8y_translator::json::from_thin_edge_json;
+//! let single_value_thin_edge_json = r#"{
+//! "time": "2020-06-22T17:03:14.000+02:00",
+//! "temperature": 23,
+//! "pressure": 220
+//! }"#;
+//! let output = from_thin_edge_json(single_value_thin_edge_json);
+//! ```
+
+use crate::serializer;
+use chrono::prelude::*;
+use clock::{Clock, WallClock};
+use thin_edge_json::parser::*;
+
+#[derive(thiserror::Error, Debug)]
+pub enum CumulocityJsonError {
+ #[error(transparent)]
+ C8yJsonSerializationError(#[from] serializer::C8yJsonSerializationError),
+
+ #[error(transparent)]
+ ThinEdgeJsonParserError(#[from] ThinEdgeJsonParserError),
+}
+
+/// Converts from thin-edge Json to c8y_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)?;
+ Ok(c8y_vec)
+}
+
+/// Converts from thin-edge Json to c8y_json with child id information
+pub fn from_thin_edge_json_with_child(
+ input: &str,
+ child_id: &str,
+) -> Result<String, CumulocityJsonError> {
+ let timestamp = WallClock.now();
+ let c8y_vec = from_thin_edge_json_with_timestamp(input, timestamp, Some(child_id))?;
+ Ok(c8y_vec)
+}
+
+fn from_thin_edge_json_with_timestamp(
+ input: &str,
+ timestamp: DateTime<FixedOffset>,
+ maybe_child_id: Option<&str>,
+) -> Result<String, CumulocityJsonError> {
+ let mut serializer = serializer::C8yJsonSerializer::new(timestamp, maybe_child_id);
+ let () = parse_str(input, &mut serializer)?;
+ Ok(serializer.into_string()?)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use assert_json_diff::*;
+ use serde_json::{json, Value};
+ use test_case::test_case;
+
+ #[test]
+ fn check_single_value_translation() {
+ let single_value_thin_edge_json = r#"{
+ "temperature": 23.0,
+ "pressure": 220.0
+ }"#;
+
+ let timestamp = FixedOffset::east(5 * 3600).ymd(2021, 4, 8).and_hms(0, 0, 0);
+
+ let output =
+ from_thin_edge_json_with_timestamp(single_value_thin_edge_json, timestamp, None);
+
+ let expected_output = json!({
+ "type": "ThinEdgeMeasurement",
+ "time": timestamp.to_rfc3339(),
+ "temperature": {
+ "temperature": {
+ "value": 23.0
+ }
+ },
+ "pressure": {
+ "pressure": {
+ "value": 220.0
+ }
+ }
+ });
+
+ assert_json_eq!(
+ serde_json::from_str::<serde_json::Value>(output.unwrap().as_str()).unwrap(),
+ expected_output
+ );
+ }
+
+ #[test]
+ fn check_thin_edge_translation_with_timestamp() {
+ let single_value_thin_edge_json = r#"{
+ "time" : "2013-06-22T17:03:14.123+02:00",
+ "temperature": 23.0,
+ "pressure": 220.0
+ }"#;
+
+ let expected_output = r#"{
+ "type": "ThinEdgeMeasurement",
+ "time": "2013-06-22T17:03:14.123+02:00",
+ "temperature": {
+ "temperature": {
+ "value": 23.0
+ }
+ },
+ "pressure" : {
+ "pressure": {
+ "value" : 220.0
+ }
+ }
+ }"#;
+
+ let output = from_thin_edge_json(single_value_thin_edge_json);
+
+ assert_eq!(
+ expected_output.split_whitespace().collect::<String>(),
+ output.unwrap().split_whitespace().collect::<String>()
+ );
+ }
+
+ #[test]
+ fn check_multi_value_translation() {
+ let multi_value_thin_edge_json = r#"{
+ "temperature": 25.0 ,
+ "location": {
+ "latitude": 32.54,
+ "longitude": -117.67,
+ "altitude": 98.6
+ },
+ "pressure": 98.0
+ }"#;
+
+ let timestamp = FixedOffset::east(5 * 3600).ymd(2021, 4, 8).and_hms(0, 0, 0);
+
+ let output =
+ from_thin_edge_json_with_timestamp(multi_value_thin_edge_json, timestamp, None);
+
+ let expected_output = json!({
+ "type": "ThinEdgeMeasurement",
+ "time": timestamp.to_rfc3339(),
+ "temperature": {
+ "temperature": {
+ "value": 25.0
+ }
+ },
+ "location": {
+ "latitude": {
+ "value": 32.54
+ },
+ "longitude": {
+ "value": -117.67
+ },
+ "altitude": {
+ "value": 98.6
+ }
+ },
+ "pressure": {
+ "pressure": {
+ "value": 98.0
+ }
+ }
+ });
+
+ assert_json_eq!(
+ serde_json::from_str::<serde_json::Value>(output.unwrap().as_str()).unwrap(),
+ expected_output
+ );
+ }
+
+ #[test]
+ fn thin_edge_json_round_tiny_number() {
+ let input = r#"{
+ "time" : "2013-06-22T17:03:14.000+02:00",
+ "temperature": 10e-9999999999
+ }"#;
+
+ let expected_output = r#"{
+ "type": "ThinEdgeMeasurement",
+ "time": "2013-06-22T17:03:14+02:00",
+ "temperature": {
+ "temperature": {
+ "value": 0.0
+ }
+ }
+ }"#;
+
+ let output = from_thin_edge_json(input);
+
+ let actual_output = output.unwrap().split_whitespace().collect::<String>();
+
+ assert_eq!(
+ expected_output.split_whitespace().collect::<String>(),
+ actual_output
+ );
+ }
+ use proptest::prelude::*;
+
+ proptest! {
+
+ #[test]
+ fn it_works_for_any_measurement(measurement in r#"[a-z]{3,6}"#) {
+ if measurement == "time" || measurement == "type" {
+ // Skip this test case, since the random measurement name happens to be a reserved key.
+ return Ok(());
+ }
+ let input = format!(r#"{{"time": "2013-06-22T17:03:14.453+02:00",
+ "{}": 123.0
+ }}"#, measurement);
+ let time = "2013-06-22T17:03:14.453+02:00";
+ let expected_output = format!(r#"{{
+ "type": "ThinEdgeMeasurement",
+ "time": "{}",
+ "{}": {{
+ "{}": {{
+ "value": 123.0
+ }}
+ }}
+ }}"#, time, measurement, measurement);
+
+ let output = from_thin_edge_json(input.as_str()).unwrap();
+ assert_eq!(
+ expected_output.split_whitespace().collect::<String>(),
+ output
+ .split_whitespace()
+ .collect::<String>()
+ );
+ }
+ }
+
+ #[test_case(
+ "child1",
+ r#"{"temperature": 23.0}"#,
+ json!({
+ "type": "ThinEdgeMeasurement",
+ "externalSource": {"externalId": "child1","type": "c8y_Serial",},
+ "time": "2021-04-08T00:00:00+05:00",
+ "temperature": {"temperature": {"value": 23.0}}
+ })
+ ;"child device single value thin-edge json translation")]
+ #[test_case(
+ "child2",
+ r#"{"temperature": 23.0, "pressure": 220.0}"#,
+ json!({
+ "type": "ThinEdgeMeasurement",
+ "externalSource": {"externalId": "child2","type": "c8y_Serial",},
+ "time": "2021-04-08T00:00:00+05:00",
+ "temperature": {"temperature": {"value": 23.0}},
+ "pressure": {"pressure": {"value": 220.0}}
+ })
+ ;"child device multiple values thin-edge json translation")]
+ #[test_case(
+ "child3",
+ r#"{"temperature": 23.0, "time": "2021-04-23T19:00:00+05:00"}"#,
+ json!({
+ "type": "ThinEdgeMeasurement",
+ "externalSource": {"externalId": "child3","type": "c8y_Serial",},
+ "time": "2021-04-23T19:00:00+05:00",
+ "temperature": {"temperature": {"value": 23.0}},
+ })
+ ;"child device single value with timestamp thin-edge json translation")]
+ fn check_value_translation_for_child_device(
+ child_id: &str,
+ thin_edge_json: &str,
+ expected_output: Value,
+ ) {
+ let timestamp = FixedOffset::east(5 * 3600).ymd(2021, 4, 8).and_hms(0, 0, 0);
+ let output = from_thin_edge_json_with_timestamp(thin_edge_json, timestamp, Some(child_id));
+ assert_json_eq!(
+ serde_json::from_str::<serde_json::Value>(output.unwrap().as_str()).unwrap(),
+ expected_output
+ );
+ }
+}