use std::fmt::{self, Debug, Formatter};
use std::sync::{Arc, Mutex};
use wyrd_spec::error::WyrdError;
use crate::bifrost::{Bifrost, WriterTable};
pub const DRIFT_OBSERVATIONS_TABLE: &str = "vala.drift.observations";
pub struct StartedBifrost {
pub bifrost: Bifrost,
pub drift: WriterTable,
}
enum Phase {
NotStarted,
Starting,
Started(Arc<StartedBifrost>),
Closed,
}
pub struct BifrostLifecycle {
phase: Mutex<Phase>,
}
impl Default for BifrostLifecycle {
fn default() -> Self {
Self {
phase: Mutex::new(Phase::NotStarted),
}
}
}
impl Debug for BifrostLifecycle {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let phase = match &*self.phase.lock().expect("bifrost phase lock poisoned") {
Phase::NotStarted => "not_started",
Phase::Starting => "starting",
Phase::Started(_) => "started",
Phase::Closed => "closed",
};
f.debug_struct("BifrostLifecycle")
.field("phase", &phase)
.finish()
}
}
impl BifrostLifecycle {
pub fn claim(&self) -> Result<StartClaim<'_>, WyrdError> {
let mut phase = self.phase.lock().expect("bifrost phase lock poisoned");
match &*phase {
Phase::NotStarted => {
*phase = Phase::Starting;
Ok(StartClaim { lifecycle: self })
}
Phase::Starting | Phase::Started(_) => Err(already_started()),
Phase::Closed => Err(closed()),
}
}
pub fn started(&self) -> Result<Arc<StartedBifrost>, WyrdError> {
match &*self.phase.lock().expect("bifrost phase lock poisoned") {
Phase::Started(started) => Ok(Arc::clone(started)),
Phase::NotStarted | Phase::Starting => Err(not_started()),
Phase::Closed => Err(closed()),
}
}
pub async fn shutdown(&self) -> Result<(), WyrdError> {
let started = {
let mut phase = self.phase.lock().expect("bifrost phase lock poisoned");
match &*phase {
Phase::Started(started) => Arc::clone(started),
Phase::NotStarted | Phase::Starting => {
*phase = Phase::Closed;
return Ok(());
}
Phase::Closed => return Ok(()),
}
};
started.bifrost.shutdown().await?;
*self.phase.lock().expect("bifrost phase lock poisoned") = Phase::Closed;
Ok(())
}
}
pub struct StartClaim<'a> {
lifecycle: &'a BifrostLifecycle,
}
impl StartClaim<'_> {
pub async fn complete(self, bifrost: Bifrost) -> Result<(), WyrdError> {
let drift = bifrost
.resolve_writer_table(DRIFT_OBSERVATIONS_TABLE)
.await?;
crate::observe::drift::require_projection_columns(&drift)?;
let started = Arc::new(StartedBifrost { bifrost, drift });
let mut phase = self
.lifecycle
.phase
.lock()
.expect("bifrost phase lock poisoned");
match &*phase {
Phase::Starting => {
*phase = Phase::Started(started);
Ok(())
}
_ => Err(closed()),
}
}
}
impl Drop for StartClaim<'_> {
fn drop(&mut self) {
if let Ok(mut phase) = self.lifecycle.phase.lock()
&& matches!(*phase, Phase::Starting)
{
*phase = Phase::NotStarted;
}
}
}
fn already_started() -> WyrdError {
WyrdError::SdkBifrostAlreadyStarted {
message: "this WyrdState already started Bifrost".to_owned(),
details: serde_json::Value::Null,
}
}
fn not_started() -> WyrdError {
WyrdError::SdkBifrostNotStarted {
message: "call start_bifrost before observing".to_owned(),
details: serde_json::Value::Null,
}
}
fn closed() -> WyrdError {
WyrdError::SdkBifrostClosed {
message: "this WyrdState was shut down; create a new state to start again".to_owned(),
details: serde_json::Value::Null,
}
}