Skip to main content

oxihuman_core/
domain_event.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5pub struct DomainEvent {
6    pub id: u64,
7    pub aggregate_id: u64,
8    pub event_type: String,
9    pub payload: String,
10    pub timestamp_ms: u64,
11}
12
13pub fn new_domain_event(
14    id: u64,
15    agg_id: u64,
16    event_type: &str,
17    payload: &str,
18    ts: u64,
19) -> DomainEvent {
20    DomainEvent {
21        id,
22        aggregate_id: agg_id,
23        event_type: event_type.to_string(),
24        payload: payload.to_string(),
25        timestamp_ms: ts,
26    }
27}
28
29pub fn event_to_json(e: &DomainEvent) -> String {
30    format!(
31        "{{\"id\":{},\"aggregate_id\":{},\"event_type\":\"{}\",\"payload\":\"{}\",\"timestamp_ms\":{}}}",
32        e.id, e.aggregate_id, e.event_type, e.payload, e.timestamp_ms
33    )
34}
35
36pub fn event_is_type(e: &DomainEvent, t: &str) -> bool {
37    e.event_type == t
38}
39
40pub fn events_by_aggregate(events: &[DomainEvent], agg_id: u64) -> Vec<&DomainEvent> {
41    events.iter().filter(|e| e.aggregate_id == agg_id).collect()
42}
43
44pub fn events_after(events: &[DomainEvent], ts: u64) -> Vec<&DomainEvent> {
45    events.iter().filter(|e| e.timestamp_ms > ts).collect()
46}
47
48pub fn events_of_type<'a>(events: &'a [DomainEvent], t: &str) -> Vec<&'a DomainEvent> {
49    events.iter().filter(|e| e.event_type == t).collect()
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_new_domain_event() {
58        /* create domain event with all fields */
59        let e = new_domain_event(1, 10, "Created", "{}", 1000);
60        assert_eq!(e.id, 1);
61        assert_eq!(e.aggregate_id, 10);
62        assert!(event_is_type(&e, "Created"));
63    }
64
65    #[test]
66    fn test_event_to_json() {
67        /* serializes to JSON string */
68        let e = new_domain_event(1, 2, "Test", "data", 500);
69        let j = event_to_json(&e);
70        assert!(j.contains("\"id\":1"));
71        assert!(j.contains("\"event_type\":\"Test\""));
72    }
73
74    #[test]
75    fn test_events_by_aggregate() {
76        /* filter by aggregate_id */
77        let events = vec![
78            new_domain_event(1, 10, "A", "", 100),
79            new_domain_event(2, 20, "B", "", 200),
80            new_domain_event(3, 10, "C", "", 300),
81        ];
82        let r = events_by_aggregate(&events, 10);
83        assert_eq!(r.len(), 2);
84    }
85
86    #[test]
87    fn test_events_after() {
88        /* filter by timestamp */
89        let events = vec![
90            new_domain_event(1, 1, "A", "", 100),
91            new_domain_event(2, 1, "B", "", 200),
92            new_domain_event(3, 1, "C", "", 300),
93        ];
94        let r = events_after(&events, 150);
95        assert_eq!(r.len(), 2);
96    }
97
98    #[test]
99    fn test_events_of_type() {
100        /* filter by event type */
101        let events = vec![
102            new_domain_event(1, 1, "Created", "", 100),
103            new_domain_event(2, 1, "Updated", "", 200),
104            new_domain_event(3, 1, "Created", "", 300),
105        ];
106        let r = events_of_type(&events, "Created");
107        assert_eq!(r.len(), 2);
108    }
109
110    #[test]
111    fn test_event_is_type_false() {
112        /* wrong type returns false */
113        let e = new_domain_event(1, 1, "A", "", 0);
114        assert!(!event_is_type(&e, "B"));
115    }
116
117    #[test]
118    fn test_events_empty() {
119        /* empty slice returns empty results */
120        let events: Vec<DomainEvent> = vec![];
121        assert!(events_by_aggregate(&events, 1).is_empty());
122    }
123}