From 03124da93bf8788c0b79ce61219e3c8a2c603768 Mon Sep 17 00:00:00 2001 From: Lukasz Woznicki Date: Wed, 9 Feb 2022 23:35:36 +0000 Subject: Use 'time' instead of 'chrono' due to CVE for thin_edge_json and all dependent crates Signed-off-by: Lukasz Woznicki --- .../c8y_smartrest/src/smartrest_deserializer.rs | 3 +- crates/core/c8y_translator/Cargo.toml | 3 +- crates/core/c8y_translator/src/json.rs | 23 ++-- crates/core/c8y_translator/src/serializer.rs | 53 ++++----- crates/core/tedge_mapper/Cargo.toml | 1 - crates/core/tedge_mapper/src/az_converter.rs | 4 +- .../tedge_mapper/src/collectd_mapper/batcher.rs | 7 +- .../tedge_mapper/src/collectd_mapper/collectd.rs | 40 +++---- .../core/tedge_mapper/src/sm_c8y_mapper/mapper.rs | 4 +- crates/core/thin_edge_json/Cargo.toml | 4 +- crates/core/thin_edge_json/benches/parsing.rs | 4 +- crates/core/thin_edge_json/examples/validate.rs | 4 +- crates/core/thin_edge_json/src/builder.rs | 7 +- crates/core/thin_edge_json/src/data.rs | 6 +- crates/core/thin_edge_json/src/group.rs | 21 ++-- crates/core/thin_edge_json/src/measurement.rs | 14 ++- crates/core/thin_edge_json/src/parser.rs | 121 +++++++++++---------- crates/core/thin_edge_json/src/serialize.rs | 52 ++++++--- .../reject_invalid_timestamp.expected_error | 2 +- .../reject_partial_timestamp.expected_error | 2 +- ...timestamp_missing_time_separator.expected_error | 2 +- 21 files changed, 195 insertions(+), 182 deletions(-) (limited to 'crates/core') diff --git a/crates/core/c8y_smartrest/src/smartrest_deserializer.rs b/crates/core/c8y_smartrest/src/smartrest_deserializer.rs index a5265279..0b736767 100644 --- a/crates/core/c8y_smartrest/src/smartrest_deserializer.rs +++ b/crates/core/c8y_smartrest/src/smartrest_deserializer.rs @@ -170,8 +170,7 @@ fn to_datetime<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { - // NOTE `OffsetDateTime` is used here because c8y uses for log requests a date time string which - // does not exactly equal `chrono::DateTime::parse_from_rfc3339` + // NOTE `OffsetDateTime` is used here because c8y uses for log requests a date time string which is not compliant with rfc3339 // c8y result: // 2021-10-23T19:03:26+0100 // rfc3339 expected: diff --git a/crates/core/c8y_translator/Cargo.toml b/crates/core/c8y_translator/Cargo.toml index 52e98e04..d0e77df4 100644 --- a/crates/core/c8y_translator/Cargo.toml +++ b/crates/core/c8y_translator/Cargo.toml @@ -6,11 +6,11 @@ edition = "2021" rust-version = "1.58.1" [dependencies] -chrono = "0.4" clock = { path = "../../common/clock" } json-writer = { path = "../../common/json_writer" } thin_edge_json = { path = "../thin_edge_json" } thiserror = "1.0" +time = "0.3" [dev-dependencies] anyhow = "1.0" @@ -21,6 +21,7 @@ pretty_assertions = "1.0" proptest = "1.0" serde_json = "1.0" test-case = "1.2" +time = { version = "0.3", features = ["macros"] } [features] # use: #[cfg(feature="integration-test")] diff --git a/crates/core/c8y_translator/src/json.rs b/crates/core/c8y_translator/src/json.rs index 8c9a2b5b..0ecacd4f 100644 --- a/crates/core/c8y_translator/src/json.rs +++ b/crates/core/c8y_translator/src/json.rs @@ -14,9 +14,9 @@ //! ``` use crate::serializer; -use chrono::prelude::*; use clock::{Clock, WallClock}; use thin_edge_json::parser::*; +use time::{self, OffsetDateTime}; #[derive(thiserror::Error, Debug)] pub enum CumulocityJsonError { @@ -46,7 +46,7 @@ pub fn from_thin_edge_json_with_child( fn from_thin_edge_json_with_timestamp( input: &str, - timestamp: DateTime, + timestamp: OffsetDateTime, maybe_child_id: Option<&str>, ) -> Result { let mut serializer = serializer::C8yJsonSerializer::new(timestamp, maybe_child_id); @@ -58,8 +58,10 @@ fn from_thin_edge_json_with_timestamp( mod tests { use super::*; use assert_json_diff::*; + use proptest::prelude::*; use serde_json::{json, Value}; use test_case::test_case; + use time::{format_description, macros::datetime}; #[test] fn check_single_value_translation() { @@ -68,14 +70,17 @@ mod tests { "pressure": 220.0 }"#; - let timestamp = FixedOffset::east(5 * 3600).ymd(2021, 4, 8).and_hms(0, 0, 0); + let timestamp = datetime!(2021-04-08 0:00:0 +05:00); 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(), + "time": timestamp + .format(&format_description::well_known::Rfc3339) + .unwrap() + .as_str(), "temperature": { "temperature": { "value": 23.0 @@ -137,14 +142,17 @@ mod tests { "pressure": 98.0 }"#; - let timestamp = FixedOffset::east(5 * 3600).ymd(2021, 4, 8).and_hms(0, 0, 0); + let timestamp = datetime!(2021-04-08 0:00:0 +05:00); 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(), + "time": timestamp + .format(&format_description::well_known::Rfc3339) + .unwrap() + .as_str(), "temperature": { "temperature": { "value": 25.0 @@ -200,7 +208,6 @@ mod tests { actual_output ); } - use proptest::prelude::*; proptest! { @@ -270,7 +277,7 @@ mod tests { thin_edge_json: &str, expected_output: Value, ) { - let timestamp = FixedOffset::east(5 * 3600).ymd(2021, 4, 8).and_hms(0, 0, 0); + let timestamp = datetime!(2021-04-08 0:00:0 +05:00); let output = from_thin_edge_json_with_timestamp(thin_edge_json, timestamp, Some(child_id)); assert_json_eq!( serde_json::from_str::(output.unwrap().as_str()).unwrap(), diff --git a/crates/core/c8y_translator/src/serializer.rs b/crates/core/c8y_translator/src/serializer.rs index 96a3e507..e268235a 100644 --- a/crates/core/c8y_translator/src/serializer.rs +++ b/crates/core/c8y_translator/src/serializer.rs @@ -1,12 +1,12 @@ -use chrono::prelude::*; use json_writer::{JsonWriter, JsonWriterError}; use thin_edge_json::measurement::MeasurementVisitor; +use time::{format_description, OffsetDateTime}; pub struct C8yJsonSerializer { json: JsonWriter, is_within_group: bool, timestamp_present: bool, - default_timestamp: DateTime, + default_timestamp: OffsetDateTime, } #[derive(thiserror::Error, Debug)] @@ -34,7 +34,7 @@ pub enum MeasurementStreamError { } impl C8yJsonSerializer { - pub fn new(default_timestamp: DateTime, maybe_child_id: Option<&str>) -> Self { + pub fn new(default_timestamp: OffsetDateTime, maybe_child_id: Option<&str>) -> Self { let capa = 1024; // XXX: Choose a capacity based on expected JSON length. let mut json = JsonWriter::with_capacity(capa); @@ -96,13 +96,18 @@ impl C8yJsonSerializer { impl MeasurementVisitor for C8yJsonSerializer { type Error = C8yJsonSerializationError; - fn visit_timestamp(&mut self, timestamp: DateTime) -> Result<(), Self::Error> { + fn visit_timestamp(&mut self, timestamp: OffsetDateTime) -> Result<(), Self::Error> { if self.is_within_group { return Err(MeasurementStreamError::UnexpectedTimestamp.into()); } self.json.write_key("time")?; - self.json.write_str(timestamp.to_rfc3339().as_str())?; + self.json.write_str( + timestamp + .format(&format_description::well_known::Rfc3339) + .unwrap() + .as_str(), + )?; self.timestamp_present = true; Ok(()) @@ -146,18 +151,16 @@ impl MeasurementVisitor for C8yJsonSerializer { #[cfg(test)] mod tests { + use ::time::macros::datetime; use assert_json_diff::*; use assert_matches::*; use serde_json::json; use super::*; - use chrono::offset::FixedOffset; #[test] fn serialize_single_value_message() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); serializer.visit_timestamp(timestamp)?; @@ -183,9 +186,7 @@ mod tests { } #[test] fn serialize_multi_value_message() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); serializer.visit_timestamp(timestamp)?; @@ -236,9 +237,7 @@ mod tests { #[test] fn serialize_empty_message() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); @@ -257,9 +256,7 @@ mod tests { #[test] fn serialize_timestamp_message() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); serializer.visit_timestamp(timestamp)?; @@ -281,9 +278,7 @@ mod tests { #[test] fn serialize_timestamp_within_group() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); serializer.visit_start_group("location")?; @@ -301,9 +296,7 @@ mod tests { #[test] fn serialize_unexpected_end_of_group() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); serializer.visit_measurement("alti", 2100.4)?; @@ -323,9 +316,7 @@ mod tests { #[test] fn serialize_unexpected_start_of_group() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); serializer.visit_start_group("location")?; @@ -346,9 +337,7 @@ mod tests { #[test] fn serialize_unexpected_end_of_message() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, None); serializer.visit_start_group("location")?; @@ -369,9 +358,7 @@ mod tests { #[test] fn serialize_timestamp_child_message() -> anyhow::Result<()> { - let timestamp = FixedOffset::east(5 * 3600) - .ymd(2021, 6, 22) - .and_hms_nano(17, 3, 14, 123456789); + let timestamp = datetime!(2021-06-22 17:03:14.123456789 +05:00); let mut serializer = C8yJsonSerializer::new(timestamp, Some("child1")); serializer.visit_timestamp(timestamp)?; diff --git a/crates/core/tedge_mapper/Cargo.toml b/crates/core/tedge_mapper/Cargo.toml index c1a13215..baf8403f 100644 --- a/crates/core/tedge_mapper/Cargo.toml +++ b/crates/core/tedge_mapper/Cargo.toml @@ -32,7 +32,6 @@ async-trait = "0.1" batcher = { path = "../../common/batcher" } c8y_smartrest = { path = "../c8y_smartrest" } c8y_translator = { path = "../c8y_translator" } -chrono = "0.4" clock = { path = "../../common/clock" } csv = "1.1" download = { path = "../../common/download" } diff --git a/crates/core/tedge_mapper/src/az_converter.rs b/crates/core/tedge_mapper/src/az_converter.rs index 27e6c1e6..d6ca77dc 100644 --- a/crates/core/tedge_mapper/src/az_converter.rs +++ b/crates/core/tedge_mapper/src/az_converter.rs @@ -53,15 +53,15 @@ mod tests { use crate::size_threshold::SizeThresholdExceeded; use assert_json_diff::*; use assert_matches::*; - use chrono::{FixedOffset, TimeZone}; use mqtt_channel::Topic; use serde_json::json; + use time::macros::datetime; struct TestClock; impl Clock for TestClock { fn now(&self) -> clock::Timestamp { - FixedOffset::east(5 * 3600).ymd(2021, 4, 8).and_hms(0, 0, 0) + datetime!(2021-04-08 00:00:00 +05:00) } } diff --git a/crates/core/tedge_mapper/src/collectd_mapper/batcher.rs b/crates/core/tedge_mapper/src/collectd_mapper/batcher.rs index e7ecd830..9844485e 100644 --- a/crates/core/tedge_mapper/src/collectd_mapper/batcher.rs +++ b/crates/core/tedge_mapper/src/collectd_mapper/batcher.rs @@ -7,7 +7,6 @@ use thin_edge_json::{ }; use crate::collectd_mapper::{collectd::CollectdMessage, error::DeviceMonitorError}; -use chrono::Local; use thin_edge_json::group::MeasurementGrouperError; #[derive(Debug)] @@ -22,7 +21,7 @@ impl MessageBatch { let mut messages = messages.into_iter(); if let Some(first_message) = messages.next() { - let timestamp = first_message.timestamp.with_timezone(Local::now().offset()); + let timestamp = first_message.timestamp; let mut batch = MessageBatch::start_batch(first_message, timestamp)?; for message in messages { batch.add_to_batch(message)?; @@ -72,12 +71,12 @@ impl MessageBatch { mod tests { use super::*; use assert_matches::assert_matches; - use chrono::{TimeZone, Utc}; use clock::{Clock, WallClock}; + use time::macros::datetime; #[test] fn test_message_batch_processor() -> anyhow::Result<()> { - let timestamp = Utc.ymd(2015, 5, 15).and_hms_milli(0, 0, 1, 444); + let timestamp = datetime!(2015-05-15 0:00:01.444 UTC); let collectd_message = CollectdMessage::new("temperature", "value", 32.5, timestamp); let mut message_batch = MessageBatch::start_batch(collectd_message, WallClock.now())?; diff --git a/crates/core/tedge_mapper/src/collectd_mapper/collectd.rs b/crates/core/tedge_mapper/src/collectd_mapper/collectd.rs index ab98f67d..0bb0fe22 100644 --- a/crates/core/tedge_mapper/src/collectd_mapper/collectd.rs +++ b/crates/core/tedge_mapper/src/collectd_mapper/collectd.rs @@ -1,13 +1,13 @@ use batcher::Batchable; -use chrono::{DateTime, NaiveDateTime, Utc}; use mqtt_channel::Message; use thin_edge_json::measurement::MeasurementVisitor; +use time::{Duration, OffsetDateTime}; #[derive(Debug)] pub struct CollectdMessage { pub metric_group_key: String, pub metric_key: String, - pub timestamp: DateTime, + pub timestamp: OffsetDateTime, pub metric_value: f64, } @@ -43,7 +43,7 @@ impl CollectdMessage { metric_group_key: &str, metric_key: &str, metric_value: f64, - timestamp: DateTime, + timestamp: OffsetDateTime, ) -> Self { Self { metric_group_key: metric_group_key.to_string(), @@ -167,10 +167,11 @@ impl CollectdPayload { }) } - pub fn timestamp(&self) -> DateTime { + pub fn timestamp(&self) -> OffsetDateTime { let timestamp = self.timestamp.trunc() as i64; let nanoseconds = (self.timestamp.fract() * 1.0e9) as u32; - DateTime::::from_utc(NaiveDateTime::from_timestamp(timestamp, nanoseconds), Utc) + OffsetDateTime::from_unix_timestamp(timestamp).unwrap() + + Duration::nanoseconds(nanoseconds as i64) } } @@ -181,18 +182,17 @@ impl Batchable for CollectdMessage { format!("{}/{}", &self.metric_group_key, &self.metric_key) } - fn event_time(&self) -> DateTime { + fn event_time(&self) -> OffsetDateTime { self.timestamp } } #[cfg(test)] mod tests { - use std::ops::Index; - use assert_matches::assert_matches; - use chrono::TimeZone; use mqtt_channel::Topic; + use std::ops::Index; + use time::macros::datetime; use super::*; @@ -212,10 +212,7 @@ mod tests { assert_eq!(metric_group_key, "temperature"); assert_eq!(metric_key, "value"); - assert_eq!( - *timestamp, - Utc.ymd(1973, 11, 29).and_hms_milli(21, 33, 09, 0) - ); + assert_eq!(*timestamp, datetime!(1973-11-29 21:33:09.0 UTC)); assert_eq!(*metric_value, 32.5); } @@ -230,15 +227,12 @@ mod tests { metric_group_key, metric_key, timestamp, - metric_value, + metric_value: _, } = collectd_message.index(0); assert_eq!(metric_group_key, "temperature"); assert_eq!(metric_key, "value_val1"); - assert_eq!( - *timestamp, - Utc.ymd(1973, 11, 29).and_hms_milli(21, 33, 09, 0) - ); + assert_eq!(*timestamp, datetime!(1973-11-29 21:33:09.0 UTC)); let CollectdMessage { metric_group_key, @@ -249,10 +243,7 @@ mod tests { assert_eq!(metric_group_key, "temperature"); assert_eq!(metric_key, "value_val2"); - assert_eq!( - *timestamp, - Utc.ymd(1973, 11, 29).and_hms_milli(21, 33, 09, 0) - ); + assert_eq!(*timestamp, datetime!(1973-11-29 21:33:09.0 UTC)); assert_eq!(*metric_value, 45.2); } @@ -272,10 +263,7 @@ mod tests { assert_eq!(metric_group_key, "temperature"); assert_eq!(metric_key, "value"); - assert_eq!( - *timestamp, - Utc.ymd(1973, 11, 29).and_hms_milli(21, 33, 09, 125) - ); + assert_eq!(*timestamp, datetime!(1973-11-29 21:33:09.125 UTC)); assert_eq!(*metric_value, 32.5); } diff --git a/crates/core/tedge_mapper/src/sm_c8y_mapper/mapper.rs b/crates/core/tedge_mapper/src/sm_c8y_mapper/mapper.rs index 251e0f90..a778f86b 100644 --- a/crates/core/tedge_mapper/src/sm_c8y_mapper/mapper.rs +++ b/crates/core/tedge_mapper/src/sm_c8y_mapper/mapper.rs @@ -618,7 +618,7 @@ mod tests { #[test_case("/path/to/another-variant-2021-10-25T07:45:41Z.log")] #[test_case("/yet-another-variant-2021-10-25T07:45:41Z.log")] fn test_datetime_parsing_from_path(file_path: &str) { - // checking that `get_date_from_file_path` unwraps a `chrono::NaiveDateTime` object. + // checking that `get_date_from_file_path` unwraps a `OffsetDateTime` object. // this should return an Ok Result. let path_buf = PathBuf::from_str(file_path).unwrap(); let path_buf_datetime = get_datetime_from_file_path(&path_buf); @@ -630,7 +630,7 @@ mod tests { #[test_case("/path/to/another-variant-07:45:41Z-2021-10-25T.log")] #[test_case("/yet-another-variant-2021-10-25T07:45Z.log")] fn test_datetime_parsing_from_path_fail(file_path: &str) { - // checking that `get_date_from_file_path` unwraps a `chrono::NaiveDateTime` object. + // checking that `get_date_from_file_path` unwraps a `OffsetDateTime` object. // this should return an err. let path_buf = PathBuf::from_str(file_path).unwrap(); let path_buf_datetime = get_datetime_from_file_path(&path_buf); diff --git a/crates/core/thin_edge_json/Cargo.toml b/crates/core/thin_edge_json/Cargo.toml index 5ce83271..9ed0471a 100644 --- a/crates/core/thin_edge_json/Cargo.toml +++ b/crates/core/thin_edge_json/Cargo.toml @@ -8,13 +8,12 @@ rust-version = "1.58.1" # 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 = { version = "1.0", features = ["derive"] } serde_json = "1" thiserror = "1.0" +time = { version = "0.3", features = ["formatting", "local-offset", "parsing", "serde"] } [dev-dependencies] anyhow = "1.0" @@ -26,6 +25,7 @@ stats_alloc = "0.1" walkdir = "2" assert_matches = "1.5" test-case = "1.2" +time = { version = "0.3", features = ["macros"] } [[bench]] name = "parsing" diff --git a/crates/core/thin_edge_json/benches/parsing.rs b/crates/core/thin_edge_json/benches/parsing.rs index b4294cb6..bdd6871f 100644 --- a/crates/core/thin_edge_json/benches/parsing.rs +++ b/crates/core/thin_edge_json/benches/parsing.rs @@ -1,6 +1,6 @@ -use chrono::prelude::*; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use thin_edge_json::measurement::MeasurementVisitor; +use time::OffsetDateTime; const INPUT: &str = r#"{ "time" : "2021-04-30T17:03:14.123+02:00", @@ -26,7 +26,7 @@ struct DummyVisitor; impl MeasurementVisitor for DummyVisitor { type Error = DummyError; - fn visit_timestamp(&mut self, _value: DateTime) -> Result<(), Self::Error> { + fn visit_timestamp(&mut self, _value: OffsetDateTime) -> Result<(), Self::Error> { Ok(()) } fn visit_measurement(&mut self, _name: &str, _value: f64) -> Result<(), Self::Error> { diff --git a/crates/core/thin_edge_json/examples/validate.rs b/crates/core/thin_edge_json/examples/validate.rs index 49560051..cbab5138 100644 --- a/crates/core/thin_edge_json/examples/validate.rs +++ b/crates/core/thin_edge_json/examples/validate.rs @@ -1,6 +1,6 @@ -use chrono::prelude::*; use std::env; use thin_edge_json::measurement::MeasurementVisitor; +use time::OffsetDateTime; #[global_allocator] static GLOBAL: &stats_alloc::StatsAlloc = &stats_alloc::INSTRUMENTED_SYSTEM; @@ -36,7 +36,7 @@ struct DummyVisitor; impl MeasurementVisitor for DummyVisitor { type Error = DummyError; - fn visit_timestamp(&mut self, _value: DateTime) -> Result<(), Self::Error> { + fn visit_timestamp(&mut self, _value: OffsetDateTime) -> Result<(), Self::Error> { Ok(()) } fn visit_measurement(&mut self, _name: &str, _value: f64) -> Result<(), Self::Error> { diff --git a/crates/core/thin_edge_json/src/builder.rs b/crates/core/thin_edge_json/src/builder.rs index 098c7304..cb4b9ee6 100644 --- a/crates/core/thin_edge_json/src/builder.rs +++ b/crates/core/thin_edge_json/src/builder.rs @@ -1,9 +1,10 @@ +use time::OffsetDateTime; + use crate::{data::*, measurement::*}; -use chrono::prelude::*; /// A `MeasurementVisitor` that builds up `ThinEdgeJson`. pub struct ThinEdgeJsonBuilder { - timestamp: Option>, + timestamp: Option, inside_group: Option, measurements: Vec, } @@ -36,7 +37,7 @@ impl ThinEdgeJsonBuilder { impl MeasurementVisitor for ThinEdgeJsonBuilder { type Error = ThinEdgeJsonBuilderError; - fn visit_timestamp(&mut self, value: DateTime) -> Result<(), Self::Error> { + fn visit_timestamp(&mut self, value: OffsetDateTime) -> Result<(), Self::Error> { match self.timestamp { None => { self.timestamp = Some(value); diff --git a/crates/core/thin_edge_json/src/data.rs b/crates/core/thin_edge_json/src/data.rs index 4fa25cf4..ef3245e3 100644 --- a/crates/core/thin_edge_json/src/data.rs +++ b/crates/core/thin_edge_json/src/data.rs @@ -1,11 +1,11 @@ //! The in-memory data model representing ThinEdge JSON. -use chrono::prelude::*; +use time::OffsetDateTime; /// In-memory representation of parsed ThinEdge JSON. #[derive(Debug)] pub struct ThinEdgeJson { - pub timestamp: Option>, + pub timestamp: Option, pub values: Vec, } @@ -14,7 +14,7 @@ impl ThinEdgeJson { self.timestamp.is_some() } - pub fn set_timestamp(&mut self, timestamp: DateTime) { + pub fn set_timestamp(&mut self, timestamp: OffsetDateTime) { self.timestamp = Some(timestamp) } } diff --git a/crates/core/thin_edge_json/src/group.rs b/crates/core/thin_edge_json/src/group.rs index 17488a3a..706812dc 100644 --- a/crates/core/thin_edge_json/src/group.rs +++ b/crates/core/thin_edge_json/src/group.rs @@ -1,12 +1,11 @@ -use chrono::offset::FixedOffset; -use chrono::DateTime; use std::collections::HashMap; +use time::OffsetDateTime; use crate::measurement::MeasurementVisitor; #[derive(Debug)] pub struct MeasurementGroup { - timestamp: Option>, + timestamp: Option, values: HashMap, } @@ -18,7 +17,7 @@ impl MeasurementGroup { } } - pub fn timestamp(&self) -> Option> { + pub fn timestamp(&self) -> Option { self.timestamp } @@ -138,7 +137,7 @@ impl Default for MeasurementGrouper { impl MeasurementVisitor for MeasurementGrouper { type Error = MeasurementGrouperError; - fn visit_timestamp(&mut self, time: DateTime) -> Result<(), Self::Error> { + fn visit_timestamp(&mut self, time: OffsetDateTime) -> Result<(), Self::Error> { self.measurement_group.timestamp = Some(time); Ok(()) } @@ -192,9 +191,9 @@ impl MeasurementVisitor for MeasurementGrouper { #[cfg(test)] mod tests { use super::*; - use chrono::prelude::*; use mockall::predicate::*; use mockall::*; + use time::{macros::datetime, Duration}; #[derive(thiserror::Error, Debug, Clone)] pub enum TestError { @@ -209,7 +208,7 @@ mod tests { impl MeasurementVisitor for GroupedVisitor { type Error = TestError; - fn visit_timestamp(&mut self, value: DateTime) -> Result<(), TestError>; + fn visit_timestamp(&mut self, value: OffsetDateTime) -> Result<(), TestError>; fn visit_measurement(&mut self, name: &str, value: f64) -> Result<(), TestError>; fn visit_start_group(&mut self, group: &str) -> Result<(), TestError>; fn visit_end_group(&mut self) -> Result<(), TestError>; @@ -348,9 +347,9 @@ mod tests { Ok(()) } - fn test_timestamp(minute: u32) -> DateTime { - FixedOffset::east(5 * 3600) - .ymd(2021, 4, 8) - .and_hms(13, minute, 00) + fn test_timestamp(minute: u32) -> OffsetDateTime { + let mut dt = datetime!(2021-04-08 13:00:00 +05:00); + dt += Duration::minutes(minute as i64); + dt } } diff --git a/crates/core/thin_edge_json/src/measurement.rs b/crates/core/thin_edge_json/src/measurement.rs index a458372d..13be5190 100644 --- a/crates/core/thin_edge_json/src/measurement.rs +++ b/crates/core/thin_edge_json/src/measurement.rs @@ -1,5 +1,4 @@ -use chrono::offset::FixedOffset; -use chrono::DateTime; +use time::OffsetDateTime; /// The `MeasurementVisitor` trait represents the capability to visit a series of measurements, possibly grouped. /// @@ -7,7 +6,8 @@ use chrono::DateTime; /// /// ``` /// # use thin_edge_json::measurement::*; -/// # use chrono::*; +/// # use time::{OffsetDateTime, format_description}; +/// /// struct MeasurementPrinter { /// group: Option, /// } @@ -27,9 +27,11 @@ use chrono::DateTime; /// impl MeasurementVisitor for MeasurementPrinter { /// type Error = MeasurementError; /// -/// fn visit_timestamp(&mut self, timestamp: DateTime) -> Result<(), Self::Error> { +/// fn visit_timestamp(&mut self, timestamp: OffsetDateTime) -> Result<(), Self::Error> { +/// let format = +/// format_description::parse("[day] [month repr:short] [year] [hour repr:24]:[minute]:[seconds] [offset_hour sign:mandatory]:[offset_minute]").unwrap(); /// if self.group.is_none() { -/// Ok(println!("time = {}", timestamp.to_rfc2822())) +/// Ok(println!("time = {}", timestamp.format(&format).unwrap())) /// } else { /// Err(MeasurementError::UnexpectedTimestamp) /// } @@ -67,7 +69,7 @@ pub trait MeasurementVisitor { type Error: std::error::Error + std::fmt::Debug; /// Set the timestamp shared by all the measurements of this series. - fn visit_timestamp(&mut self, value: DateTime) -> Result<(), Self::Error>; + fn visit_timestamp(&mut self, value: OffsetDateTime) -> Result<(), Self::Error>; /// Add a new measurement, attached to the current group if any. fn visit_measurement(&mut self, name: &str, value: f64) -> Result<(), Self::Error>; diff --git a/crates/core/thin_edge_json/src/parser.rs b/crates/core/thin_edge_json/src/parser.rs index 3d41ced3..080345ed 100644 --- a/crates/core/thin_edge_json/src/parser.rs +++ b/crates/core/thin_edge_json/src/parser.rs @@ -3,7 +3,6 @@ //! [^1]: It only allocates in presence of escaped strings as keys. //! use crate::measurement::MeasurementVisitor; -use chrono::prelude::*; use serde::{ de::{self, DeserializeSeed, MapAccess}, Deserializer, @@ -11,6 +10,7 @@ use serde::{ use std::borrow::Cow; use std::convert::TryFrom; use std::fmt; +use time::{format_description, OffsetDateTime}; /// Parses `input` as ThinEdge JSON yielding the parsed measurements to the `visitor`. pub fn parse_str( @@ -99,8 +99,11 @@ where } "time" => { let timestamp_str: &str = map.next_value()?; - let timestamp = DateTime::parse_from_rfc3339(timestamp_str) - .map_err(|err| de::Error::custom(invalid_timestamp(timestamp_str, err)))?; + let timestamp = OffsetDateTime::parse( + timestamp_str, + &format_description::well_known::Rfc3339, + ) + .map_err(|err| de::Error::custom(invalid_timestamp(timestamp_str, err)))?; let () = self .visitor @@ -293,69 +296,71 @@ fn map_error(error: serde_json::Error, input: &str) -> ThinEdgeJsonParserError { input_excerpt, } } +#[cfg(test)] +mod tests { + use time::macros::datetime; + + use crate::parser::parse_str; + + #[test] + fn it_deserializes_thin_edge_json() -> anyhow::Result<()> { + use crate::builder::ThinEdgeJsonBuilder; + let input = r#"{ + "time" : "2021-04-30T17:03:14.123+02:00", + "pressure": 123.4, + "temperature": 24, + "coordinate": { + "x": 1, + "y": 2.0, + "z": -42.0 + }, + "escaped\\": 123.0 + }"#; + + let mut builder = ThinEdgeJsonBuilder::new(); + + let () = parse_str(input, &mut builder)?; + + let output = builder.done()?; + + assert_eq!( + output.timestamp, + Some(datetime!(2021-04-30 17:03:14.123 +02:00)) + ); + + assert_eq!( + output.values, + vec![ + ("pressure", 123.4).into(), + ("temperature", 24.0).into(), + ( + "coordinate", + vec![("x", 1.0).into(), ("y", 2.0).into(), ("z", -42.0).into(),] + ) + .into(), + (r#"escaped\"#, 123.0).into(), + ] + ); + Ok(()) + } -#[test] -fn it_deserializes_thin_edge_json() -> anyhow::Result<()> { - use crate::builder::ThinEdgeJsonBuilder; - let input = r#"{ - "time" : "2021-04-30T17:03:14.123+02:00", - "pressure": 123.4, - "temperature": 24, - "coordinate": { - "x": 1, - "y": 2.0, - "z": -42.0 - }, - "escaped\\": 123.0 - }"#; - - let mut builder = ThinEdgeJsonBuilder::new(); - - let () = parse_str(input, &mut builder)?; - - let output = builder.done()?; - - assert_eq!( - output.timestamp, - Some( - FixedOffset::east(2 * 3600) - .ymd(2021, 4, 30) - .and_hms_milli(17, 3, 14, 123) - ) - ); - - assert_eq!( - output.values, - vec![ - ("pressure", 123.4).into(), - ("temperature", 24.0).into(), - ( - "coordinate", - vec![("x", 1.0).into(), ("y", 2.0).into(), ("z", -42.0).into(),] - ) - .into(), - (r#"escaped\"#, 123.0).into(), - ] - ); - Ok(()) -} - -#[test] -fn it_shows_input_excerpt_on_error() -> anyhow::Result<()> { - use crate::builder::ThinEdgeJsonBuilder; + #[test] + fn it_shows_input_excerpt_on_error() -> anyhow::Result<()> { + use crate::builder::ThinEdgeJsonBuilder; - let input = "{\n\"time\" : null\n}"; + let input = "{\n\"time\" : null\n}"; - let mut builder = ThinEdgeJsonBuilder::new(); + let mut builder = ThinEdgeJsonBuilder::new(); - let res = parse_str(input, &mut builder); + let res = parse_str(input, &mut builder); - assert!(res.is_err()); + assert!(res.is_err()); - assert_eq!( + assert_eq!( res.unwrap_err().to_string(), "Invalid JSON: invalid type: null, expected a borrowed string at line 2 column 13: `l\n}`", ); - Ok(()) + Ok(()) + } } diff --git a/crates/core/thin_edge_json/src/serialize.rs b/crates/core/thin_edge_json/src/serialize.rs index 3823776b..0c307ec0 100644 --- a/crates/core/thin_edge_json/src/serialize.rs +++ b/crates/core/thin_edge_json/src/serialize.rs @@ -1,12 +1,11 @@ use crate::measurement::MeasurementVisitor; -use chrono::offset::FixedOffset; -use chrono::DateTime; use json_writer::{JsonWriter, JsonWriterError}; +use time::{format_description, OffsetDateTime}; pub struct ThinEdgeJsonSerializer { json: JsonWriter, is_within_group: bool, - default_timestamp: Option>, + default_timestamp: Option, timestamp_present: bool, } @@ -15,6 +14,9 @@ pub enum ThinEdgeJsonSerializationError { #[error(transparent)] FormatError(#[from] std::fmt::Error), + #[error(transparent)] + FromTimeFormatError(#[from] time::error::Format), + #[error(transparent)] MeasurementCollectorError(#[from] MeasurementStreamError), @@ -45,7 +47,7 @@ impl ThinEdgeJsonSerializer { Self::new_with_timestamp(None) } - pub fn new_with_timestamp(default_timestamp: Option>) -> Self { + pub fn new_with_timestamp(default_timestamp: Option) -> Self { let capa = 1024; // XXX: Choose a capacity based on expected JSON length. let mut json = JsonWriter::with_capacity(capa); json.write_open_obj(); @@ -92,13 +94,17 @@ impl Default for ThinEdgeJsonSerializer { impl MeasurementVisitor for ThinEdgeJsonSerializer { type Error = ThinEdgeJsonSerializationError; - fn visit_timestamp(&mut self, timestamp: DateTime) -> Result<(), Self::Error> { + fn visit_timestamp(&mut self, timestamp: OffsetDateTime) -> Result<(), Self::Error> { if self.is_within_group { return Err(MeasurementStreamError::UnexpectedTimestamp.into()); } self.json.write_key("time")?; - self.json.write_str(timestamp.to_rfc3339().as_str())?; + self.json.write_str( + timestamp + .format(&format_description::well_known::Rfc3339)? + .as_str(), + )?; self.timestamp_present = true; Ok(()) } @@ -134,10 +140,9 @@ impl MeasurementVisitor for ThinEdgeJsonSerializer { #[cfg(test)] mod tests { use super::*; - use chrono::{offset::FixedOffset, DateTime, Local}; - fn test_timestamp() -> DateTime { - let local_time_now: DateTime = Local::now(); - local_time_now.with_timezone(local_time_now.offset()) + + fn test_timestamp() -> OffsetDateTime { + OffsetDateTime::now_utc() } #[test] @@ -149,7 +154,14 @@ mod tests { serializer.visit_measurement("temperature", 25.5)?; let body = r#""temperature":25.5"#; - let expected_output = format!(r#"{{"time":"{}",{}}}"#, timestamp.to_rfc3339(), body); + let expected_output = format!( + r#"{{"time":"{}",{}}}"#, + timestamp + .format(&format_description::well_known::Rfc3339) + .unwrap() + .as_str(), + body + ); let output = serializer.into_string()?; assert_eq!(output, expected_output); Ok(()) @@ -178,7 +190,14 @@ mod tests { serializer.visit_end_group()?; serializer.visit_measurement("pressure", 255.0)?; let body = r#""temperature":25.5,"location":{"alti":2100.4,"longi":2200.4,"lati":2300.4},"pressure":255.0}"#; - let expected_output = format!(r#"{{"time":"{}",{}"#, timestamp.to_rfc3339(), body); + let expected_output = format!( + r#"{{"time":"{}",{}"#, + timestamp + .format(&format_description::well_known::Rfc3339) + .unwrap() + .as_str(), + body + ); let output = serializer.into_string()?; assert_eq!(expected_output, output); Ok(()) @@ -198,7 +217,14 @@ mod tests { let mut serializer = ThinEdgeJsonSerializer::new(); let timestamp = test_timestamp(); serializer.visit_timestamp(timestamp)?; - let expected_output = format!(r#"{{"time":"{}"{}"#, timestamp.to_rfc3339(), "}"); + let expected_output = format!( + r#"{{"time":"{}"{}"#, + timestamp + .format(&format_description::well_known::Rfc3339) + .unwrap() + .as_str(), + "}" + ); let output = serializer.into_string()?; assert_eq!(expected_output, output); Ok(()) diff --git a/crates/core/thin_edge_json/tests/fixtures/invalid/reject_invalid_timestamp.expected_error b/crates/core/thin_edge_json/tests/fixtures/invalid/reject_invalid_timestamp.expected_error index c28fb90e..081cbde6 100644 --- a/crates/core/thin_edge_json/tests/fixtures/invalid/reject_invalid_timestamp.expected_error +++ b/crates/core/thin_edge_json/tests/fixtures/invalid/reject_invalid_timestamp.expected_error @@ -1,4 +1,4 @@ -Invalid JSON: Invalid ISO8601 timestamp (expected YYYY-MM-DDThh:mm:ss.sss.±hh:mm): "2013-06-22 3am": input contains invalid characters at line 2 column 27: `", +Invalid JSON: Invalid ISO8601 timestamp (expected YYYY-MM-DDThh:mm:ss.sss.±hh:mm): "2013-06-22 3am": a character literal was not valid at line 2 column 27: `", "pressure": 220 } ` \ No newline at end of file diff --git a/crates/core/thin_edge_json/tests/fixtures/invalid/reject_partial_timestamp.expected_error b/crates/core/thin_edge_json/tests/fixtures/invalid/reject_partial_timestamp.expected_error index 78bd00e0..54a5bc5a 100644 --- a/crates/core/thin_edge_json/tests/fixtures/invalid/reject_partial_timestamp.expected_error +++ b/crates/core/thin_edge_json/tests/fixtures/invalid/reject_partial_timestamp.expected_error @@ -1,4 +1,4 @@ -Invalid JSON: Invalid ISO8601 timestamp (expected YYYY-MM-DDThh:mm:ss.sss.±hh:mm): "2013-06-22": premature end of input at line 2 column 23: `", +Invalid JSON: Invalid ISO8601 timestamp (expected YYYY-MM-DDThh:mm:ss.sss.±hh:mm): "2013-06-22": a character literal was not valid at line 2 column 23: `", "pressure": 220 } ` \ No newline at end of file diff --git a/crates/core/thin_edge_json/tests/fixtures/invalid/reject_timestamp_missing_time_separator.expected_error b/crates/core/thin_edge_json/tests/fixtures/invalid/reject_timestamp_missing_time_separator.expected_error index 1e75dd3a..a2569eb9 100644 --- a/crates/core/thin_edge_json/tests/fixtures/invalid/reject_timestamp_missing_time_separator.expected_error +++ b/crates/core/thin_edge_json/tests/fixtures/invalid/reject_timestamp_missing_time_separator.expected_error @@ -1,2 +1,2 @@ -Invalid JSON: Invalid ISO8601 timestamp (expected YYYY-MM-DDThh:mm:ss.sss.±hh:mm): "2013-06-2217:03:14.000658767+02:00": input contains invalid characters at line 3 column 1: `} +Invalid JSON: Invalid ISO8601 timestamp (expected YYYY-MM-DDThh:mm:ss.sss.±hh:mm): "2013-06-2217:03:14.000658767+02:00": a character literal was not valid at line 3 column 1: `} ` \ No newline at end of file -- cgit v1.2.3