use std::convert::TryInto;
use chrono::{DateTime, TimeZone, Utc};
use serde::{
de::{Error, Unexpected, Visitor},
Deserialize, Deserializer,
};
pub fn object_empty_as_none<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
for<'a> T: Deserialize<'a>,
{
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
struct Empty {}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum Aux<T> {
T(T),
Empty(Empty),
Null,
}
match Deserialize::deserialize(deserializer)? {
Aux::T(t) => Ok(Some(t)),
Aux::Empty(_) | Aux::Null => Ok(None),
}
}
pub fn false_or_datetime<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: Deserializer<'de>,
{
struct FalseOrDatetimeVisitor;
impl Visitor<'_> for FalseOrDatetimeVisitor {
type Value = Option<DateTime<Utc>>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("`false`, or a unix timestamp in seconds potentially followed by `.0`")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: Error,
{
#[allow(clippy::match_bool)]
match v {
false => Ok(None),
true => Err(E::invalid_value(
Unexpected::Bool(true),
&"`false`, or a unix timestamp potentially followed by `.0`",
)),
}
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Some(Utc.timestamp(v.try_into().map_err(E::custom)?, 0)))
}
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_f64(f64::from(v))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: Error,
{
if v.fract() == 0.0 && v > 0.0 {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] self.visit_u64(v as u64)
} else {
Err(E::invalid_value(
Unexpected::Float(v as _),
&"float with no fractional portion",
))
}
}
}
deserializer.deserialize_any(FalseOrDatetimeVisitor)
}
pub fn integer_or_float_to_datetime<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
struct IntegerOrFloatToDatetimeVisitor;
impl Visitor<'_> for IntegerOrFloatToDatetimeVisitor {
type Value = DateTime<Utc>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("unix timestamp in seconds or a float with no fractional part")
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(Utc.timestamp(v.try_into().map_err(E::custom)?, 0))
}
fn visit_f32<E>(self, v: f32) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_f64(f64::from(v))
}
fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
where
E: Error,
{
if v.fract() == 0.0 && v > 0.0 {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] self.visit_u64(v as u64)
} else {
Err(E::invalid_value(
Unexpected::Float(v as _),
&"float with no fractional portion",
))
}
}
}
deserializer.deserialize_any(IntegerOrFloatToDatetimeVisitor)
}
pub(crate) fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
match u8::deserialize(deserializer)? {
0 => Ok(false),
1 => Ok(true),
other => Err(D::Error::invalid_value(
Unexpected::Unsigned(u64::from(other)),
&"zero or one",
)),
}
}
pub(crate) mod mime_serde {
use std::str::FromStr;
use mime::Mime;
use serde::{de::Visitor, Deserializer, Serializer};
pub(crate) fn serialize<S>(mime: &Mime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(mime.as_ref())
}
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<Mime, D::Error>
where
D: Deserializer<'de>,
{
struct MimeVisitor;
impl<'de> Visitor<'de> for MimeVisitor {
type Value = Mime;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid MIME type")
}
fn visit_str<E>(self, value: &str) -> Result<Mime, E>
where
E: serde::de::Error,
{
Mime::from_str(value).map_err(E::custom)
}
}
deserializer.deserialize_str(MimeVisitor)
}
}