#![forbid(unsafe_code)]
use enum_dispatch::enum_dispatch;
#[cfg(any(test, feature = "async"))]
use pin_project::pin_project;
use std::{
fmt::Debug,
time::{Duration, Instant},
};
#[cfg(any(test, feature = "async"))]
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
#[cfg(any(test, feature = "async"))]
pub mod interval;
#[cfg(any(test, feature = "testing"))]
pub mod mock;
pub mod real;
#[cfg(any(test, feature = "async"))]
pub mod timeout;
#[cfg(any(test, feature = "testing"))]
pub use crate::mock::{MockSleep, MockTimeService};
pub use crate::real::RealTimeService;
#[cfg(any(test, feature = "async"))]
pub use crate::{interval::Interval, real::RealSleep, timeout::Timeout};
#[cfg(any(test, feature = "async"))]
const ZERO_DURATION: Duration = Duration::from_nanos(0);
#[enum_dispatch(TimeServiceTrait)]
#[derive(Clone, Debug)]
pub enum TimeService {
RealTimeService(RealTimeService),
#[cfg(any(test, feature = "testing"))]
MockTimeService(MockTimeService),
}
impl TimeService {
pub fn real() -> Self {
RealTimeService::new().into()
}
#[cfg(any(test, feature = "testing"))]
pub fn mock() -> Self {
MockTimeService::new().into()
}
#[cfg(any(test, feature = "testing"))]
pub fn into_mock(self) -> MockTimeService {
match self {
TimeService::MockTimeService(inner) => inner,
ts => panic!("Unexpected TimeService, expected MockTimeService: {:?}", ts),
}
}
}
impl Default for TimeService {
fn default() -> Self {
Self::real()
}
}
#[enum_dispatch]
pub trait TimeServiceTrait: Send + Sync + Clone + Debug {
fn now(&self) -> Instant;
fn now_unix_time(&self) -> Duration;
fn now_secs(&self) -> u64 {
self.now_unix_time().as_secs()
}
#[cfg(any(test, feature = "async"))]
fn sleep(&self, duration: Duration) -> Sleep;
#[cfg(any(test, feature = "async"))]
fn sleep_until(&self, deadline: Instant) -> Sleep {
let duration = deadline.saturating_duration_since(self.now());
self.sleep(duration)
}
fn sleep_blocking(&self, duration: Duration);
#[cfg(any(test, feature = "async"))]
fn interval(&self, period: Duration) -> Interval {
let delay = self.sleep(ZERO_DURATION);
Interval::new(delay, period)
}
#[cfg(any(test, feature = "async"))]
fn interval_at(&self, start: Instant, period: Duration) -> Interval {
let delay = self.sleep_until(start);
Interval::new(delay, period)
}
#[cfg(any(test, feature = "async"))]
fn timeout<F: Future>(&self, duration: Duration, future: F) -> Timeout<F> {
let delay = self.sleep(duration);
Timeout::new(future, delay)
}
#[cfg(any(test, feature = "async"))]
fn timeout_at<F: Future>(&self, deadline: Instant, future: F) -> Timeout<F> {
let delay = self.sleep_until(deadline);
Timeout::new(future, delay)
}
}
#[pin_project(project = SleepProject)]
#[derive(Debug)]
#[cfg(any(test, feature = "async"))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub enum Sleep {
RealSleep(#[pin] RealSleep),
#[cfg(any(test, feature = "testing"))]
MockSleep(MockSleep),
}
#[cfg(any(test, feature = "async"))]
impl From<RealSleep> for Sleep {
fn from(sleep: RealSleep) -> Self {
Sleep::RealSleep(sleep)
}
}
#[cfg(any(test, feature = "fuzzing", feature = "testing"))]
impl From<MockSleep> for Sleep {
fn from(sleep: MockSleep) -> Self {
Sleep::MockSleep(sleep)
}
}
#[cfg(any(test, feature = "async"))]
impl Future for Sleep {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
SleepProject::RealSleep(inner) => inner.poll(cx),
#[cfg(any(test, feature = "testing"))]
SleepProject::MockSleep(inner) => Pin::new(inner).poll(cx),
}
}
}
#[cfg(any(test, feature = "async"))]
pub trait SleepTrait: Future<Output = ()> + Send + Sync + Debug {
fn is_elapsed(&self) -> bool;
fn reset(self: Pin<&mut Self>, duration: Duration);
fn reset_until(self: Pin<&mut Self>, deadline: Instant);
}
#[cfg(any(test, feature = "async"))]
impl SleepTrait for Sleep {
fn is_elapsed(&self) -> bool {
match self {
Sleep::RealSleep(inner) => SleepTrait::is_elapsed(inner),
#[cfg(any(test, feature = "fuzzing", feature = "testing"))]
Sleep::MockSleep(inner) => SleepTrait::is_elapsed(inner),
}
}
fn reset(self: Pin<&mut Self>, duration: Duration) {
match self.project() {
SleepProject::RealSleep(inner) => SleepTrait::reset(inner, duration),
#[cfg(any(test, feature = "fuzzing", feature = "testing"))]
SleepProject::MockSleep(inner) => SleepTrait::reset(Pin::new(inner), duration),
}
}
fn reset_until(self: Pin<&mut Self>, deadline: Instant) {
match self.project() {
SleepProject::RealSleep(inner) => SleepTrait::reset_until(inner, deadline),
#[cfg(any(test, feature = "fuzzing", feature = "testing"))]
SleepProject::MockSleep(inner) => SleepTrait::reset_until(Pin::new(inner), deadline),
}
}
}