Skip to content

Commit

Permalink
feat(prost-types): Implement conversion Duration to/from `chrono::T…
Browse files Browse the repository at this point in the history
…imeDelta` (#1236)
  • Loading branch information
caspermeijn authored Feb 12, 2025
1 parent a2e8f57 commit 87c22b4
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ jobs:
uses: model-checking/[email protected]
with:
args: |
-p prost-types
-p prost-types --features chrono
no-std:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions prost-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ arbitrary = ["dep:arbitrary"]
[dependencies]
prost = { version = "0.13.5", path = "../prost", default-features = false, features = ["prost-derive"] }
arbitrary = { version = "1.4", features = ["derive"], optional = true }
chrono = { version = "0.4.34", default-features = false, optional = true }

[dev-dependencies]
proptest = "1"
Expand Down
58 changes: 56 additions & 2 deletions prost-types/src/duration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,42 @@ impl FromStr for Duration {
}
}

#[cfg(feature = "chrono")]
mod chrono {
use ::chrono::TimeDelta;

use super::*;

impl From<::chrono::TimeDelta> for Duration {
fn from(value: ::chrono::TimeDelta) -> Self {
let mut result = Self {
seconds: value.num_seconds(),
nanos: value.subsec_nanos(),
};
result.normalize();
result
}
}

impl TryFrom<Duration> for ::chrono::TimeDelta {
type Error = DurationError;

fn try_from(mut value: Duration) -> Result<TimeDelta, duration::DurationError> {
value.normalize();
let seconds = TimeDelta::try_seconds(value.seconds).ok_or(DurationError::OutOfRange)?;
let nanos = TimeDelta::nanoseconds(value.nanos.into());
seconds.checked_add(&nanos).ok_or(DurationError::OutOfRange)
}
}
}

#[cfg(kani)]
mod proofs {
use super::*;

#[cfg(feature = "std")]
#[kani::proof]
fn check_duration_roundtrip() {
fn check_duration_std_roundtrip() {
let seconds = kani::any();
let nanos = kani::any();
kani::assume(nanos < 1_000_000_000);
Expand Down Expand Up @@ -218,7 +247,7 @@ mod proofs {

#[cfg(feature = "std")]
#[kani::proof]
fn check_duration_roundtrip_nanos() {
fn check_duration_std_roundtrip_nanos() {
let seconds = 0;
let nanos = kani::any();
let std_duration = std::time::Duration::new(seconds, nanos);
Expand All @@ -243,6 +272,31 @@ mod proofs {
))
}
}

#[cfg(feature = "chrono")]
#[kani::proof]
fn check_duration_chrono_roundtrip() {
let seconds = kani::any();
let nanos = kani::any();
let prost_duration = Duration { seconds, nanos };
match ::chrono::TimeDelta::try_from(prost_duration) {
Err(DurationError::OutOfRange) => {
// Test case not valid: duration out of range
return;
}
Err(err) => {
panic!("Unexpected error: {err}")
}
Ok(chrono_duration) => {
let mut normalized_prost_duration = prost_duration;
normalized_prost_duration.normalize();
assert_eq!(
Duration::try_from(chrono_duration).unwrap(),
normalized_prost_duration
);
}
}
}
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions prost-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
//! ## Feature Flags
//! - `std`: Enable integration with standard library. Disable this feature for `no_std` support. This feature is enabled by default.
//! - `arbitrary`: Enable integration with crate `arbitrary`. All types on this crate will implement `trait Arbitrary`.
//! - `chrono`: Enable integration with crate `chrono`. Time related types implement conversions to/from their `chrono` equivalent.
//!
//! [1]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf
Expand Down

0 comments on commit 87c22b4

Please sign in to comment.