use crate::query::datetime::Datetime; use crate::{DiskIoTune, FileIoBufferSize, ReadSys}; use err::Error; use serde::{Deserialize, Serialize}; use std::fmt; use std::time::Duration; fn bool_true() -> bool { true } fn bool_is_true(x: &bool) -> bool { *x } #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Api1Range { #[serde(rename = "type", default, skip_serializing_if = "String::is_empty")] ty: String, #[serde(rename = "startDate")] beg: Datetime, #[serde(rename = "endDate")] end: Datetime, } impl Api1Range { pub fn new(beg: Datetime, end: Datetime) -> Result { let ret = Self { ty: String::new(), beg, end, }; Ok(ret) } pub fn beg(&self) -> &Datetime { &self.beg } pub fn end(&self) -> &Datetime { &self.end } } #[test] fn serde_de_range_zulu() { let s = r#"{"startDate": "2022-11-22T10:15:12.412Z", "endDate": "2022-11-22T10:15:12.413556Z"}"#; let range: Api1Range = serde_json::from_str(s).unwrap(); assert_eq!(range.beg().offset().local_minus_utc(), 0); assert_eq!(range.end().offset().local_minus_utc(), 0); assert_eq!(range.beg().timestamp_subsec_micros(), 412000); assert_eq!(range.end().timestamp_subsec_micros(), 413556); } #[test] fn serde_de_range_offset() { let s = r#"{"startDate": "2022-11-22T10:15:12.412Z", "endDate": "2022-11-22T10:15:12.413556Z"}"#; let range: Api1Range = serde_json::from_str(s).unwrap(); assert_eq!(range.beg().offset().local_minus_utc(), 0); assert_eq!(range.end().offset().local_minus_utc(), 0); assert_eq!(range.beg().timestamp_subsec_micros(), 412000); assert_eq!(range.end().timestamp_subsec_micros(), 413556); } #[test] fn serde_ser_range_offset() { use chrono::{FixedOffset, NaiveDate, TimeZone}; let beg = FixedOffset::east_opt(60 * 60 * 3) .unwrap() .from_local_datetime( &NaiveDate::from_ymd_opt(2022, 11, 22) .unwrap() .and_hms_milli_opt(13, 14, 15, 16) .unwrap(), ) .earliest() .unwrap(); let end = FixedOffset::east_opt(-60 * 60 * 1) .unwrap() .from_local_datetime( &NaiveDate::from_ymd_opt(2022, 11, 22) .unwrap() .and_hms_milli_opt(13, 14, 15, 800) .unwrap(), ) .earliest() .unwrap(); let range = Api1Range::new(beg.into(), end.into()).unwrap(); let js = serde_json::to_string(&range).unwrap(); let exp = r#"{"startDate":"2022-11-22T13:14:15.016+03:00","endDate":"2022-11-22T13:14:15.800-01:00"}"#; assert_eq!(js, exp); } #[test] fn serde_ser_range_01() -> Result<(), Error> { let beg = Datetime::try_from("2022-11-22T02:03:04Z")?; let end = Datetime::try_from("2022-11-22T02:03:04.123Z")?; let range = Api1Range::new(beg, end).unwrap(); let js = serde_json::to_string(&range).unwrap(); let exp = r#"{"startDate":"2022-11-22T02:03:04Z","endDate":"2022-11-22T02:03:04.123Z"}"#; assert_eq!(js, exp); Ok(()) } #[test] fn serde_ser_range_02() -> Result<(), Error> { let beg = Datetime::try_from("2022-11-22T02:03:04.987654Z")?; let end = Datetime::try_from("2022-11-22T02:03:04.777000Z")?; let range = Api1Range::new(beg, end).unwrap(); let js = serde_json::to_string(&range).unwrap(); let exp = r#"{"startDate":"2022-11-22T02:03:04.987654Z","endDate":"2022-11-22T02:03:04.777Z"}"#; assert_eq!(js, exp); Ok(()) } /// In Api1, the list of channels consists of either `BACKEND/CHANNELNAME` /// or just `CHANNELNAME`. #[derive(Debug, PartialEq)] pub struct ChannelTuple { backend: Option, name: String, } impl ChannelTuple { pub fn new(backend: String, name: String) -> Self { Self { backend: Some(backend), name, } } pub fn from_name(name: String) -> Self { Self { backend: None, name } } pub fn backend(&self) -> Option<&String> { self.backend.as_ref() } pub fn name(&self) -> &str { &self.name } } mod serde_channel_tuple { use super::*; use serde::de::{Deserialize, Deserializer, Visitor}; use serde::ser::{Serialize, Serializer}; impl Serialize for ChannelTuple { fn serialize(&self, serializer: S) -> Result where S: Serializer, { if let Some(backend) = self.backend.as_ref() { serializer.serialize_str(&format!("{}/{}", backend, self.name)) } else { serializer.serialize_str(&self.name) } } } struct Vis; impl<'de> Visitor<'de> for Vis { type Value = ChannelTuple; fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "[Backendname/]Channelname") } fn visit_str(self, val: &str) -> Result where E: serde::de::Error, { let mut it = val.split("/"); // Even empty string splits into one element of empty string let s0 = it.next().unwrap(); if let Some(s1) = it.next() { let ret = ChannelTuple { backend: Some(s0.into()), name: s1.into(), }; Ok(ret) } else { let ret = ChannelTuple { backend: None, name: s0.into(), }; Ok(ret) } } } impl<'de> Deserialize<'de> for ChannelTuple { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(Vis) } } #[test] fn ser_name() { let x = ChannelTuple { backend: None, name: "temperature".into(), }; let js = serde_json::to_string(&x).unwrap(); assert_eq!(js, r#""temperature""#); } #[test] fn ser_backend_name() { let x = ChannelTuple { backend: Some("beach".into()), name: "temperature".into(), }; let js = serde_json::to_string(&x).unwrap(); assert_eq!(js, r#""beach/temperature""#); } } #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Api1Query { range: Api1Range, channels: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] timeout: Option, // All following parameters are private and not to be used #[serde(default, skip_serializing_if = "Option::is_none")] file_io_buffer_size: Option, #[serde(default, skip_serializing_if = "Option::is_none")] decompress: Option, #[serde(default, skip_serializing_if = "Option::is_none")] events_max: Option, #[serde(default, skip_serializing_if = "Option::is_none")] io_queue_len: Option, #[serde(default, skip_serializing_if = "String::is_empty")] log_level: String, #[serde(default, skip_serializing_if = "String::is_empty")] read_sys: String, } impl Api1Query { pub fn new(range: Api1Range, channels: Vec) -> Self { Self { range, channels, timeout: None, decompress: None, events_max: None, file_io_buffer_size: None, io_queue_len: None, log_level: String::new(), read_sys: String::new(), } } pub fn disk_io_tune(&self) -> DiskIoTune { let mut k = DiskIoTune::default(); if let Some(x) = &self.file_io_buffer_size { k.read_buffer_len = x.0; } if let Some(io_queue_len) = self.io_queue_len { k.read_queue_len = io_queue_len as usize; } let read_sys: ReadSys = self.read_sys.as_str().into(); k.read_sys = read_sys; k } pub fn range(&self) -> &Api1Range { &self.range } pub fn channels(&self) -> &[ChannelTuple] { &self.channels } pub fn timeout(&self) -> Option { self.timeout } pub fn timeout_or_default(&self) -> Duration { Duration::from_secs(60 * 30) } pub fn log_level(&self) -> &str { &self.log_level } pub fn decompress(&self) -> Option { self.decompress } pub fn events_max(&self) -> Option { self.events_max } pub fn set_decompress(&mut self, v: Option) { self.decompress = v; } } #[test] fn serde_api1_query() -> Result<(), Error> { let beg = Datetime::try_from("2022-11-22T08:09:10Z")?; let end = Datetime::try_from("2022-11-23T08:11:05.455009+02:00")?; let range = Api1Range::new(beg, end).unwrap(); let ch0 = ChannelTuple::from_name("nameonly".into()); let ch1 = ChannelTuple::new("somebackend".into(), "somechan".into()); let qu = Api1Query::new(range, vec![ch0, ch1]); let js = serde_json::to_string(&qu).unwrap(); assert_eq!( js, r#"{"range":{"startDate":"2022-11-22T08:09:10Z","endDate":"2022-11-23T08:11:05.455009+02:00"},"channels":["nameonly","somebackend/somechan"]}"# ); Ok(()) }