#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod aggregator;
pub mod context;
pub mod cursor;
mod executor;
pub mod metadata;
pub mod projection;
pub mod subscription;
#[cfg(feature = "macro")]
pub use evento_macro::*;
pub use aggregator::*;
pub use executor::*;
pub use subscription::RoutingKey;
use std::fmt::Debug;
use ulid::Ulid;
use crate::{cursor::Cursor, metadata::Metadata};
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct EventCursor {
pub i: String,
pub v: u16,
pub t: u64,
pub s: u32,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Event {
pub id: Ulid,
pub aggregate_id: String,
pub aggregate_type: String,
pub version: u16,
pub name: String,
pub routing_key: Option<String>,
pub data: Vec<u8>,
pub metadata: Metadata,
pub timestamp: u64,
pub timestamp_subsec: u32,
}
impl Cursor for Event {
type T = EventCursor;
fn serialize(&self) -> Self::T {
EventCursor {
i: self.id.to_string(),
v: self.version,
t: self.timestamp,
s: self.timestamp_subsec,
}
}
}
impl cursor::Bind for Event {
type T = Self;
fn sort_by(data: &mut Vec<Self::T>, is_order_desc: bool) {
if !is_order_desc {
data.sort_by(|a, b| {
if a.timestamp != b.timestamp {
return a.timestamp.cmp(&b.timestamp);
}
if a.timestamp_subsec != b.timestamp_subsec {
return a.timestamp_subsec.cmp(&b.timestamp_subsec);
}
if a.version != b.version {
return a.version.cmp(&b.version);
}
a.id.cmp(&b.id)
});
} else {
data.sort_by(|a, b| {
if a.timestamp != b.timestamp {
return b.timestamp.cmp(&a.timestamp);
}
if a.timestamp_subsec != b.timestamp_subsec {
return b.timestamp_subsec.cmp(&a.timestamp_subsec);
}
if a.version != b.version {
return b.version.cmp(&a.version);
}
b.id.cmp(&a.id)
});
}
}
fn retain(
data: &mut Vec<Self::T>,
cursor: <<Self as cursor::Bind>::T as Cursor>::T,
is_order_desc: bool,
) {
data.retain(|event| {
if is_order_desc {
event.timestamp < cursor.t
|| (event.timestamp == cursor.t
&& (event.timestamp_subsec < cursor.s
|| (event.timestamp_subsec == cursor.s
&& (event.version < cursor.v
|| (event.version == cursor.v
&& event.id.to_string() < cursor.i)))))
} else {
event.timestamp > cursor.t
|| (event.timestamp == cursor.t
&& (event.timestamp_subsec > cursor.s
|| (event.timestamp_subsec == cursor.s
&& (event.version > cursor.v
|| (event.version == cursor.v
&& event.id.to_string() > cursor.i)))))
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cursor::Bind;
fn event_at(timestamp: u64, timestamp_subsec: u32) -> Event {
Event {
id: Ulid::generate(),
timestamp,
timestamp_subsec,
..Default::default()
}
}
#[test]
fn cursor_encoding_is_stable_across_releases() {
use crate::cursor::{Cursor, Value};
let event = Event {
id: Ulid::from_string("01ARZ3NDEKTSV4RRFFQ69G5FAV").unwrap(),
version: 7,
timestamp: 1_700_000_000,
timestamp_subsec: 123,
..Default::default()
};
let fixture = Value("GjAxQVJaM05ERUtUU1Y0UlJGRlE2OUc1RkFWBwACAPFTZQR7".to_string());
assert_eq!(event.serialize_cursor().unwrap(), fixture);
let decoded = Event::deserialize_cursor(&fixture).unwrap();
assert_eq!(decoded.i, "01ARZ3NDEKTSV4RRFFQ69G5FAV");
assert_eq!(decoded.v, 7);
assert_eq!(decoded.t, 1_700_000_000);
assert_eq!(decoded.s, 123);
}
#[test]
fn sort_orders_by_timestamp_before_subsec() {
let earlier = event_at(1000, 500);
let later = event_at(1001, 100);
let mut asc = vec![later.clone(), earlier.clone()];
Event::sort_by(&mut asc, false);
assert_eq!(
(asc[0].timestamp, asc[1].timestamp),
(1000, 1001),
"ascending order must place the smaller whole-second first"
);
let mut desc = vec![earlier, later];
Event::sort_by(&mut desc, true);
assert_eq!(
(desc[0].timestamp, desc[1].timestamp),
(1001, 1000),
"descending order must place the larger whole-second first"
);
}
#[test]
fn retain_agrees_with_sort_order() {
let cursor = EventCursor {
i: Ulid::nil().to_string(),
v: 0,
t: 1000,
s: 500,
};
let mut forward = vec![event_at(1001, 100), event_at(1000, 400)];
Event::retain(&mut forward, cursor, false);
assert_eq!(forward.len(), 1);
assert_eq!(forward[0].timestamp, 1001);
}
}