1use std::sync::Arc;
3
4use crate::{
5 Clock, EventCounterConfig, EventStore, IntervalConfig, Result, Storage, SystemClock, TimeUnit,
6};
7
8pub struct EventStoreBuilder {
27 clock: Option<Arc<dyn Clock>>,
28 storage: Option<Box<dyn Storage>>,
29 formatter: Option<Arc<dyn crate::Formatter>>,
30 configs: Vec<IntervalConfig>,
31 #[cfg(feature = "tokio")]
32 auto_persist_interval: Option<chrono::Duration>,
33}
34
35impl EventStoreBuilder {
36 pub(crate) fn new() -> Self {
38 Self {
39 clock: None,
40 storage: None,
41 formatter: None,
42 configs: Vec::new(),
43 #[cfg(feature = "tokio")]
44 auto_persist_interval: None,
45 }
46 }
47
48 pub fn with_clock(mut self, clock: Arc<dyn Clock>) -> Self {
54 self.clock = Some(clock);
55 self
56 }
57
58 pub fn with_storage(mut self, storage: impl Storage + 'static) -> Self {
62 self.storage = Some(Box::new(storage));
63 self
64 }
65
66 pub fn with_format(mut self, formatter: impl crate::Formatter + 'static) -> Self {
70 self.formatter = Some(Arc::new(formatter));
71 self
72 }
73
74 pub fn track_seconds(mut self, count: usize) -> Self {
76 self.configs
77 .push(IntervalConfig::new_unchecked(count, TimeUnit::Seconds));
78 self
79 }
80
81 pub fn track_minutes(mut self, count: usize) -> Self {
83 self.configs
84 .push(IntervalConfig::new_unchecked(count, TimeUnit::Minutes));
85 self
86 }
87
88 pub fn track_hours(mut self, count: usize) -> Self {
90 self.configs
91 .push(IntervalConfig::new_unchecked(count, TimeUnit::Hours));
92 self
93 }
94
95 pub fn track_days(mut self, count: usize) -> Self {
97 self.configs
98 .push(IntervalConfig::new_unchecked(count, TimeUnit::Days));
99 self
100 }
101
102 pub fn track_weeks(mut self, count: usize) -> Self {
104 self.configs
105 .push(IntervalConfig::new_unchecked(count, TimeUnit::Weeks));
106 self
107 }
108
109 pub fn track_months(mut self, count: usize) -> Self {
111 self.configs
112 .push(IntervalConfig::new_unchecked(count, TimeUnit::Months));
113 self
114 }
115
116 pub fn track_years(mut self, count: usize) -> Self {
118 self.configs
119 .push(IntervalConfig::new_unchecked(count, TimeUnit::Years));
120 self
121 }
122
123 pub fn for_rate_limiting(mut self) -> Self {
127 self.configs = vec![
128 IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
129 IntervalConfig::new_unchecked(72, TimeUnit::Hours),
130 ];
131 self
132 }
133
134 pub fn for_analytics(mut self) -> Self {
138 self.configs = vec![
139 IntervalConfig::new_unchecked(56, TimeUnit::Days),
140 IntervalConfig::new_unchecked(52, TimeUnit::Weeks),
141 IntervalConfig::new_unchecked(12, TimeUnit::Months),
142 ];
143 self
144 }
145
146 #[cfg(feature = "tokio")]
166 pub fn auto_persist(mut self, interval: chrono::Duration) -> Self {
167 self.auto_persist_interval = Some(interval);
168 self
169 }
170
171 pub fn build(self) -> Result<EventStore> {
185 #[cfg(feature = "tokio")]
187 if let Some(interval) = self.auto_persist_interval {
188 if interval.num_milliseconds() <= 0 {
189 return Err(crate::error::Error::InvalidAutoPersistInterval(interval));
190 }
191 if self.storage.is_none() {
193 return Err(crate::error::Error::AutoPersistRequiresStorage);
194 }
195 }
196
197 let clock = self.clock.unwrap_or_else(|| SystemClock::new());
198 let storage = self.storage;
199
200 let formatter = if storage.is_some() {
202 match self.formatter {
203 Some(f) => Some(f),
204 None => {
205 #[cfg(feature = "serde-bincode")]
207 {
208 Some(Arc::new(crate::formatter::BincodeFormat) as Arc<dyn crate::Formatter>)
209 }
210 #[cfg(all(feature = "serde-json", not(feature = "serde-bincode")))]
211 {
212 Some(Arc::new(crate::formatter::JsonFormat) as Arc<dyn crate::Formatter>)
213 }
214 #[cfg(not(any(feature = "serde-bincode", feature = "serde-json")))]
215 {
216 return Err(crate::error::Error::NoFormatterForStorage);
217 }
218 }
219 }
220 } else {
221 None
222 };
223
224 let config = if self.configs.is_empty() {
225 EventCounterConfig::default()
227 } else {
228 for config in &self.configs {
230 config.validate()?;
231 }
232 EventCounterConfig::new(self.configs)
233 };
234
235 #[allow(unused_mut)] let mut base_store = super::EventStore::from_parts(clock, storage, formatter, config);
238
239 #[cfg(feature = "tokio")]
241 {
242 if let Some(_interval) = self.auto_persist_interval {
243 let handle =
244 super::EventStore::spawn_auto_persist(base_store.inner.clone(), _interval);
245 base_store.auto_persist_handle = Some(handle);
246 }
247 }
248
249 Ok(base_store)
251 }
252}
253
254impl Default for EventStoreBuilder {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl EventStore {
261 pub fn builder() -> EventStoreBuilder {
263 EventStoreBuilder::new()
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 use crate::storage::MemoryStorage;
272 use crate::TestClock;
273
274 #[test]
275 fn test_new_builder() {
276 let builder = EventStoreBuilder::new();
277 assert!(builder.clock.is_none());
278 assert!(builder.storage.is_none());
279 assert_eq!(builder.configs.len(), 0);
280 }
281
282 #[test]
283 fn test_default_builder() {
284 let builder = EventStoreBuilder::default();
285 assert!(builder.clock.is_none());
286 assert!(builder.storage.is_none());
287 assert_eq!(builder.configs.len(), 0);
288 }
289
290 #[test]
291 fn test_with_clock() {
292 let clock = TestClock::new();
293 let builder = EventStoreBuilder::new().with_clock(clock);
294 assert!(builder.clock.is_some());
295 }
296
297 #[test]
298 fn test_with_storage() {
299 let storage = MemoryStorage::new();
300 let builder = EventStoreBuilder::new().with_storage(storage);
301 assert!(builder.storage.is_some());
302 }
303
304 #[test]
305 fn test_track_minutes() {
306 let builder = EventStoreBuilder::new().track_minutes(60);
307 assert_eq!(builder.configs.len(), 1);
308 assert_eq!(builder.configs[0].bucket_count(), 60);
309 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Minutes);
310 }
311
312 #[test]
313 fn test_track_hours() {
314 let builder = EventStoreBuilder::new().track_hours(72);
315 assert_eq!(builder.configs.len(), 1);
316 assert_eq!(builder.configs[0].bucket_count(), 72);
317 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Hours);
318 }
319
320 #[test]
321 fn test_track_days() {
322 let builder = EventStoreBuilder::new().track_days(56);
323 assert_eq!(builder.configs.len(), 1);
324 assert_eq!(builder.configs[0].bucket_count(), 56);
325 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Days);
326 }
327
328 #[test]
329 fn test_track_weeks() {
330 let builder = EventStoreBuilder::new().track_weeks(52);
331 assert_eq!(builder.configs.len(), 1);
332 assert_eq!(builder.configs[0].bucket_count(), 52);
333 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Weeks);
334 }
335
336 #[test]
337 fn test_track_months() {
338 let builder = EventStoreBuilder::new().track_months(12);
339 assert_eq!(builder.configs.len(), 1);
340 assert_eq!(builder.configs[0].bucket_count(), 12);
341 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Months);
342 }
343
344 #[test]
345 fn test_track_years() {
346 let builder = EventStoreBuilder::new().track_years(4);
347 assert_eq!(builder.configs.len(), 1);
348 assert_eq!(builder.configs[0].bucket_count(), 4);
349 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Years);
350 }
351
352 #[test]
353 fn test_for_rate_limiting_preset() {
354 let builder = EventStoreBuilder::new().for_rate_limiting();
355 assert_eq!(builder.configs.len(), 2);
356
357 assert_eq!(builder.configs[0].bucket_count(), 60);
359 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Minutes);
360
361 assert_eq!(builder.configs[1].bucket_count(), 72);
363 assert_eq!(builder.configs[1].time_unit(), TimeUnit::Hours);
364 }
365
366 #[test]
367 fn test_for_analytics_preset() {
368 let builder = EventStoreBuilder::new().for_analytics();
369 assert_eq!(builder.configs.len(), 3);
370
371 assert_eq!(builder.configs[0].bucket_count(), 56);
373 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Days);
374
375 assert_eq!(builder.configs[1].bucket_count(), 52);
377 assert_eq!(builder.configs[1].time_unit(), TimeUnit::Weeks);
378
379 assert_eq!(builder.configs[2].bucket_count(), 12);
381 assert_eq!(builder.configs[2].time_unit(), TimeUnit::Months);
382 }
383
384 #[test]
385 fn test_build_default() {
386 let store = EventStoreBuilder::new().build().unwrap();
387
388 let intervals = store.tracked_intervals();
390 assert_eq!(intervals.len(), 6);
391
392 assert!(intervals.contains(&(TimeUnit::Minutes, 60)));
394 assert!(intervals.contains(&(TimeUnit::Hours, 72)));
395 assert!(intervals.contains(&(TimeUnit::Days, 56)));
396 assert!(intervals.contains(&(TimeUnit::Weeks, 52)));
397 assert!(intervals.contains(&(TimeUnit::Months, 12)));
398 assert!(intervals.contains(&(TimeUnit::Years, 4)));
399 }
400
401 #[test]
402 fn test_build_with_custom_config() {
403 let store = EventStoreBuilder::new()
404 .track_minutes(30)
405 .track_hours(24)
406 .build()
407 .unwrap();
408
409 let intervals = store.tracked_intervals();
410 assert_eq!(intervals.len(), 2);
411 assert!(intervals.contains(&(TimeUnit::Minutes, 30)));
412 assert!(intervals.contains(&(TimeUnit::Hours, 24)));
413 }
414
415 #[test]
416 fn test_build_with_rate_limiting_preset() {
417 let store = EventStoreBuilder::new()
418 .for_rate_limiting()
419 .build()
420 .unwrap();
421
422 let intervals = store.tracked_intervals();
423 assert_eq!(intervals.len(), 2);
424 assert!(intervals.contains(&(TimeUnit::Minutes, 60)));
425 assert!(intervals.contains(&(TimeUnit::Hours, 72)));
426 }
427
428 #[test]
429 fn test_build_with_analytics_preset() {
430 let store = EventStoreBuilder::new().for_analytics().build().unwrap();
431
432 let intervals = store.tracked_intervals();
433 assert_eq!(intervals.len(), 3);
434 assert!(intervals.contains(&(TimeUnit::Days, 56)));
435 assert!(intervals.contains(&(TimeUnit::Weeks, 52)));
436 assert!(intervals.contains(&(TimeUnit::Months, 12)));
437 }
438
439 #[test]
440 fn test_build_with_clock() {
441 let test_clock = TestClock::new();
442 let _expected_time = test_clock.now();
443
444 let store = EventStoreBuilder::new()
445 .with_clock(test_clock)
446 .build()
447 .unwrap();
448
449 store.record("test");
451 assert_eq!(store.query("test").last_days(1).sum(), Some(1));
452 }
453
454 #[test]
455 #[cfg(any(feature = "serde-bincode", feature = "serde-json"))]
456 fn test_build_with_storage() {
457 let storage = MemoryStorage::new();
458 let store = EventStoreBuilder::new()
459 .with_storage(storage)
460 .build()
461 .unwrap();
462
463 assert_eq!(store.tracked_intervals().len(), 6);
466 }
467
468 #[test]
469 #[cfg(any(feature = "serde-bincode", feature = "serde-json"))]
470 fn test_build_with_all_options() {
471 let test_clock = TestClock::new();
472 let storage = MemoryStorage::new();
473
474 let store = EventStoreBuilder::new()
475 .with_clock(test_clock)
476 .with_storage(storage)
477 .track_minutes(120)
478 .track_hours(48)
479 .build()
480 .unwrap();
481
482 let intervals = store.tracked_intervals();
483 assert_eq!(intervals.len(), 2);
484 assert!(intervals.contains(&(TimeUnit::Minutes, 120)));
485 assert!(intervals.contains(&(TimeUnit::Hours, 48)));
486 }
487
488 #[test]
489 fn test_event_store_builder_method() {
490 let builder = EventStore::builder();
491 assert!(builder.clock.is_none());
492 assert!(builder.storage.is_none());
493 assert_eq!(builder.configs.len(), 0);
494 }
495
496 #[test]
497 fn test_builder_fluent_api() {
498 let store = EventStore::builder()
500 .track_minutes(60)
501 .track_hours(24)
502 .track_days(7)
503 .build()
504 .unwrap();
505
506 let intervals = store.tracked_intervals();
507 assert_eq!(intervals.len(), 3);
508 }
509
510 #[test]
511 fn test_preset_overwrites_previous_config() {
512 let builder = EventStoreBuilder::new()
514 .track_minutes(30)
515 .track_hours(12)
516 .for_rate_limiting();
517
518 assert_eq!(builder.configs.len(), 2);
520 assert_eq!(builder.configs[0].bucket_count(), 60);
521 assert_eq!(builder.configs[1].bucket_count(), 72);
522 }
523
524 #[test]
525 fn test_custom_config_after_preset() {
526 let builder = EventStoreBuilder::new().for_rate_limiting().track_days(30);
528
529 assert_eq!(builder.configs.len(), 3);
531 assert_eq!(builder.configs[0].time_unit(), TimeUnit::Minutes);
532 assert_eq!(builder.configs[1].time_unit(), TimeUnit::Hours);
533 assert_eq!(builder.configs[2].time_unit(), TimeUnit::Days);
534 }
535
536 #[test]
538 fn test_track_minutes_rejects_zero() {
539 let result = EventStoreBuilder::new().track_minutes(0).build();
540 assert!(result.is_err());
541 if let Err(e) = result {
542 assert!(e.to_string().contains("bucket count"));
543 }
544 }
545
546 #[test]
547 fn test_track_minutes_rejects_too_large() {
548 let result = EventStoreBuilder::new().track_minutes(100_001).build();
549 assert!(result.is_err());
550 if let Err(e) = result {
551 assert!(e.to_string().contains("bucket count"));
552 }
553 }
554
555 #[test]
556 fn test_track_hours_rejects_zero() {
557 let result = EventStoreBuilder::new().track_hours(0).build();
558 assert!(result.is_err());
559 if let Err(e) = result {
560 assert!(e.to_string().contains("bucket count"));
561 }
562 }
563
564 #[test]
565 fn test_track_hours_rejects_too_large() {
566 let result = EventStoreBuilder::new().track_hours(100_001).build();
567 assert!(result.is_err());
568 if let Err(e) = result {
569 assert!(e.to_string().contains("bucket count"));
570 }
571 }
572
573 #[test]
574 fn test_track_days_rejects_zero() {
575 let result = EventStoreBuilder::new().track_days(0).build();
576 assert!(result.is_err());
577 if let Err(e) = result {
578 assert!(e.to_string().contains("bucket count"));
579 }
580 }
581
582 #[test]
583 fn test_track_days_rejects_too_large() {
584 let result = EventStoreBuilder::new().track_days(100_001).build();
585 assert!(result.is_err());
586 if let Err(e) = result {
587 assert!(e.to_string().contains("bucket count"));
588 }
589 }
590
591 #[test]
592 fn test_track_weeks_rejects_zero() {
593 let result = EventStoreBuilder::new().track_weeks(0).build();
594 assert!(result.is_err());
595 if let Err(e) = result {
596 assert!(e.to_string().contains("bucket count"));
597 }
598 }
599
600 #[test]
601 fn test_track_weeks_rejects_too_large() {
602 let result = EventStoreBuilder::new().track_weeks(100_001).build();
603 assert!(result.is_err());
604 if let Err(e) = result {
605 assert!(e.to_string().contains("bucket count"));
606 }
607 }
608
609 #[test]
610 fn test_track_months_rejects_zero() {
611 let result = EventStoreBuilder::new().track_months(0).build();
612 assert!(result.is_err());
613 if let Err(e) = result {
614 assert!(e.to_string().contains("bucket count"));
615 }
616 }
617
618 #[test]
619 fn test_track_months_rejects_too_large() {
620 let result = EventStoreBuilder::new().track_months(100_001).build();
621 assert!(result.is_err());
622 if let Err(e) = result {
623 assert!(e.to_string().contains("bucket count"));
624 }
625 }
626
627 #[test]
628 fn test_track_years_rejects_zero() {
629 let result = EventStoreBuilder::new().track_years(0).build();
630 assert!(result.is_err());
631 if let Err(e) = result {
632 assert!(e.to_string().contains("bucket count"));
633 }
634 }
635
636 #[test]
637 fn test_track_years_rejects_too_large() {
638 let result = EventStoreBuilder::new().track_years(100_001).build();
639 assert!(result.is_err());
640 if let Err(e) = result {
641 assert!(e.to_string().contains("bucket count"));
642 }
643 }
644
645 #[test]
646 fn test_track_minutes_accepts_valid_values() {
647 let result = EventStoreBuilder::new().track_minutes(1).build();
648 assert!(result.is_ok());
649
650 let result = EventStoreBuilder::new().track_minutes(100_000).build();
651 assert!(result.is_ok());
652 }
653
654 #[test]
655 fn test_track_hours_accepts_valid_values() {
656 let result = EventStoreBuilder::new().track_hours(1).build();
657 assert!(result.is_ok());
658
659 let result = EventStoreBuilder::new().track_hours(100_000).build();
660 assert!(result.is_ok());
661 }
662
663 #[cfg(feature = "tokio")]
664 #[test]
665 fn test_auto_persist_method_exists() {
666 use chrono::Duration;
667
668 let builder = EventStoreBuilder::new().auto_persist(Duration::seconds(60));
669
670 assert!(builder.auto_persist_interval.is_some());
671 assert_eq!(
672 builder.auto_persist_interval.unwrap(),
673 Duration::seconds(60)
674 );
675 }
676
677 #[cfg(all(feature = "tokio", feature = "serde"))]
679 #[tokio::test]
680 async fn test_unified_build_without_auto_persist() {
681 let store = EventStoreBuilder::new()
682 .with_storage(MemoryStorage::new())
683 .build()
684 .unwrap();
685
686 store.record("test");
688 assert_eq!(store.query("test").last_days(1).sum(), Some(1));
689 }
690
691 #[cfg(all(feature = "tokio", feature = "serde"))]
692 #[tokio::test]
693 async fn test_unified_build_with_auto_persist() {
694 use chrono::Duration;
695
696 let store = EventStoreBuilder::new()
697 .with_storage(MemoryStorage::new())
698 .auto_persist(Duration::milliseconds(50))
699 .build()
700 .unwrap();
701
702 store.record("test");
704 assert_eq!(store.query("test").last_days(1).sum(), Some(1));
705
706 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
708 assert!(!store.is_dirty());
709 }
710
711 #[cfg(all(feature = "tokio", feature = "serde"))]
712 #[tokio::test]
713 async fn test_unified_build_auto_persist_no_locking_needed() {
714 use chrono::Duration;
715
716 let store = EventStoreBuilder::new()
717 .with_storage(MemoryStorage::new())
718 .auto_persist(Duration::milliseconds(50))
719 .build()
720 .unwrap();
721
722 store.record("event1");
724 store.record("event2");
725 store.record("event3");
726
727 assert_eq!(store.query("event1").last_days(1).sum(), Some(1));
729 assert_eq!(store.query("event2").last_days(1).sum(), Some(1));
730 assert_eq!(store.query("event3").last_days(1).sum(), Some(1));
731 }
732
733 #[cfg(feature = "serde-bincode")]
735 #[test]
736 fn test_with_format_bincode() {
737 use crate::formatter::BincodeFormat;
738
739 let builder = EventStoreBuilder::new()
740 .with_storage(MemoryStorage::new())
741 .with_format(BincodeFormat);
742
743 assert!(builder.formatter.is_some());
744 }
745
746 #[cfg(feature = "serde-json")]
747 #[test]
748 fn test_with_format_json() {
749 use crate::formatter::JsonFormat;
750
751 let builder = EventStoreBuilder::new()
752 .with_storage(MemoryStorage::new())
753 .with_format(JsonFormat);
754
755 assert!(builder.formatter.is_some());
756 }
757
758 #[cfg(feature = "serde-bincode")]
759 #[test]
760 fn test_default_formatter_is_bincode() {
761 let store = EventStoreBuilder::new()
762 .with_storage(MemoryStorage::new())
763 .build()
764 .unwrap();
765
766 store.record("test");
768 assert_eq!(store.query("test").last_days(1).sum(), Some(1));
769 }
770
771 #[cfg(all(feature = "serde-bincode", feature = "serde"))]
772 #[test]
773 fn test_formatter_used_in_persistence() {
774 use crate::formatter::BincodeFormat;
775
776 let store = EventStoreBuilder::new()
777 .with_storage(MemoryStorage::new())
778 .with_format(BincodeFormat)
779 .build()
780 .unwrap();
781
782 store.record("event1");
783 store.record("event2");
784
785 let result = store.persist();
787 assert!(result.is_ok());
788 }
789
790 #[cfg(all(feature = "serde-json", feature = "serde"))]
791 #[test]
792 fn test_json_formatter_used_in_persistence() {
793 use crate::formatter::JsonFormat;
794
795 let store = EventStoreBuilder::new()
796 .with_storage(MemoryStorage::new())
797 .with_format(JsonFormat)
798 .build()
799 .unwrap();
800
801 store.record("event1");
802 store.record("event2");
803
804 let result = store.persist();
806 assert!(result.is_ok());
807 }
808
809 #[test]
810 #[cfg(feature = "tokio")]
811 fn test_auto_persist_negative_interval_fails() {
812 use chrono::Duration;
813
814 let result = EventStoreBuilder::new()
815 .with_storage(MemoryStorage::new())
816 .auto_persist(Duration::seconds(-60))
817 .build();
818
819 assert!(result.is_err());
820 match result {
821 Err(crate::error::Error::InvalidAutoPersistInterval(d)) => {
822 assert_eq!(d.num_seconds(), -60);
823 }
824 _ => panic!("Expected InvalidAutoPersistInterval error"),
825 }
826 }
827
828 #[test]
829 #[cfg(feature = "tokio")]
830 fn test_auto_persist_zero_interval_fails() {
831 use chrono::Duration;
832
833 let result = EventStoreBuilder::new()
834 .with_storage(MemoryStorage::new())
835 .auto_persist(Duration::zero())
836 .build();
837
838 assert!(result.is_err());
839 match result {
840 Err(crate::error::Error::InvalidAutoPersistInterval(d)) => {
841 assert!(d.num_milliseconds() <= 0);
842 }
843 _ => panic!("Expected InvalidAutoPersistInterval error"),
844 }
845 }
846}