use std::ops::Range;
use chrono::NaiveDate;
use chrono::NaiveTime;
use serde::de::Error;
use serde::de::Unexpected;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use serde_urlencoded::to_string as to_query;
use crate::Str;
fn deserialize_naive_time<'de, D>(deserializer: D) -> Result<NaiveTime, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
NaiveTime::parse_from_str(&string, "%H:%M").map_err(|_| {
Error::invalid_value(
Unexpected::Str(&string),
&"a time stamp string in format %H:%M",
)
})
}
fn serialize_naive_time<S>(time: &NaiveTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&time.format("%H:%M").to_string())
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct OpenClose {
#[serde(rename = "date")]
pub date: NaiveDate,
#[serde(
rename = "open",
deserialize_with = "deserialize_naive_time",
serialize_with = "serialize_naive_time"
)]
pub open: NaiveTime,
#[serde(
rename = "close",
deserialize_with = "deserialize_naive_time",
serialize_with = "serialize_naive_time"
)]
pub close: NaiveTime,
#[doc(hidden)]
#[serde(skip)]
pub _non_exhaustive: (),
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ListReq {
#[serde(rename = "start")]
pub start: NaiveDate,
#[serde(rename = "end")]
pub end: NaiveDate,
#[doc(hidden)]
#[serde(skip)]
pub _non_exhaustive: (),
}
impl From<Range<NaiveDate>> for ListReq {
fn from(range: Range<NaiveDate>) -> Self {
Self {
start: range.start,
end: range.end,
_non_exhaustive: (),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[allow(missing_copy_implementations)]
pub struct ListReqInit {
#[doc(hidden)]
pub _non_exhaustive: (),
}
impl ListReqInit {
#[inline]
pub fn init(self, start: NaiveDate, end: NaiveDate) -> ListReq {
let Self { _non_exhaustive } = self;
ListReq {
start,
end,
_non_exhaustive: (),
}
}
}
Endpoint! {
pub List(ListReq),
Ok => Vec<OpenClose>, [
OK,
],
Err => ListError, []
fn path(_input: &Self::Input) -> Str {
"/v2/calendar".into()
}
fn query(input: &Self::Input) -> Result<Option<Str>, Self::ConversionError> {
Ok(Some(to_query(input)?.into()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api_info::ApiInfo;
use crate::Client;
use serde_json::from_slice as from_json;
use serde_json::to_vec as to_json;
use test_log::test;
#[test]
fn serialize_deserialize_open_close() {
let open_close = OpenClose {
date: NaiveDate::from_ymd_opt(2020, 4, 9).unwrap(),
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
_non_exhaustive: (),
};
let json = to_json(&open_close).unwrap();
assert_eq!(from_json::<OpenClose>(&json).unwrap(), open_close);
}
#[test]
fn parse_open_close_unexpected_time() {
let serialized = br#"{"date":"2020-04-09","open":"09:30:00","close":"16:00"}"#;
let err = from_json::<OpenClose>(serialized).unwrap_err();
assert!(err
.to_string()
.starts_with("invalid value: string \"09:30:00\""));
}
#[test]
fn serialize_deserialize_calendar_request() {
let start = NaiveDate::from_ymd_opt(2020, 4, 6).unwrap();
let end = NaiveDate::from_ymd_opt(2020, 4, 10).unwrap();
let request = ListReqInit::default().init(start, end);
let json = to_json(&request).unwrap();
assert_eq!(from_json::<ListReq>(&json).unwrap(), request);
}
#[test(tokio::test)]
async fn get() {
let api_info = ApiInfo::from_env().unwrap();
let client = Client::new(api_info);
let start = NaiveDate::from_ymd_opt(2020, 4, 6).unwrap();
let end = NaiveDate::from_ymd_opt(2020, 4, 10).unwrap();
let calendar = client
.issue::<List>(&ListReq::from(start..end))
.await
.unwrap();
let expected = (6..10)
.map(|day| OpenClose {
date: NaiveDate::from_ymd_opt(2020, 4, day).unwrap(),
open: NaiveTime::from_hms_opt(9, 30, 0).unwrap(),
close: NaiveTime::from_hms_opt(16, 0, 0).unwrap(),
_non_exhaustive: (),
})
.collect::<Vec<_>>();
assert_eq!(calendar, expected);
}
}