Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify SerdeError #1458

Merged
merged 3 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 35 additions & 42 deletions src/datetime/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

use super::DateTime;
use crate::format::{write_rfc3339, SecondsFormat};
use crate::naive::datetime::serde::serde_from;
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, Offset, TimeZone, Utc};
Expand Down Expand Up @@ -148,10 +147,10 @@
use core::fmt;
use serde::{de, ser};

use crate::offset::TimeZone;
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::{serde_from, NanoSecondsTimestampVisitor};
use super::NanoSecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of nanoseconds since the epoch
///
Expand Down Expand Up @@ -240,24 +239,20 @@
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt(
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
),
&value,
DateTime::from_timestamp(
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
)
.ok_or_else(|| invalid_ts(value))
}

/// Deserialize a timestamp in nanoseconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32),
&value,
)
DateTime::from_timestamp((value / 1_000_000_000) as i64, (value % 1_000_000_000) as u32)
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -448,10 +443,11 @@
use core::fmt;
use serde::{de, ser};

use super::{serde_from, MicroSecondsTimestampVisitor};
use crate::offset::TimeZone;
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::MicroSecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of microseconds since the epoch
///
/// Intended for use with `serde`s `serialize_with` attribute.
Expand Down Expand Up @@ -529,24 +525,23 @@
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt(
value.div_euclid(1_000_000),
(value.rem_euclid(1_000_000) * 1_000) as u32,
),
&value,
DateTime::from_timestamp(
value.div_euclid(1_000_000),
(value.rem_euclid(1_000_000) * 1000) as u32,
)
.ok_or_else(|| invalid_ts(value))
}

/// Deserialize a timestamp in milliseconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt((value / 1_000_000) as i64, ((value % 1_000_000) * 1_000) as u32),
&value,
DateTime::from_timestamp(
(value / 1_000_000) as i64,
((value % 1_000_000) * 1_000) as u32,
)
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -726,10 +721,11 @@
use core::fmt;
use serde::{de, ser};

use super::{serde_from, MilliSecondsTimestampVisitor};
use crate::offset::TimeZone;
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::MilliSecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of milliseconds since the epoch
///
/// Intended for use with `serde`s `serialize_with` attribute.
Expand Down Expand Up @@ -807,18 +803,16 @@
where
E: de::Error,
{
serde_from(Utc.timestamp_millis_opt(value), &value)
DateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value))
}

/// Deserialize a timestamp in milliseconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
Utc.timestamp_opt((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32),
&value,
)
DateTime::from_timestamp((value / 1000) as i64, ((value % 1000) * 1_000_000) as u32)
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -1005,8 +999,10 @@
use core::fmt;
use serde::{de, ser};

use super::{serde_from, SecondsTimestampVisitor};
use crate::{DateTime, LocalResult, TimeZone, Utc};
use crate::serde::invalid_ts;
use crate::{DateTime, Utc};

use super::SecondsTimestampVisitor;

/// Serialize a UTC datetime into an integer number of seconds since the epoch
///
Expand Down Expand Up @@ -1075,22 +1071,19 @@
where
E: de::Error,
{
serde_from(Utc.timestamp_opt(value, 0), &value)
DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value))

Check warning on line 1074 in src/datetime/serde.rs

View check run for this annotation

Codecov / codecov/patch

src/datetime/serde.rs#L1074

Added line #L1074 was not covered by tests
}

/// Deserialize a timestamp in seconds since the epoch
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
serde_from(
if value > i64::MAX as u64 {
LocalResult::None
} else {
Utc.timestamp_opt(value as i64, 0)
},
&value,
)
if value > i64::MAX as u64 {
Err(invalid_ts(value))

Check warning on line 1083 in src/datetime/serde.rs

View check run for this annotation

Codecov / codecov/patch

src/datetime/serde.rs#L1083

Added line #L1083 was not covered by tests
} else {
DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value))
}
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,33 @@
/// [2]: https://serde.rs/field-attrs.html#with
#[cfg(feature = "serde")]
pub mod serde {
use core::fmt;
use serde::de;

pub use super::datetime::serde::*;

/// Create a custom `de::Error` with `SerdeError::InvalidTimestamp`.
pub(crate) fn invalid_ts<E, T>(value: T) -> E
where
E: de::Error,
T: fmt::Display,
{
E::custom(SerdeError::InvalidTimestamp(value))
}

enum SerdeError<T: fmt::Display> {
InvalidTimestamp(T),
}

Check warning on line 644 in src/lib.rs

View check run for this annotation

Codecov / codecov/patch

src/lib.rs#L638-L644

Added lines #L638 - L644 were not covered by tests
impl<T: fmt::Display> fmt::Display for SerdeError<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SerdeError::InvalidTimestamp(ts) => {
write!(f, "value is not a legal timestamp: {}", ts)
}
}
}
}

