use std::{collections::HashMap, error::Error, fmt};
use chrono::{DateTime, Utc};
use serde_json::Value;
use crate::{Failure, Permanent};
#[cfg(test)]
mod tests;
const MAX_UPCAST_STEPS: usize = 16;
#[derive(Clone, Debug, PartialEq)]
pub struct UpcastEvent {
event_type: String,
data: Value,
subject: String,
id: String,
time: DateTime<Utc>,
source: String,
}
impl UpcastEvent {
#[must_use]
pub fn new(event_type: impl Into<String>, data: Value) -> Self {
Self {
event_type: event_type.into(),
data,
subject: String::new(),
id: String::new(),
time: DateTime::UNIX_EPOCH,
source: String::new(),
}
}
pub(crate) fn from_stored(event: &eventsourcingdb::Event) -> Self {
Self {
event_type: event.ty().to_owned(),
data: event.data().clone(),
subject: event.subject().to_owned(),
id: event.id().to_owned(),
time: *event.time(),
source: event.source().to_owned(),
}
}
#[must_use]
pub fn with_type(mut self, event_type: impl Into<String>) -> Self {
self.event_type = event_type.into();
self
}
#[must_use]
pub fn with_data(mut self, data: Value) -> Self {
self.data = data;
self
}
#[must_use]
pub fn event_type(&self) -> &str {
&self.event_type
}
#[must_use]
pub fn data(&self) -> &Value {
&self.data
}
#[must_use]
pub fn into_data(self) -> Value {
self.data
}
#[must_use]
pub fn subject(&self) -> &str {
&self.subject
}
#[must_use]
pub fn id(&self) -> &str {
&self.id
}
#[must_use]
pub fn time(&self) -> &DateTime<Utc> {
&self.time
}
#[must_use]
pub fn source(&self) -> &str {
&self.source
}
}
type Upcaster = Box<
dyn Fn(UpcastEvent) -> Result<Vec<UpcastEvent>, Box<dyn Error + Send + Sync + 'static>>
+ Send
+ Sync,
>;
#[derive(Default)]
pub struct Upcasters {
by_type: HashMap<String, Upcaster>,
}
impl Upcasters {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn upcast<F, E>(mut self, from: impl Into<String>, upcast: F) -> Self
where
F: Fn(UpcastEvent) -> Result<Vec<UpcastEvent>, E> + Send + Sync + 'static,
E: Into<Box<dyn Error + Send + Sync + 'static>>,
{
let from = from.into();
assert!(
!self.by_type.contains_key(&from),
"architecturekit: event type {from:?} already has an upcaster",
);
let upcaster: Upcaster = Box::new(move |event| upcast(event).map_err(Into::into));
let _ = self.by_type.insert(from, upcaster);
self
}
pub(crate) fn apply(&self, event: UpcastEvent) -> Result<Vec<UpcastEvent>, Permanent> {
self.apply_with_budget(event, MAX_UPCAST_STEPS)
}
fn apply_with_budget(
&self,
event: UpcastEvent,
budget: usize,
) -> Result<Vec<UpcastEvent>, Permanent> {
let Some(upcast) = self.by_type.get(&event.event_type) else {
return Ok(vec![event]);
};
if budget == 0 {
return Err(Permanent::Other(Failure::new(
format!(
"upcasting {:?} exceeded {MAX_UPCAST_STEPS} steps, check for an upcaster that keeps its event type",
event.event_type,
),
None,
)));
}
let event_type = event.event_type.clone();
let produced = upcast(event).map_err(|cause| {
Permanent::Other(Failure::new(
format!("upcasting {event_type:?}"),
Some(cause),
))
})?;
let mut result = Vec::new();
for next in produced {
result.extend(self.apply_with_budget(next, budget - 1)?);
}
Ok(result)
}
}
impl fmt::Debug for Upcasters {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut event_types: Vec<&String> = self.by_type.keys().collect();
event_types.sort();
formatter
.debug_struct("Upcasters")
.field("event_types", &event_types)
.finish()
}
}