use core::fmt;
use chrono::{DateTime, LocalResult, TimeZone, Utc};
use serde::{de, Deserialize};
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_any(GithubTimestampVisitor)
}
#[derive(Debug, Deserialize)]
struct WrappedGithubTimestamp(#[serde(deserialize_with = "deserialize")] DateTime<Utc>);
pub(crate) fn deserialize_opt<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: de::Deserializer<'de>,
{
Option::<WrappedGithubTimestamp>::deserialize(deserializer)
.map(|opt_wrapped| opt_wrapped.map(|wrapped| wrapped.0))
}
struct GithubTimestampVisitor;
impl de::Visitor<'_> for GithubTimestampVisitor {
type Value = DateTime<Utc>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter
.write_str("a RFC3339 date and time _string_ or a unix timestamp _integer_ in seconds")
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(Utc.timestamp_opt(v, 0), &v)
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(Utc.timestamp_opt(v as i64, 0), &v)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
v.parse().map_err(E::custom)
}
}
pub(crate) fn serde_from<T, E, V>(me: LocalResult<T>, ts: &V) -> Result<T, E>
where
E: de::Error,
V: fmt::Display,
T: fmt::Display,
{
match me {
LocalResult::None => Err(E::custom(format!("value is not a legal timestamp: {ts}"))),
LocalResult::Ambiguous(min, max) => Err(E::custom(format!(
"value is an ambiguous timestamp: {ts}. It could be {min} or {max}"
))),
LocalResult::Single(val) => Ok(val),
}
}