use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::domain::TaskState;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EventEnvelope<E> {
pub aggregate_id: Uuid,
pub aggregate_seq: u64,
pub global_seq: u64,
pub event: E,
pub occurred_at: i64,
}
pub trait EventStorePort: Send + Sync {
type Error: std::error::Error + Send + Sync + 'static;
type Event: Serialize + for<'de> Deserialize<'de> + Clone + Send + Sync;
fn append(
&self,
aggregate_id: Uuid,
expected_seq: u64,
event: &Self::Event,
) -> Result<EventEnvelope<Self::Event>, Self::Error>;
fn load(&self, aggregate_id: Uuid) -> Result<Vec<EventEnvelope<Self::Event>>, Self::Error>;
}
pub trait Projection {
type State;
type Event;
fn initial() -> Self::State;
fn apply(state: Self::State, event: &Self::Event) -> Self::State;
}
pub fn replay<P: Projection>(events: &[P::Event]) -> P::State {
events
.iter()
.fold(P::initial(), |state, event| P::apply(state, event))
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum TaskLifecycleEvent {
Created {
prompt: String,
cwd: String,
},
Advanced {
to: TaskState,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TaskProjectionState {
pub id: Option<Uuid>,
pub prompt: String,
pub cwd: String,
pub state: TaskState,
}
pub struct TaskLifecycleProjection;
impl Projection for TaskLifecycleProjection {
type State = TaskProjectionState;
type Event = TaskLifecycleEvent;
fn initial() -> Self::State {
TaskProjectionState {
id: None,
prompt: String::new(),
cwd: String::new(),
state: TaskState::Submitted,
}
}
fn apply(mut state: Self::State, event: &Self::Event) -> Self::State {
match event {
TaskLifecycleEvent::Created { prompt, cwd } => {
state.prompt = prompt.clone();
state.cwd = cwd.clone();
state.state = TaskState::Submitted;
}
TaskLifecycleEvent::Advanced { to } => {
if TaskState::can_transition(state.state, *to) {
state.state = *to;
}
}
}
state
}
}
pub fn replay_task_state<S>(store: &S, aggregate_id: Uuid) -> Result<TaskProjectionState, S::Error>
where
S: EventStorePort<Event = TaskLifecycleEvent>,
{
let envelopes = store.load(aggregate_id)?;
let events: Vec<TaskLifecycleEvent> = envelopes.into_iter().map(|e| e.event).collect();
Ok(replay::<TaskLifecycleProjection>(&events))
}