use std::{sync::Arc, time::Instant};
use basis::{RuntimeBuilder, runtime::GatewayRingEvent};
use serde_json::Value;
use crate::{approve::PromptHost, stop::Stop};
pub(crate) type GatewayObserver = Arc<dyn Fn(GatewayRingEvent) + Send + Sync>;
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>>,
gateway_observer: Option<GatewayObserver>,
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,
gateway_observer: None,
waiter_deadline: None,
stop: None,
}
}
#[must_use]
pub(crate) fn observing_gateway_ring(self, observer: Option<GatewayObserver>) -> Self {
Self {
gateway_observer: observer,
..self
}
}
pub(crate) fn observe_gateway_ring(&self, runtime: RuntimeBuilder) -> RuntimeBuilder {
match &self.gateway_observer {
Some(observer) => {
let observer = Arc::clone(observer);
runtime.with_gateway_ring_observer(move |event| observer(event))
}
None => runtime,
}
}
#[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(),
gateway_observer: self.gateway_observer.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());
}
}
#[cfg(test)]
mod gateway_tests {
use super::*;
use basis::runtime::{GatewayMemberRef, GatewayRingEvent};
use std::sync::Mutex;
#[test]
fn a_child_driven_quietly_keeps_the_process_gateway_observer() {
let events = Arc::new(Mutex::new(Vec::new()));
let seen = Arc::clone(&events);
let context =
DriveContext::default().observing_gateway_ring(Some(Arc::new(move |event| {
seen.lock().unwrap().push(event);
})));
let child = context.hidden();
let event = GatewayRingEvent::Recovered {
member: GatewayMemberRef {
index: 0,
label: "preferred".to_string(),
},
};
child
.gateway_observer
.as_ref()
.expect("child keeps gateway observer")(event.clone());
assert_eq!(*events.lock().unwrap(), [event]);
}
}