Check warning on line 653 in src/lib.rs

View check run for this annotation

Codecov / codecov/patch

src/lib.rs#L651-L653

Added lines #L651 - L653 were not covered by tests
}

/// Zero-copy serialization/deserialization with rkyv.
Expand Down
81 changes: 16 additions & 65 deletions src/naive/datetime/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use serde::{de, ser};

use super::NaiveDateTime;
use crate::offset::LocalResult;

/// Serialize a `NaiveDateTime` as an RFC 3339 string
///
Expand Down Expand Up @@ -83,7 +82,7 @@
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of nanoseconds since the epoch
Expand Down Expand Up @@ -176,7 +175,7 @@
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
Expand All @@ -187,7 +186,7 @@
(value / 1_000_000_000) as i64,
(value % 1_000_000_000) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -372,7 +371,7 @@
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of microseconds since the epoch
Expand Down Expand Up @@ -451,8 +450,7 @@
where
E: de::Error,
{
NaiveDateTime::from_timestamp_micros(value)
.ok_or_else(|| E::custom(ne_timestamp(value)))
NaiveDateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
Expand All @@ -463,7 +461,7 @@
(value / 1_000_000) as i64,
((value % 1_000_000) * 1_000) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -636,7 +634,7 @@
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of milliseconds since the epoch
Expand Down Expand Up @@ -715,8 +713,7 @@
where
E: de::Error,
{
NaiveDateTime::from_timestamp_millis(value)
.ok_or_else(|| E::custom(ne_timestamp(value)))
NaiveDateTime::from_timestamp_millis(value).ok_or_else(|| invalid_ts(value))
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
Expand All @@ -727,7 +724,7 @@
(value / 1000) as i64,
((value % 1000) * 1_000_000) as u32,
)
.ok_or_else(|| E::custom(ne_timestamp(value)))
.ok_or_else(|| invalid_ts(value))
}
}
}
Expand Down Expand Up @@ -896,7 +893,7 @@
use core::fmt;
use serde::{de, ser};

use super::ne_timestamp;
use crate::serde::invalid_ts;
use crate::NaiveDateTime;

/// Serialize a datetime into an integer number of seconds since the epoch
Expand Down Expand Up @@ -968,16 +965,18 @@
where
E: de::Error,
{
NaiveDateTime::from_timestamp_opt(value, 0)
.ok_or_else(|| E::custom(ne_timestamp(value)))
NaiveDateTime::from_timestamp_opt(value, 0).ok_or_else(|| invalid_ts(value))

Check warning on line 968 in src/naive/datetime/serde.rs

View check run for this annotation

Codecov / codecov/patch

src/naive/datetime/serde.rs#L968

Added line #L968 was not covered by tests
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
NaiveDateTime::from_timestamp_opt(value as i64, 0)
.ok_or_else(|| E::custom(ne_timestamp(value)))
if value > i64::MAX as u64 {
Err(invalid_ts(value))

Check warning on line 976 in src/naive/datetime/serde.rs

View check run for this annotation

Codecov / codecov/patch

src/naive/datetime/serde.rs#L976

Added line #L976 was not covered by tests
} else {
NaiveDateTime::from_timestamp_opt(value as i64, 0).ok_or_else(|| invalid_ts(value))
}
}
}
}
Expand Down Expand Up @@ -1109,54 +1108,6 @@
}
}

// lik? function to convert a LocalResult into a serde-ish Result
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(ne_timestamp(ts))),
LocalResult::Ambiguous(min, max) => {
Err(E::custom(SerdeError::Ambiguous { timestamp: ts, min, max }))
}
LocalResult::Single(val) => Ok(val),
}
}

enum SerdeError<V: fmt::Display, D: fmt::Display> {
NonExistent { timestamp: V },
Ambiguous { timestamp: V, min: D, max: D },
}

/// Construct a [`SerdeError::NonExistent`]
fn ne_timestamp<T: fmt::Display>(ts: T) -> SerdeError<T, u8> {
SerdeError::NonExistent::<T, u8> { timestamp: ts }
}

impl<V: fmt::Display, D: fmt::Display> fmt::Debug for SerdeError<V, D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ChronoSerdeError({})", self)
}
}

// impl<V: fmt::Display, D: fmt::Debug> core::error::Error for SerdeError<V, D> {}
impl<V: fmt::Display, D: fmt::Display> fmt::Display for SerdeError<V, D> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SerdeError::NonExistent { timestamp } => {
write!(f, "value is not a legal timestamp: {}", timestamp)
}
SerdeError::Ambiguous { timestamp, min, max } => write!(
f,
"value is an ambiguous timestamp: {}, could be either of {}, {}",
timestamp, min, max
),
}
}
}

#[cfg(test)]
mod tests {
use crate::naive::datetime::{test_decodable_json, test_encodable_json};
Expand Down
Loading