use async_trait::async_trait;
use std::time::Duration;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum TimeError {
#[error("operation timed out")]
Elapsed,
#[error("time provider shut down")]
Shutdown,
}
#[async_trait(?Send)]
pub trait TimeProvider: Clone {
async fn sleep(&self, duration: Duration) -> Result<(), TimeError>;
fn now(&self) -> Duration;
fn timer(&self) -> Duration;
async fn timeout<F, T>(&self, duration: Duration, future: F) -> Result<T, TimeError>
where
F: std::future::Future<Output = T>;
}
#[derive(Debug, Clone)]
pub struct TokioTimeProvider {
start_time: std::time::Instant,
}
impl TokioTimeProvider {
pub fn new() -> Self {
Self {
start_time: std::time::Instant::now(),
}
}
}
impl Default for TokioTimeProvider {
fn default() -> Self {
Self::new()
}
}
#[async_trait(?Send)]
impl TimeProvider for TokioTimeProvider {
async fn sleep(&self, duration: Duration) -> Result<(), TimeError> {
tokio::time::sleep(duration).await;
Ok(())
}
fn now(&self) -> Duration {
self.start_time.elapsed()
}
fn timer(&self) -> Duration {
self.now()
}
async fn timeout<F, T>(&self, duration: Duration, future: F) -> Result<T, TimeError>
where
F: std::future::Future<Output = T>,
{
match tokio::time::timeout(duration, future).await {
Ok(result) => Ok(result),
Err(_) => Err(TimeError::Elapsed),
}
}
}