use chrono::{DateTime, TimeDelta, Utc};
use futures_util::stream;
use serde_json::{Value, json};
use tokio_util::sync::CancellationToken;
use crate::{
Events, Projection, ProjectionError, Revision, Subject, TransactionalProjection,
projection::MADE_UP_SOURCE as SOURCE,
store::projecting::{RebuildWriter, TransactionalWriter, Writer, drive},
};
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct StoredEvent {
pub id: Revision,
pub time: DateTime<Utc>,
pub source: String,
pub subject: Subject,
pub event_type: String,
pub data: Value,
pub trace_parent: Option<String>,
pub trace_state: Option<String>,
}
impl StoredEvent {
#[must_use]
#[track_caller]
pub fn new<E: Events>(subject: Subject, id: impl Into<Revision>, event: &E) -> Self {
let data = match event.to_data() {
Ok(data) => data,
Err(error) => panic!(
"architecturekit: StoredEvent::new can not encode an event of type {:?}: {error}",
event.event_type(),
),
};
Self {
id: id.into(),
time: DateTime::UNIX_EPOCH,
source: SOURCE.to_owned(),
subject,
event_type: event.event_type().to_owned(),
data,
trace_parent: None,
trace_state: None,
}
}
}
impl From<StoredEvent> for eventsourcingdb::Event {
fn from(stored: StoredEvent) -> Self {
let mut event = json!({
"specversion": "1.0",
"id": stored.id.to_string(),
"time": stored.time,
"source": stored.source,
"subject": stored.subject.as_str(),
"type": stored.event_type,
"datacontenttype": "application/json",
"data": stored.data,
"hash": "0".repeat(64),
"predecessorhash": "0".repeat(64),
});
if let Some(trace_parent) = stored.trace_parent {
event["traceparent"] = Value::String(trace_parent);
}
if let Some(trace_state) = stored.trace_state {
event["tracestate"] = Value::String(trace_state);
}
serde_json::from_value(event)
.expect("the client SDK reads every event of this shape, whatever its values")
}
}
#[must_use]
#[track_caller]
pub fn stored_events<E: Events>(
subject: &Subject,
events: impl IntoIterator<Item = E>,
) -> Vec<StoredEvent> {
events
.into_iter()
.zip(0_u32..)
.map(|(event, id)| StoredEvent::new(subject.clone(), id, &event))
.collect()
}
#[must_use]
#[track_caller]
pub fn stored_events_at<E: Events>(
subject: &Subject,
first: impl Into<Revision>,
at: DateTime<Utc>,
events: impl IntoIterator<Item = E>,
) -> Vec<StoredEvent> {
let first = first.into();
events
.into_iter()
.zip(0_u32..)
.map(|(event, offset)| {
let Ok(id) = Revision::try_from(first.get() + u64::from(offset)) else {
panic!(
"architecturekit: stored_events_at numbers the events from {first} on beyond the largest revision"
);
};
let mut stored = StoredEvent::new(subject.clone(), id, &event);
stored.time = at + TimeDelta::minutes(i64::from(offset));
stored
})
.collect()
}
pub async fn project<P: Projection>(
projection: &P,
events: &[StoredEvent],
) -> Result<(), ProjectionError> {
drive_through(&mut RebuildWriter::new(projection), events, 1).await
}
pub async fn project_transactional<P: TransactionalProjection>(
projection: &P,
events: &[StoredEvent],
) -> Result<(), ProjectionError> {
drive_through(
&mut TransactionalWriter::new(projection),
events,
events.len(),
)
.await
}
async fn drive_through<W: Writer>(
writer: &mut W,
events: &[StoredEvent],
batch_size: usize,
) -> Result<(), ProjectionError> {
let events = events.iter().cloned().map(|event| Ok(event.into()));
drive(
writer,
stream::iter(events),
|_| Ok(()),
batch_size,
&CancellationToken::new(),
|_| {},
)
.await
.1
}