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
174impl<T, S> EntityEvents<T, S>
175where
176 T: EsEvent,
177 S: EsSnapshot,
178{
179 pub fn init(id: <T as EsEvent>::EntityId, initial_events: impl IntoIterator<Item = T>) -> Self {
181 let context = if <T as EsEvent>::event_context() {
182 Some(crate::EventContext::data_for_storing())
183 } else {
184 None
185 };
186 let new_events = initial_events
187 .into_iter()
188 .map(|event| EventWithContext {
189 event,
190 context: context.clone(),
191 })
192 .collect();
193 Self {
194 entity_id: id,
195 base_sequence: 0,
196 snapshot: None,
197 persisted_events: Vec::new(),
198 new_events,
199 }
200 }
201
202 pub fn id(&self) -> &<T as EsEvent>::EntityId {
204 &self.entity_id
205 }
206
207 pub fn entity_first_persisted_at(&self) -> Option<DateTime<Utc>> {
209 self.snapshot
210 .as_ref()
211 .map(|s| s.first_recorded_at)
212 .or_else(|| self.persisted_events.first().map(|e| e.recorded_at))
213 }
214
215 pub fn entity_last_modified_at(&self) -> Option<DateTime<Utc>> {
217 self.persisted_events
218 .last()
219 .map(|e| e.recorded_at)
220 .or_else(|| self.snapshot.as_ref().map(|s| s.recorded_at))
221 }
222
223 pub fn push(&mut self, event: T) {
225 let context = if <T as EsEvent>::event_context() {
226 Some(crate::EventContext::data_for_storing())
227 } else {
228 None
229 };
230 self.new_events.push(EventWithContext { event, context });
231 }
232
233 pub fn extend(&mut self, events: impl IntoIterator<Item = T>) {
235 let context = if <T as EsEvent>::event_context() {
236 Some(crate::EventContext::data_for_storing())
237 } else {
238 None
239 };
240 self.new_events
241 .extend(events.into_iter().map(|event| EventWithContext {
242 event,
243 context: context.clone(),
244 }));
245 }
246
247 pub fn any_new(&self) -> bool {
249 !self.new_events.is_empty()
250 }
251
252 pub fn len_persisted(&self) -> usize {
255 self.base_sequence + self.persisted_events.len()
256 }
257
258 pub fn tail_len(&self) -> usize {
262 self.persisted_events.len() + self.new_events.len()
263 }
264
265 #[doc(hidden)]
266 pub fn len_new(&self) -> usize {
267 self.new_events.len()
268 }
269
270 pub fn snapshot(&self) -> Option<&SnapshotRecord<S>> {
272 self.snapshot.as_ref()
273 }
274
275 pub fn last_persisted(&self, n: usize) -> LastPersisted<'_, T> {
278 let start = self.persisted_events.len().saturating_sub(n);
279 self.persisted_events[start..].iter()
280 }
281
282 pub fn replay(&self) -> impl DoubleEndedIterator<Item = Replay<'_, T, S>> + Clone {
286 self.snapshot
287 .iter()
288 .map(|r| Replay::Snapshot(&r.state))
289 .chain(
290 self.persisted_events
291 .iter()
292 .map(|e| Replay::Event(&e.event)),
293 )
294 .chain(self.new_events.iter().map(|e| Replay::Event(&e.event)))
295 }
296
297 pub fn replay_persisted(
301 &self,
302 ) -> impl DoubleEndedIterator<Item = Replay<'_, PersistedEvent<T>, SnapshotRecord<S>>> + Clone
303 {
304 self.snapshot
305 .iter()
306 .map(Replay::Snapshot)
307 .chain(self.persisted_events.iter().map(Replay::Event))
308 }
309
310 #[doc(hidden)]
314 pub fn compact_to_snapshot(
315 &mut self,
316 state: S,
317 recorded_at: DateTime<Utc>,
318 first_recorded_at: DateTime<Utc>,
319 ) {
320 debug_assert!(self.new_events.is_empty());
321 let sequence = self.len_persisted();
322 self.persisted_events.clear();
323 self.base_sequence = sequence;
324 self.snapshot = Some(SnapshotRecord {
325 sequence,
326 state,
327 recorded_at,
328 first_recorded_at,
329 });
330 }
331
332 fn apply_hydration_row(
335 &mut self,
336 row: HydrationRow<<T as EsEvent>::EntityId>,
337 ) -> Result<(), EntityHydrationError> {
338 if let Some(mut snapshot_json) = row.snapshot
339 && S::IS_SNAPSHOT
340 {
341 if let Some(payload) = row.snapshot_forgettable_payload {
342 crate::forgettable::inject_forgettable_payload(&mut snapshot_json, payload);
343 }
344 let sequence = row
345 .snapshot_sequence
346 .expect("snapshot row missing snapshot_sequence");
347 let state: S = serde_json::from_value(snapshot_json)
348 .map_err(|source| EntityHydrationError::SnapshotDecode { sequence, source })?;
349 self.base_sequence = sequence as usize;
350 self.snapshot = Some(SnapshotRecord {
351 sequence: sequence as usize,
352 state,
353 recorded_at: row
354 .snapshot_recorded_at
355 .expect("snapshot row missing snapshot_recorded_at"),
356 first_recorded_at: row
357 .snapshot_first_recorded_at
358 .expect("snapshot row missing snapshot_first_recorded_at"),
359 });
360 }
361
362 match row.event {
363 Some(mut event_json) => {
364 if self.persisted_events.is_empty()
365 && self.snapshot.is_some()
366 && row.sequence as usize != self.base_sequence + 1
367 {
368 return Err(EntityHydrationError::SnapshotGap {
369 snapshot_sequence: self.base_sequence as i32,
370 next_event_sequence: row.sequence,
371 });
372 }
373 if let Some(payload) = row.forgettable_payload {
374 crate::forgettable::inject_forgettable_payload(&mut event_json, payload);
375 }
376 self.persisted_events.push(PersistedEvent {
377 entity_id: row.entity_id,
378 recorded_at: row.recorded_at.expect("event row missing recorded_at"),
379 sequence: row.sequence as usize,
380 event: serde_json::from_value(event_json)?,
381 context: row.context,
382 });
383 Ok(())
384 }
385 None if self.snapshot.is_some() => Ok(()),
386 None => Err(EntityHydrationError::NoEvents),
387 }
388 }
389
390 pub fn load_first<E>(
395 events: impl IntoIterator<Item = impl Into<HydrationRow<<T as EsEvent>::EntityId>>>,
396 ) -> Result<Option<E>, EntityHydrationError>
397 where
398 E: EsEntity<Event = T, Snapshot = S>,
399 {
400 let mut current_id = None;
401 let mut current: Option<Self> = None;
402 for e in events {
403 let row: HydrationRow<<T as EsEvent>::EntityId> = e.into();
404 if current_id.is_none() {
405 current_id = Some(row.entity_id.clone());
406 current = Some(Self {
407 entity_id: row.entity_id.clone(),
408 base_sequence: 0,
409 snapshot: None,
410 persisted_events: Vec::new(),
411 new_events: Vec::new(),
412 });
413 }
414 if current_id.as_ref() != Some(&row.entity_id) {
415 break;
416 }
417 let cur = current.as_mut().expect("Could not get current");
418 cur.apply_hydration_row(row)?;
419 }
420 if let Some(current) = current {
421 Ok(Some(E::try_from_events(current)?))
422 } else {
423 Ok(None)
424 }
425 }
426
427 pub fn load_n<E>(
433 events: impl IntoIterator<Item = impl Into<HydrationRow<<T as EsEvent>::EntityId>>>,
434 n: usize,
435 ) -> Result<(Vec<E>, bool), EntityHydrationError>
436 where
437 E: EsEntity<Event = T, Snapshot = S>,
438 {
439 if n == 0 {
440 let has_more = events.into_iter().next().is_some();
445 return Ok((Vec::new(), has_more));
446 }
447 let mut ret: Vec<E> = Vec::new();
448 let mut current_id = None;
449 let mut current: Option<Self> = None;
450 for e in events {
451 let row: HydrationRow<<T as EsEvent>::EntityId> = e.into();
452 if current_id.as_ref() != Some(&row.entity_id) {
453 if let Some(current) = current.take() {
454 ret.push(E::try_from_events(current)?);
455 if ret.len() == n {
456 return Ok((ret, true));
457 }
458 }
459
460 current_id = Some(row.entity_id.clone());
461 current = Some(Self {
462 entity_id: row.entity_id.clone(),
463 base_sequence: 0,
464 snapshot: None,
465 persisted_events: Vec::new(),
466 new_events: Vec::new(),
467 });
468 }
469 let cur = current.as_mut().expect("Could not get current");
470 cur.apply_hydration_row(row)?;
471 }
472 if let Some(current) = current.take() {
473 ret.push(E::try_from_events(current)?);
474 }
475 Ok((ret, false))
476 }
477
478 #[doc(hidden)]
479 pub fn iter_new_events(&self) -> impl Iterator<Item = &EventWithContext<T>> {
480 self.new_events.iter()
481 }
482
483 #[doc(hidden)]
484 pub fn mark_new_events_persisted_at(
485 &mut self,
486 recorded_at: chrono::DateTime<chrono::Utc>,
487 ) -> usize {
488 let n = self.new_events.len();
489 let offset = self.len_persisted() + 1;
490 self.persisted_events
491 .extend(
492 self.new_events
493 .drain(..)
494 .enumerate()
495 .map(|(i, event)| PersistedEvent {
496 entity_id: self.entity_id.clone(),
497 recorded_at,
498 sequence: i + offset,
499 event: event.event,
500 context: event.context,
501 }),
502 );
503 n
504 }
505
506 #[doc(hidden)]
507 pub fn new_event_types(&self) -> Vec<String> {
508 self.new_events
509 .iter()
510 .map(|event| event.event.event_type().to_string())
511 .collect()
512 }
513
514 #[doc(hidden)]
515 pub fn serialize_new_events(&self) -> Vec<serde_json::Value> {
516 self.new_events
517 .iter()
518 .map(|event| serde_json::to_value(&event.event).expect("Failed to serialize event"))
519 .collect()
520 }
521
522 #[doc(hidden)]
532 pub fn forget_and_take(&mut self, mut forget_fn: impl FnMut(&mut T)) -> Self {
533 for persisted in &mut self.persisted_events {
534 forget_fn(&mut persisted.event);
535 }
536 let entity_id = self.entity_id.clone();
537 std::mem::replace(
538 self,
539 Self {
540 entity_id,
541 base_sequence: 0,
542 snapshot: None,
543 persisted_events: Vec::new(),
544 new_events: Vec::new(),
545 },
546 )
547 }
548
549 #[doc(hidden)]
550 pub fn serialize_new_event_contexts(&self) -> Option<Vec<crate::ContextData>> {
551 if <T as EsEvent>::event_context() {
552 let contexts = self
553 .new_events
554 .iter()
555 .map(|event| event.context.clone().expect("Missing context"))
556 .collect();
557
558 Some(contexts)
559 } else {
560 None
561 }
562 }
563}
564
565impl<T: EsEvent> EntityEvents<T, NoSnapshot> {
566 pub fn iter_persisted(&self) -> impl DoubleEndedIterator<Item = &PersistedEvent<T>> + Clone {
568 self.persisted_events.iter()
569 }
570
571 pub fn iter_all(&self) -> impl DoubleEndedIterator<Item = &T> + Clone {
605 self.persisted_events
606 .iter()
607 .map(|e| &e.event)
608 .chain(self.new_events.iter().map(|e| &e.event))
609 }
610
611 #[doc(hidden)]
615 pub fn widen_snapshot<S: EsSnapshot>(self) -> EntityEvents<T, S> {
616 EntityEvents {
617 entity_id: self.entity_id,
618 base_sequence: 0,
619 snapshot: None,
620 persisted_events: self.persisted_events,
621 new_events: self.new_events,
622 }
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629 use proptest::prelude::*;
630 use uuid::Uuid;
631
632 fn valid_event(id: Uuid, sequence: i32, name: &str) -> GenericEvent<Uuid> {
634 GenericEvent {
635 entity_id: id,
636 sequence,
637 event: serde_json::to_value(DummyEntityEvent::Created(name.to_string()))
638 .expect("could not serialize"),
639 context: None,
640 recorded_at: chrono::Utc::now(),
641 forgettable_payload: None,
642 }
643 }
644
645 fn json_value() -> impl Strategy<Value = serde_json::Value> {
649 let scalar = prop_oneof![
650 Just(serde_json::Value::Null),
651 any::<bool>().prop_map(serde_json::Value::Bool),
652 any::<i64>().prop_map(serde_json::Value::from),
653 any::<f64>().prop_map(serde_json::Value::from),
654 ".{0,15}".prop_map(serde_json::Value::String),
655 ]
656 .boxed();
657 let nested = prop_oneof![
658 proptest::collection::vec(scalar.clone(), 0..4).prop_map(serde_json::Value::Array),
659 proptest::collection::vec((".{0,6}", scalar.clone()), 0..4).prop_map(|pairs| {
660 let mut m = serde_json::Map::new();
661 for (k, v) in pairs {
662 m.insert(k, v);
663 }
664 serde_json::Value::Object(m)
665 },),
666 ];
667 prop_oneof![scalar, nested]
668 }
669
670 #[derive(Debug, serde::Serialize, serde::Deserialize)]
671 enum DummyEntityEvent {
672 Created(String),
673 }
674
675 impl EsEvent for DummyEntityEvent {
676 type EntityId = Uuid;
677 fn event_context() -> bool {
678 true
679 }
680 fn event_type(&self) -> &'static str {
681 match self {
682 Self::Created(_) => "created",
683 }
684 }
685 }
686
687 struct DummyEntity {
688 name: String,
689
690 events: EntityEvents<DummyEntityEvent>,
691 }
692
693 impl EsEntity for DummyEntity {
694 type Event = DummyEntityEvent;
695 type New = NewDummyEntity;
696 type Snapshot = NoSnapshot;
697
698 fn events_mut(&mut self) -> &mut EntityEvents<DummyEntityEvent> {
699 &mut self.events
700 }
701 fn events(&self) -> &EntityEvents<DummyEntityEvent> {
702 &self.events
703 }
704 }
705
706 impl TryFromEvents<DummyEntityEvent> for DummyEntity {
707 fn try_from_events(
708 events: EntityEvents<DummyEntityEvent>,
709 ) -> Result<Self, EntityHydrationError> {
710 let name = events
711 .iter_persisted()
712 .map(|e| match &e.event {
713 DummyEntityEvent::Created(name) => name.clone(),
714 })
715 .next()
716 .expect("Could not find name");
717 Ok(Self { name, events })
718 }
719 }
720
721 struct NewDummyEntity {}
722
723 impl IntoEvents<DummyEntityEvent> for NewDummyEntity {
724 fn into_events(self) -> EntityEvents<DummyEntityEvent> {
725 EntityEvents::init(
726 Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
727 vec![DummyEntityEvent::Created("".to_owned())],
728 )
729 }
730 }
731
732 #[test]
733 fn load_zero_events() {
734 let generic_events: Vec<GenericEvent<Uuid>> = vec![];
735 let res = EntityEvents::load_first::<DummyEntity>(generic_events);
736 assert!(matches!(res, Ok(None)));
737 }
738
739 #[test]
740 fn load_first() {
741 let generic_events = vec![GenericEvent {
742 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
743 sequence: 1,
744 event: serde_json::to_value(DummyEntityEvent::Created("dummy-name".to_owned()))
745 .expect("Could not serialize"),
746 context: None,
747 recorded_at: chrono::Utc::now(),
748 forgettable_payload: None,
749 }];
750 let entity: DummyEntity = EntityEvents::load_first(generic_events)
751 .expect("Could not load")
752 .expect("No entity found");
753 assert!(entity.name == "dummy-name");
754 }
755
756 #[test]
757 fn load_n() {
758 let generic_events = vec![
759 GenericEvent {
760 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
761 sequence: 1,
762 event: serde_json::to_value(DummyEntityEvent::Created("dummy-name".to_owned()))
763 .expect("Could not serialize"),
764 context: None,
765 recorded_at: chrono::Utc::now(),
766 forgettable_payload: None,
767 },
768 GenericEvent {
769 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
770 sequence: 1,
771 event: serde_json::to_value(DummyEntityEvent::Created("other-name".to_owned()))
772 .expect("Could not serialize"),
773 context: None,
774 recorded_at: chrono::Utc::now(),
775 forgettable_payload: None,
776 },
777 ];
778 let (entity, more): (Vec<DummyEntity>, _) =
779 EntityEvents::load_n(generic_events, 2).expect("Could not load");
780 assert!(!more);
781 assert_eq!(entity.len(), 2);
782 }
783
784 #[test]
785 fn last_persisted_does_not_panic_when_n_exceeds_len() {
786 let generic_events = vec![GenericEvent {
787 entity_id: Uuid::parse_str("00000000-0000-0000-0000-000000000004").unwrap(),
788 sequence: 1,
789 event: serde_json::to_value(DummyEntityEvent::Created("dummy".to_owned()))
790 .expect("Could not serialize"),
791 context: None,
792 recorded_at: chrono::Utc::now(),
793 forgettable_payload: None,
794 }];
795 let entity: DummyEntity = EntityEvents::load_first(generic_events)
796 .expect("Could not load")
797 .expect("No entity found");
798 let events = entity.events();
799
800 assert_eq!(events.last_persisted(1).count(), 1);
802 assert_eq!(events.last_persisted(10).count(), 1);
804 assert_eq!(events.last_persisted(0).count(), 0);
806 }
807
808 proptest! {
809 #[test]
810 fn load_first_empty_input_returns_none(_ in Just(())) {
811 let res = EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(
812 Vec::<GenericEvent<Uuid>>::new(),
813 );
814 prop_assert!(matches!(res, Ok(None)));
815 }
816
817 #[test]
818 fn load_first_non_empty_valid_hydrates(
819 count in 1u8..8,
820 names in proptest::collection::vec(".{0,10}", 1..8),
821 ) {
822 let id = Uuid::nil();
823 let events: Vec<_> = (1..=count as i32)
824 .zip(names.iter())
825 .map(|(s, n)| valid_event(id, s, n))
826 .collect();
827 let res = EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(events);
828 prop_assert!(matches!(res, Ok(Some(_))));
829 }
830
831 #[test]
834 fn load_first_arbitrary_json_never_panics(
835 raw in proptest::collection::vec(json_value(), 0..10),
836 ) {
837 let events: Vec<_> = raw
838 .into_iter()
839 .enumerate()
840 .map(|(i, v)| GenericEvent {
841 entity_id: Uuid::nil(),
842 sequence: i as i32,
843 event: v,
844 context: None,
845 recorded_at: chrono::Utc::now(),
846 forgettable_payload: None,
847 })
848 .collect();
849 let is_empty = events.is_empty();
850 let res = EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(events);
851 if let Ok(None) = res {
852 prop_assert!(is_empty);
853 }
854 }
855
856 #[test]
860 fn load_n_respects_limit_and_more_flag(
861 k in 1u8..8,
862 per in 1u8..4,
863 n in 1u8..12,
864 ) {
865 let mut events = Vec::new();
866 for i in 0..k {
867 let id = Uuid::from_u128(i as u128);
868 for s in 1..=per as i32 {
869 events.push(valid_event(id, s, &format!("e{i}-{s}")));
870 }
871 }
872 let (entities, has_more) =
873 EntityEvents::<DummyEntityEvent>::load_n::<DummyEntity>(events, n as usize)
874 .expect("valid events hydrate");
875 prop_assert_eq!(entities.len(), (n as usize).min(k as usize));
876 prop_assert_eq!(has_more, n < k);
877 }
878
879 #[test]
883 fn load_n_zero_returns_no_entities(k in 0u8..5, per in 1u8..3) {
884 let mut events = Vec::new();
885 for i in 0..k {
886 let id = Uuid::from_u128(i as u128);
887 for s in 1..=per as i32 {
888 events.push(valid_event(id, s, &format!("e{i}-{s}")));
889 }
890 }
891 let (entities, has_more) =
892 EntityEvents::<DummyEntityEvent>::load_n::<DummyEntity>(events, 0)
893 .expect("valid events hydrate");
894 prop_assert!(entities.is_empty());
895 prop_assert_eq!(has_more, k > 0);
896 }
897
898 #[test]
899 fn load_n_arbitrary_json_never_panics(
900 raw in proptest::collection::vec(json_value(), 0..10),
901 n in 0u8..12,
902 ) {
903 let events: Vec<_> = raw
904 .into_iter()
905 .enumerate()
906 .map(|(i, v)| GenericEvent {
907 entity_id: Uuid::nil(),
908 sequence: i as i32,
909 event: v,
910 context: None,
911 recorded_at: chrono::Utc::now(),
912 forgettable_payload: None,
913 })
914 .collect();
915 let _ = EntityEvents::<DummyEntityEvent>::load_n::<DummyEntity>(events, n as usize);
916 }
917
918 #[test]
921 fn last_persisted_clamps_for_any_n(
922 p in 1u8..8,
923 n in 0u16..12,
924 ) {
925 let id = Uuid::nil();
926 let events: Vec<_> = (1..=p as i32)
927 .map(|s| valid_event(id, s, &format!("n{s}")))
928 .collect();
929 let entity: DummyEntity =
930 EntityEvents::<DummyEntityEvent>::load_first::<DummyEntity>(events)
931 .expect("load")
932 .expect("some");
933 let count = entity.events().last_persisted(n as usize).count();
934 prop_assert_eq!(count, (n as usize).min(p as usize));
935 }
936
937 #[test]
941 fn mark_new_events_assigns_contiguous_sequences(
942 a in 0u8..5,
943 b in 0u8..5,
944 ) {
945 let id = Uuid::nil();
946 let mut events = EntityEvents::init(
947 id,
948 (0..a).map(|i| DummyEntityEvent::Created(format!("n{i}"))),
949 );
950 let now = chrono::Utc::now();
951
952 prop_assert_eq!(events.mark_new_events_persisted_at(now), a as usize);
953 prop_assert!(!events.any_new());
954 prop_assert_eq!(events.len_persisted(), a as usize);
955 let seqs: Vec<usize> = events.iter_persisted().map(|e| e.sequence).collect();
956 prop_assert_eq!(seqs, (1..=a as usize).collect::<Vec<_>>());
957
958 for i in 0..b {
959 events.push(DummyEntityEvent::Created(format!("m{i}")));
960 }
961 if b > 0 {
962 prop_assert!(events.any_new());
963 }
964 prop_assert_eq!(events.mark_new_events_persisted_at(now), b as usize);
965 prop_assert!(!events.any_new());
966 prop_assert_eq!(events.len_persisted(), (a + b) as usize);
967 let seqs: Vec<usize> = events.iter_persisted().map(|e| e.sequence).collect();
968 prop_assert_eq!(seqs, (1..=(a + b) as usize).collect::<Vec<_>>());
969 }
970 }
971}