use std::{sync::Arc, time::Instant};
use serde_json::Value;
use crate::{approve::PromptHost, stop::Stop};
pub trait LiveSink: Send + Sync {
fn on_event(&self, event: &Value);
}
#[derive(Clone, Default)]
pub(crate) struct DriveContext {
pub(crate) live: Option<Arc<dyn LiveSink>>,
pub(crate) prompt_host: Option<Arc<dyn PromptHost>>,
waiter_deadline: Option<Instant>,
stop: Option<Stop>,
}
impl DriveContext {
pub(crate) fn new(
live: Option<Arc<dyn LiveSink>>,
prompt_host: Option<Arc<dyn PromptHost>>,
) -> Self {
Self {
live,
prompt_host,
waiter_deadline: None,
stop: None,
}
}
#[must_use]
pub(crate) fn stopping_on(self, stop: Option<Stop>) -> Self {
Self { stop, ..self }
}
pub(crate) fn is_stopping(&self) -> bool {
self.stop.as_ref().is_some_and(Stop::is_stopping)
}
pub(crate) async fn stopping(&self) {
match &self.stop {
Some(stop) => stop.stopping().await,
None => std::future::pending().await,
}
}
#[must_use]
pub(crate) fn until(self, waiter_deadline: Option<Instant>) -> Self {
Self {
waiter_deadline,
..self
}
}
pub(crate) fn hidden(&self) -> Self {
Self {
live: None,
prompt_host: self.prompt_host.clone(),
waiter_deadline: None,
stop: self.stop.clone(),
}
}
pub(crate) fn waiter_expired(&self) -> bool {
self.waiter_deadline
.is_some_and(|deadline| Instant::now() >= deadline)
}
pub(crate) fn show(&self, event: &Value) {
if let Some(live) = &self.live {
live.on_event(event);
}
}
pub(crate) fn can_ask(&self) -> bool {
self.prompt_host.as_deref().is_some_and(PromptHost::can_ask)
}
pub(crate) fn approver(&self) -> Option<Box<dyn basis::Approver>> {
self.prompt_host.as_deref().map(PromptHost::approver)
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, Instant};
use super::DriveContext;
#[test]
fn a_context_without_a_waiter_deadline_never_expires() {
assert!(!DriveContext::default().waiter_expired());
assert!(!DriveContext::default().until(None).waiter_expired());
}
#[test]
fn a_waiter_deadline_expires_at_and_after_the_instant() {
let now = Instant::now();
assert!(DriveContext::default().until(Some(now)).waiter_expired());
assert!(
!DriveContext::default()
.until(Some(now + Duration::from_secs(3600)))
.waiter_expired()
);
}
#[test]
fn hidden_drops_the_waiter_deadline_with_the_sink() {
let bounded = DriveContext::default().until(Some(Instant::now()));
assert!(bounded.waiter_expired());
assert!(!bounded.hidden().waiter_expired());
}
}