1use chrono::{DateTime, Utc};
4
5use super::{
6 error::EntityHydrationError,
7 snapshot::{EsSnapshot, NoSnapshot, Replay, SnapshotRecord},
8 traits::*,
9};
10
11pub type LastPersisted<'a, E> = std::slice::Iter<'a, PersistedEvent<E>>;
13
14pub struct GenericEvent<Id> {
20 pub entity_id: Id,
21 pub sequence: i32,
22 pub event: serde_json::Value,
23 pub context: Option<crate::ContextData>,
24 pub recorded_at: DateTime<Utc>,
25 pub forgettable_payload: Option<serde_json::Value>,
26}
27
28pub struct SnapshotGenericEvent<Id> {
33 pub entity_id: Id,
34 pub sequence: i32,
35 pub event: Option<serde_json::Value>,
36 pub context: Option<crate::ContextData>,
37 pub recorded_at: Option<DateTime<Utc>>,
38 pub forgettable_payload: Option<serde_json::Value>,
39 pub snapshot: Option<serde_json::Value>,
40 pub snapshot_sequence: Option<i32>,
41 pub snapshot_recorded_at: Option<DateTime<Utc>>,
42 pub snapshot_first_recorded_at: Option<DateTime<Utc>>,
43 pub snapshot_forgettable_payload: Option<serde_json::Value>,
44}
45
46#[doc(hidden)]
48pub struct HydrationRow<Id> {
49 pub entity_id: Id,
50 pub sequence: i32,
51 pub event: Option<serde_json::Value>,
52 pub context: Option<crate::ContextData>,
53 pub recorded_at: Option<DateTime<Utc>>,
54 pub forgettable_payload: Option<serde_json::Value>,
55 pub snapshot: Option<serde_json::Value>,
56 pub snapshot_sequence: Option<i32>,
57 pub snapshot_recorded_at: Option<DateTime<Utc>>,
58 pub snapshot_first_recorded_at: Option<DateTime<Utc>>,
59 pub snapshot_forgettable_payload: Option<serde_json::Value>,
60}
61
62impl<Id> From<GenericEvent<Id>> for HydrationRow<Id> {
63 fn from(e: GenericEvent<Id>) -> Self {
64 Self {
65 entity_id: e.entity_id,
66 sequence: e.sequence,
67 event: Some(e.event),
68 context: e.context,
69 recorded_at: Some(e.recorded_at),
70 forgettable_payload: e.forgettable_payload,
71 snapshot: None,
72 snapshot_sequence: None,
73 snapshot_recorded_at: None,
74 snapshot_first_recorded_at: None,
75 snapshot_forgettable_payload: None,
76 }
77 }
78}
79
80impl<Id> From<SnapshotGenericEvent<Id>> for HydrationRow<Id> {
81 fn from(e: SnapshotGenericEvent<Id>) -> Self {
82 Self {
83 entity_id: e.entity_id,
84 sequence: e.sequence,
85 event: e.event,
86 context: e.context,
87 recorded_at: e.recorded_at,
88 forgettable_payload: e.forgettable_payload,
89 snapshot: e.snapshot,
90 snapshot_sequence: e.snapshot_sequence,
91 snapshot_recorded_at: e.snapshot_recorded_at,
92 snapshot_first_recorded_at: e.snapshot_first_recorded_at,
93 snapshot_forgettable_payload: e.snapshot_forgettable_payload,
94 }
95 }
96}
97
98pub struct PersistedEvent<E: EsEvent> {
104 pub entity_id: <E as EsEvent>::EntityId,
106 pub recorded_at: DateTime<Utc>,
108 pub sequence: usize,
110 pub event: E,
112 pub context: Option<crate::ContextData>,
115}
116
117impl<E: Clone + EsEvent> Clone for PersistedEvent<E> {
118 fn clone(&self) -> Self {
119 PersistedEvent {
120 entity_id: self.entity_id.clone(),
121 recorded_at: self.recorded_at,
122 sequence: self.sequence,
123 event: self.event.clone(),
124 context: self.context.clone(),
125 }
126 }
127}
128
129pub struct EventWithContext<E: EsEvent> {
130 pub event: E,
131 pub context: Option<crate::ContextData>,
132}
133
134impl<E: Clone + EsEvent> Clone for EventWithContext<E> {
135 fn clone(&self) -> Self {
136 EventWithContext {
137 event: self.event.clone(),
138 context: self.context.clone(),
139 }
140 }
141}
142
143pub struct EntityEvents<T: EsEvent, S: EsSnapshot = NoSnapshot> {
148 pub entity_id: <T as EsEvent>::EntityId,
150 base_sequence: usize,
153 snapshot: Option<SnapshotRecord<S>>,
155 persisted_events: Vec<PersistedEvent<T>>,
158 new_events: Vec<EventWithContext<T>>,
160}
161
162impl<T: Clone + EsEvent, S: EsSnapshot + Clone> Clone for EntityEvents<T, S> {
163 fn clone(&self) -> Self {
164 Self {
165 entity_id: self.entity_id.clone(),
166 base_sequence: self.base_sequence,
167 snapshot: self.snapshot.clone(),
168 persisted_events: self.persisted_events.clone(),
169 new_events: self.new_events.clone(),
170 }
171 }
172}
173
174#[doc(hidden)]
179pub fn hydrate_checked<E>(
180 events: EntityEvents<E::Event, E::Snapshot>,
181) -> Result<E, EntityHydrationError>
182where
183 E: EsEntity,
184{
185 let container_id = events.entity_id.clone();
186 let entity = E::try_from_events(events)?;
187 if *entity.__entity_id() != container_id {
188 return Err(EntityHydrationError::IdMismatch);
189 }
190 Ok(entity)
191}
192
193impl<T, S> EntityEvents<T, S>
194where
195 T: EsEvent,
196 S: EsSnapshot,
197{
198 pub(crate) fn init(
202 id: <T as EsEvent>::EntityId,
203 initial_events: impl IntoIterator<Item = T>,
204 ) -> Self {
205 let context = if <T as EsEvent>::event_context() {
206 Some(crate::EventContext::data_for_storing())
207 } else {
208 None
209 };
210 let new_events = initial_events
211 .into_iter()
212 .map(|event| EventWithContext {
213 event,
214 context: context.clone(),
215 })
216 .collect();
217 Self {
218 entity_id: id,
219 base_sequence: 0,
220 snapshot: None,
221 persisted_events: Vec::new(),
222 new_events,
223 }
224 }
225
226 pub fn id(&self) -> &<T as EsEvent>::EntityId {
228 &self.entity_id
229 }
230
231 pub fn entity_first_persisted_at(&self) -> Option<DateTime<Utc>> {
233 self.snapshot
234 .as_ref()
235 .map(|s| s.first_recorded_at)
236 .or_else(|| self.persisted_events.first().map(|e| e.recorded_at))
237 }
238
239 pub fn entity_last_modified_at(&self) -> Option<DateTime<Utc>> {
241 self.persisted_events
242 .last()
243 .map(|e| e.recorded_at)
244 .or_else(|| self.snapshot.as_ref().map(|s| s.recorded_at))
245 }
246
247 pub fn push(&mut self, event: T) {
249 let context = if <T as EsEvent>::event_context() {
250 Some(crate::EventContext::data_for_storing())
251 } else {
252 None
253 };
254 self.new_events.push(EventWithContext { event, context });
255 }
256
257 pub fn extend(&mut self, events: impl IntoIterator<Item = T>) {
259 let context = if <T as EsEvent>::event_context() {
260 Some(crate::EventContext::data_for_storing())
261 } else {
262 None
263 };
264 self.new_events
265 .extend(events.into_iter().map(|event| EventWithContext {
266 event,
267 context: context.clone(),
268 }));
269 }
270
271 pub fn any_new(&self) -> bool {
273 !self.new_events.is_empty()
274 }
275
276 pub fn len_persisted(&self) -> usize {
279 self.base_sequence + self.persisted_events.len()
280 }
281
282 pub fn tail_len(&self) -> usize {
286 self.persisted_events.len() + self.new_events.len()
287 }
288
289 #[doc(hidden)]
290 pub fn len_new(&self) -> usize {
291 self.new_events.len()
292 }
293
294 pub fn snapshot(&self) -> Option<&SnapshotRecord<S>> {
296 self.snapshot.as_ref()
297 }
298
299 pub fn last_persisted(&self, n: usize) -> LastPersisted<'_, T> {
302 let start = self.persisted_events.len().saturating_sub(n);
303 self.persisted_events[start..].iter()
304 }
305
306 pub fn replay(&self) -> impl DoubleEndedIterator<Item = Replay<'_, T, S>> + Clone {
310 self.snapshot
311 .iter()
312 .map(|r| Replay::Snapshot(&r.state))
313 .chain(
314 self.persisted_events
315 .iter()
316 .map(|e| Replay::Event(&e.event)),
317 )
318 .chain(self.new_events.iter().map(|e| Replay::Event(&e.event)))
319 }
320
321 pub fn replay_persisted(
325 &self,
326 ) -> impl DoubleEndedIterator<Item = Replay<'_, PersistedEvent<T>, SnapshotRecord<S>>> + Clone
327 {
328 self.snapshot
329 .iter()
330 .map(Replay::Snapshot)
331 .chain(self.persisted_events.iter().map(Replay::Event))
332 }
333
334 #[doc(hidden)]
338 pub fn compact_to_snapshot(
339 &mut self,
340 state: S,
341 recorded_at: DateTime<Utc>,
342 first_recorded_at: DateTime<Utc>,
343 ) {
344 debug_assert!(self.new_events.is_empty());
345 let sequence = self.len_persisted();
346 self.persisted_events.clear();
347 self.base_sequence = sequence;
348 self.snapshot = Some(SnapshotRecord {
349 sequence,
350 state,
351 recorded_at,
352 first_recorded_at,
353 });
354 }
355
356 fn apply_hydration_row(
359 &mut self,
360 row: HydrationRow<<T as EsEvent>::EntityId>,
361 ) -> Result<(), EntityHydrationError> {
362 if let Some(mut snapshot_json) = row.snapshot
363 && S::IS_SNAPSHOT
364 {
365 if let Some(payload) = row.snapshot_forgettable_payload {
366 crate::forgettable::inject_forgettable_payload(&mut snapshot_json, payload);
367 }
368 let sequence = row
369 .snapshot_sequence
370 .expect("snapshot row missing snapshot_sequence");
371 let state: S = serde_json::from_value(snapshot_json)
372 .map_err(|source| EntityHydrationError::SnapshotDecode { sequence, source })?;
373 self.base_sequence = sequence as usize;
374 self.snapshot = Some(SnapshotRecord {
375 sequence: sequence as usize,
376 state,
377 recorded_at: row
378 .snapshot_recorded_at
379 .expect("snapshot row missing snapshot_recorded_at"),
380 first_recorded_at: row
381 .snapshot_first_recorded_at
382 .expect("snapshot row missing snapshot_first_recorded_at"),
383 });
384 }
385
386 match row.event {
387 Some(mut event_json) => {
388 if self.persisted_events.is_empty()
389 && self.snapshot.is_some()
390 && row.sequence as usize != self.base_sequence + 1
391 {
392 return Err(EntityHydrationError::SnapshotGap {
393 snapshot_sequence: self.base_sequence as i32,
394 next_event_sequence: row.sequence,
395 });
396 }
397 if let Some(payload) = row.forgettable_payload {
398 crate::forgettable::inject_forgettable_payload(&mut event_json, payload);
399 }
400 self.persisted_events.push(PersistedEvent {
401 entity_id: row.entity_id,
402 recorded_at: row.recorded_at.expect("event row missing recorded_at"),
403 sequence: row.sequence as usize,
404 event: serde_json::from_value(event_json)?,
405 context: row.context,
406 });
407 Ok(())
408 }
409 None if self.snapshot.is_some() => Ok(()),
410 None => Err(EntityHydrationError::NoEvents),
411 }
412 }
413
414 pub fn load_first<E>(
419 events: impl IntoIterator<Item = impl Into<HydrationRow<<T as EsEvent>::EntityId>>>,
420 ) -> Result<Option<E>, EntityHydrationError>
421 where
422 E: EsEntity<Event = T, Snapshot = S>,
423 {
424 let mut current_id = None;
425 let mut current: Option<Self> = None;
426 for e in events {
427 let row: HydrationRow<<T as EsEvent>::EntityId> = e.into();
428 if current_id.is_none() {
429 current_id = Some(row.entity_id.clone());
430 current = Some(Self {
431 entity_id: row.entity_id.clone(),
432 base_sequence: 0,
433 snapshot: None,
434 persisted_events: Vec::new(),
435 new_events: Vec::new(),
436 });
437 }
438 if current_id.as_ref() != Some(&row.entity_id) {
439 break;
440 }
441 let cur = current.as_mut().expect("Could not get current");
442 cur.apply_hydration_row(row)?;
443 }
444 if let Some(current) = current {
445 Ok(Some(hydrate_checked(current)?))
446 } else {
447 Ok(None)
448 }
449 }
450
451 pub fn load_n<E>(
457 events: impl IntoIterator<Item = impl Into<HydrationRow<<T as EsEvent>::EntityId>>>,
458 n: usize,
459 ) -> Result<(Vec<E>, bool), EntityHydrationError>
460 where
461 E: EsEntity<Event = T, Snapshot = S>,
462 {
463 if n == 0 {
464 let has_more = events.into_iter().next().is_some();
469 return Ok((Vec::new(), has_more));
470 }
471 let mut ret: Vec<E> = Vec::new();
472 let mut current_id = None;
473 let mut current: Option<Self> = None;
474 for e in events {
475 let row: HydrationRow<<T as EsEvent>::EntityId> = e.into();
476 if current_id.as_ref() != Some(&row.entity_id) {
477 if let Some(current) = current.take() {
478 ret.push(hydrate_checked(current)?);
479 if ret.len() == n {
480 return Ok((ret, true));
481 }
482 }
483
484 current_id = Some(row.entity_id.clone());
485 current = Some(Self {
486 entity_id: row.entity_id.clone(),
487 base_sequence: 0,
488 snapshot: None,
489 persisted_events: Vec::new(),
490 new_events: Vec::new(),
491 });
492 }
493 let cur = current.as_mut().expect("Could not get current");
494 cur.apply_hydration_row(row)?;
495 }
496 if let Some(current) = current.take() {
497 ret.push(hydrate_checked(current)?);
498 }
499 Ok((ret, false))
500 }
501
502 #[doc(hidden)]
503 pub fn iter_new_events(&self) -> impl Iterator<Item = &EventWithContext<T>> {
504 self.new_events.iter()
505 }
506
507 #[doc(hidden)]
508 pub fn mark_new_events_persisted_at(
509 &mut self,
510 recorded_at: chrono::DateTime<chrono::Utc>,
511 ) -> usize {
512 let n = self.new_events.len();
513 let offset = self.len_persisted() + 1;
514 self.persisted_events
515 .extend(
516 self.new_events
517 .drain(..)
518 .enumerate()
519 .map(|(i, event)| PersistedEvent {
520 entity_id: self.entity_id.clone(),
521 recorded_at,
522 sequence: i + offset,
523 event: event.event,
524 context: event.context,
525 }),
526 );
527 n
528 }
529
530 #[doc(hidden)]
531 pub fn new_event_types(&self) -> Vec<String> {
532 self.new_events
533 .iter()
534 .map(|event| event.event.event_type().to_string())
535 .collect()
536 }
537
538 #[doc(hidden)]
539 pub fn serialize_new_events(&self) -> Vec<serde_json::Value> {
540 self.new_events
541 .iter()
542 .map(|event| serde_json::to_value(&event.event).expect("Failed to serialize event"))
543 .collect()
544 }
545
546 #[doc(hidden)]
556 pub fn forget_and_take(&mut self, mut forget_fn: impl FnMut(&mut T)) -> Self {
557 for persisted in &mut self.persisted_events {
558 forget_fn(&mut persisted.event);
559 }
560 let entity_id = self.entity_id.clone();
561 std::mem::replace(
562 self,
563 Self {
564 entity_id,
565 base_sequence: 0,
566 snapshot: None,
567 persisted_events: Vec::new(),
568 new_events: Vec::new(),
569 },
570 )
571 }
572
573 #[doc(hidden)]
574 pub fn serialize_new_event_contexts(&self) -> Option<Vec<crate::ContextData>> {
575 if <T as EsEvent>::event_context() {
576 let contexts = self
577 .new_events
578 .iter()
579 .map(|event| event.context.clone().expect("Missing context"))
580 .collect();
581
582 Some(contexts)
583 } else {
584 None
585 }
586 }
587}
588
589impl<T: EsEvent> EntityEvents<T, NoSnapshot> {
590 pub fn iter_persisted(&self) -> impl DoubleEndedIterator<Item = &PersistedEvent<T>> + Clone {
592 self.persisted_events.iter()
593 }
594
595 pub fn iter_all(&self) -> impl DoubleEndedIterator<Item = &T> + Clone {
629 self.persisted_events
630 .iter()
631 .map(|e| &e.event)
632 .chain(self.new_events.iter().map(|e| &e.event))
633 }
634
635 #[doc(hidden)]
639 pub fn widen_snapshot<S: EsSnapshot>(self) -> EntityEvents<T, S> {
640 EntityEvents {
641 entity_id: self.entity_id,
642 base_sequence: 0,
643 snapshot: None,
644 persisted_events: self.persisted_events,
645 new_events: self.new_events,
646 }
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653 use proptest::prelude::*;
654 use uuid::Uuid;
655
656 fn valid_event(id: Uuid, sequence: i32, name: &str) -> GenericEvent<Uuid> {
658 GenericEvent {
659 entity_id: id,
660 sequence,
661 event: serde_json::to_value(DummyEntityEvent::Created(name.to_string()))
662 .expect("could not serialize"),
663 context: None,
664 recorded_at: chrono::Utc::now(),
665 forgettable_payload: None,
666 }
667 }
668
669 fn json_value() -> impl Strategy<Value = serde_json::Value> {
673 let scalar = prop_oneof![
674 Just(serde_json::Value::Null),
675 any::<bool>().prop_map(serde_json::Value::Bool),
676 any::<i64>().prop_map(serde_json::Value::from),
677 any::<f64>().prop_map(serde_json::Value::from),
678 ".{0,15}".prop_map(serde_json::Value::String),
679 ]
680 .boxed();
681 let nested = prop_oneof![
682 proptest::collection::vec(scalar.clone(), 0..4).prop_map(serde_json::Value::Array),
683 proptest::collection::vec((".{0,6}", scalar.clone()), 0..4).prop_map(|pairs| {
684 let mut m = serde_json::Map::new();
685 for (k, v) in pairs {
686 m.insert(k, v);
687 }
688 serde_json::Value::Object(m)
689 },),
690 ];
691 prop_oneof![scalar, nested]
692 }
693
694 #[derive(Debug, serde::Serialize, serde::Deserialize)]
695 enum DummyEntityEvent {
696 Created(String),
697 }
698
699 impl EsEvent for DummyEntityEvent {
700 type EntityId = Uuid;
701 fn event_context() -> bool {
702 true
703 }
704 fn event_type(&self) -> &'static str {
705 match self {
706 Self::Created(_) => "created",
707 }
708 }
709 }
710
711 struct DummyEntity {
712 name: String,
713
714 events: EntityEvents<DummyEntityEvent>,
715 }
716
717 impl EsEntity for DummyEntity {
718 type Event = DummyEntityEvent;
719 type New = NewDummyEntity;
720 type Snapshot = NoSnapshot;
721
722 fn events_mut(&mut self) -> &mut EntityEvents<DummyEntityEvent> {
723 &mut self.events
724 }
725 fn events(&self) -> &EntityEvents<DummyEntityEvent> {
726 &self.events
727 }
728 fn __entity_id(&self) -> &Uuid {
729 &self.events.entity_id
730 }
731 }
732
733 impl TryFromEvents<DummyEntityEvent> for DummyEntity {
734 fn try_from_events(
735 events: EntityEvents<DummyEntityEvent>,
736 ) -> Result<Self, EntityHydrationError> {
737 let name = events
738 .iter_persisted()
739 .map(|e| match &e.event {
740 DummyEntityEvent::Created(name) => name.clone(),
741 })
742 .next()
743 .expect("Could not find name");
744 Ok(Self { name, events })
745 }
746 }
747
748 struct NewDummyEntity {}
749
750 impl IntoEvents<DummyEntityEvent> for NewDummyEntity {
751 type IdSource = crate::FromCaller;
752
753 fn into_events(
754 self,
755 builder: crate::EntityEventsBuilder<DummyEntityEvent, crate::FromCaller>,
756 ) -> EntityEvents<DummyEntityEvent> {
757 builder.initial_events(
758 Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
759 vec![DummyEntityEvent::Created("".to_owned())],
760 )
761 }
762 }
763
764 #[test]
765 fn load_zero_events() {
766 let generic_events: Vec<GenericEvent<Uuid>> = vec![];
767 let res = EntityEvents::load_first::<DummyEntity>(generic_events);
768 assert!(matches!(res, Ok(None)));
769 }
770
771 #[test]
772 fn load_first() {
773 let generic_events = vec![GenericEvent {
774 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
775 sequence: 1,
776 event: serde_json::to_value(DummyEntityEvent::Created("dummy-name".to_owned()))
777 .expect("Could not serialize"),
778 context: None,
779 recorded_at: chrono::Utc::now(),
780 forgettable_payload: None,
781 }];
782 let entity: DummyEntity = EntityEvents::load_first(generic_events)
783 .expect("Could not load")
784 .expect("No entity found");
785 assert!(entity.name == "dummy-name");
786 }
787
788 #[test]
789 fn load_n() {
790 let generic_events = vec![
791 GenericEvent {
792 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
793 sequence: 1,
794 event: serde_json::to_value(DummyEntityEvent::Created("dummy-name".to_owned()))
795 .expect("Could not serialize"),
796 context: None,
797 recorded_at: chrono::Utc::now(),
798 forgettable_payload: None,
799 },
800 GenericEvent {
801 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
802 sequence: 1,
803 event: serde_json::to_value(DummyEntityEvent::Created("other-name".to_owned()))
804 .expect("Could not serialize"),
805 context: None,
806 recorded_at: chrono::Utc::now(),
807 forgettable_payload: None,
808 },
809 ];
810 let (entity, more): (Vec<DummyEntity>, _) =
811 EntityEvents::load_n(generic_events, 2).expect("Could not load");
812 assert!(!more);
813 assert_eq!(entity.len(), 2);
814 }
815
816 #[test]
817 fn last_persisted_does_not_panic_when_n_exceeds_len() {
818 let generic_events = vec![GenericEvent {
819 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000004").unwrap(),
820 sequence: 1,
821 event: serde_json::to_value(DummyEntityEvent::Created("dummy".to_owned()))
822 .expect("Could not serialize"),
823 context: None,
824 recorded_at: chrono::Utc::now(),
825 forgettable_payload: None,
826 }];
827 let entity: DummyEntity = EntityEvents::load_first(generic_events)
828 .expect("Could not load")
829 .expect("No entity found");
830 let events = entity.events();
831
832 assert_eq!(events.last_persisted(1).count(), 1);
834 assert_eq!(events.last_persisted(10).count(), 1);
836 assert_eq!(events.last_persisted(0).count(), 0);
838 }
839
840 proptest! {
841 #[test]
842 fn load_first_empty_input_returns_none(_ in Just(())) {
843 let res = EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(
844 Vec::<GenericEvent<Uuid>>::new(),
845 );
846 prop_assert!(matches!(res, Ok(None)));
847 }
848
849 #[test]
850 fn load_first_non_empty_valid_hydrates(
851 count in 1u8..8,
852 names in proptest::collection::vec(".{0,10}", 1..8),
853 ) {
854 let id = Uuid::nil();
855 let events: Vec<_> = (1..=count as i32)
856 .zip(names.iter())
857 .map(|(s, n)| valid_event(id, s, n))
858 .collect();
859 let res = EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(events);
860 prop_assert!(matches!(res, Ok(Some(_))));
861 }
862
863 #[test]
866 fn load_first_arbitrary_json_never_panics(
867 raw in proptest::collection::vec(json_value(), 0..10),
868 ) {
869 let events: Vec<_> = raw
870 .into_iter()
871 .enumerate()
872 .map(|(i, v)| GenericEvent {
873 entity_id: Uuid::nil(),
874 sequence: i as i32,
875 event: v,
876 context: None,
877 recorded_at: chrono::Utc::now(),
878 forgettable_payload: None,
879 })
880 .collect();
881 let is_empty = events.is_empty();
882 let res = EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(events);
883 if let Ok(None) = res {
884 prop_assert!(is_empty);
885 }
886 }
887
888 #[test]
892 fn load_n_respects_limit_and_more_flag(
893 k in 1u8..8,
894 per in 1u8..4,
895 n in 1u8..12,
896 ) {
897 let mut events = Vec::new();
898 for i in 0..k {
899 let id = Uuid::from_u128(i as u128);
900 for s in 1..=per as i32 {
901 events.push(valid_event(id, s, &format!("e{i}-{s}")));
902 }
903 }
904 let (entities, has_more) =
905 EntityEvents::<DummyEntityEvent>::load_n::<DummyEntity>(events, n as usize)
906 .expect("valid events hydrate");
907 prop_assert_eq!(entities.len(), (n as usize).min(k as usize));
908 prop_assert_eq!(has_more, n < k);
909 }
910
911 #[test]
915 fn load_n_zero_returns_no_entities(k in 0u8..5, per in 1u8..3) {
916 let mut events = Vec::new();
917 for i in 0..k {
918 let id = Uuid::from_u128(i as u128);
919 for s in 1..=per as i32 {
920 events.push(valid_event(id, s, &format!("e{i}-{s}")));
921 }
922 }
923 let (entities, has_more) =
924 EntityEvents::<DummyEntityEvent>::load_n::<DummyEntity>(events, 0)
925 .expect("valid events hydrate");
926 prop_assert!(entities.is_empty());
927 prop_assert_eq!(has_more, k > 0);
928 }
929
930 #[test]
931 fn load_n_arbitrary_json_never_panics(
932 raw in proptest::collection::vec(json_value(), 0..10),
933 n in 0u8..12,
934 ) {
935 let events: Vec<_> = raw
936 .into_iter()
937 .enumerate()
938 .map(|(i, v)| GenericEvent {
939 entity_id: Uuid::nil(),
940 sequence: i as i32,
941 event: v,
942 context: None,
943 recorded_at: chrono::Utc::now(),
944 forgettable_payload: None,
945 })
946 .collect();
947 let _ = EntityEvents::<DummyEntityEvent>::load_n::<DummyEntity>(events, n as usize);
948 }
949
950 #[test]
953 fn last_persisted_clamps_for_any_n(
954 p in 1u8..8,
955 n in 0u16..12,
956 ) {
957 let id = Uuid::nil();
958 let events: Vec<_> = (1..=p as i32)
959 .map(|s| valid_event(id, s, &format!("n{s}")))
960 .collect();
961 let entity: DummyEntity =
962 EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(events)
963 .expect("load")
964 .expect("some");
965 let count = entity.events().last_persisted(n as usize).count();
966 prop_assert_eq!(count, (n as usize).min(p as usize));
967 }
968
969 #[test]
973 fn mark_new_events_assigns_contiguous_sequences(
974 a in 0u8..5,
975 b in 0u8..5,
976 ) {
977 let id = Uuid::nil();
978 let mut events = EntityEvents::init(
979 id,
980 (0..a).map(|i| DummyEntityEvent::Created(format!("n{i}"))),
981 );
982 let now = chrono::Utc::now();
983
984 prop_assert_eq!(events.mark_new_events_persisted_at(now), a as usize);
985 prop_assert!(!events.any_new());
986 prop_assert_eq!(events.len_persisted(), a as usize);
987 let seqs: Vec<usize> = events.iter_persisted().map(|e| e.sequence).collect();
988 prop_assert_eq!(seqs, (1..=a as usize).collect::<Vec<_>>());
989
990 for i in 0..b {
991 events.push(DummyEntityEvent::Created(format!("m{i}")));
992 }
993 if b > 0 {
994 prop_assert!(events.any_new());
995 }
996 prop_assert_eq!(events.mark_new_events_persisted_at(now), b as usize);
997 prop_assert!(!events.any_new());
998 prop_assert_eq!(events.len_persisted(), (a + b) as usize);
999 let seqs: Vec<usize> = events.iter_persisted().map(|e| e.sequence).collect();
1000 prop_assert_eq!(seqs, (1..=(a + b) as usize).collect::<Vec<_>>());
1001 }
1002 }
1003}