use chrono::{DateTime, TimeDelta, Utc};
use futures_util::stream;
use serde_json::{Value, json};
use tokio_util::sync::CancellationToken;
use crate::{
Events, Projection, ProjectionError, TransactionalProjection,
store::projecting::{RebuildWriter, TransactionalWriter, Writer, drive},
};
#[cfg(test)]
mod tests;
const SOURCE: &str = "https://architecturekit.test";
#[derive(Clone, Debug, PartialEq)]
pub struct StoredEvent {
pub id: String,
pub time: DateTime<Utc>,
pub source: String,
pub subject: String,
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: &str, id: &str, 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.to_owned(),
time: DateTime::UNIX_EPOCH,
source: SOURCE.to_owned(),
subject: subject.to_owned(),
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,
"time": stored.time,
"source": stored.source,
"subject": stored.subject,
"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: &str,
events: impl IntoIterator<Item = E>,
) -> Vec<StoredEvent> {
events
.into_iter()
.enumerate()
.map(|(id, event)| StoredEvent::new(subject, &id.to_string(), &event))
.collect()
}
#[must_use]
#[track_caller]
pub fn stored_events_at<E: Events>(
subject: &str,
first_id: u64,
at: DateTime<Utc>,
events: impl IntoIterator<Item = E>,
) -> Vec<StoredEvent> {
events
.into_iter()
.zip(0_u32..)
.map(|(event, offset)| {
let id = first_id + u64::from(offset);
let mut stored = StoredEvent::new(subject, &id.to_string(), &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
}