use crate::google::protobuf::Duration;
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum DurationJiffError {
#[error("nanos field has invalid value or sign mismatch with seconds")]
InvalidNanos,
}
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl From<jiff::SignedDuration> for Duration {
fn from(d: jiff::SignedDuration) -> Self {
Self {
seconds: d.as_secs(),
nanos: d.subsec_nanos(),
..Default::default()
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl TryFrom<Duration> for jiff::SignedDuration {
type Error = DurationJiffError;
fn try_from(d: Duration) -> Result<Self, Self::Error> {
if !(-999_999_999..=999_999_999).contains(&d.nanos) {
return Err(DurationJiffError::InvalidNanos);
}
let sign_mismatch = (d.seconds > 0 && d.nanos < 0) || (d.seconds < 0 && d.nanos > 0);
if sign_mismatch {
return Err(DurationJiffError::InvalidNanos);
}
Ok(jiff::SignedDuration::new(d.seconds, d.nanos))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn positive_roundtrip() {
let sd = jiff::SignedDuration::new(300, 500_000_000);
let proto: Duration = sd.into();
assert_eq!(proto.seconds, 300);
assert_eq!(proto.nanos, 500_000_000);
let back: jiff::SignedDuration = proto.try_into().unwrap();
assert_eq!(back, sd);
}
#[test]
fn zero_roundtrip() {
let sd = jiff::SignedDuration::ZERO;
let proto: Duration = sd.into();
assert_eq!(proto.seconds, 0);
assert_eq!(proto.nanos, 0);
let back: jiff::SignedDuration = proto.try_into().unwrap();
assert_eq!(back, sd);
}
#[test]
fn negative_roundtrip() {
let sd = jiff::SignedDuration::new(-1, -500_000_000);
let proto: Duration = sd.into();
assert_eq!(proto.seconds, -1);
assert_eq!(proto.nanos, -500_000_000);
let back: jiff::SignedDuration = proto.try_into().unwrap();
assert_eq!(back, sd);
}
#[test]
fn sub_second_negative_roundtrip() {
let sd = jiff::SignedDuration::new(0, -500_000_000);
let proto: Duration = sd.into();
assert_eq!(proto.seconds, 0);
assert_eq!(proto.nanos, -500_000_000);
let back: jiff::SignedDuration = proto.try_into().unwrap();
assert_eq!(back, sd);
}
#[test]
fn invalid_nanos_rejected() {
let bad = Duration {
seconds: 1,
nanos: 1_000_000_000,
..Default::default()
};
let r: Result<jiff::SignedDuration, _> = bad.try_into();
assert_eq!(r, Err(DurationJiffError::InvalidNanos));
}
#[test]
fn nanos_i32_min_is_invalid() {
let bad = Duration {
seconds: 0,
nanos: i32::MIN,
..Default::default()
};
let r: Result<jiff::SignedDuration, _> = bad.try_into();
assert_eq!(r, Err(DurationJiffError::InvalidNanos));
}
#[test]
fn sign_mismatch_rejected() {
let bad = Duration {
seconds: 5,
nanos: -1,
..Default::default()
};
let r: Result<jiff::SignedDuration, _> = bad.try_into();
assert_eq!(r, Err(DurationJiffError::InvalidNanos));
let bad2 = Duration {
seconds: -5,
nanos: 1,
..Default::default()
};
let r2: Result<jiff::SignedDuration, _> = bad2.try_into();
assert_eq!(r2, Err(DurationJiffError::InvalidNanos));
}
#[test]
fn signed_duration_extremes_roundtrip() {
for sd in [jiff::SignedDuration::MAX, jiff::SignedDuration::MIN] {
let proto: Duration = sd.into();
assert_eq!(proto.seconds, sd.as_secs());
assert_eq!(proto.nanos, sd.subsec_nanos());
let back: jiff::SignedDuration = proto.try_into().unwrap();
assert_eq!(back, sd);
}
}
}