Skip to main content

oxihuman_core/
error_audit_log.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// A single audit event.
6pub struct AuditEventEntry {
7    pub timestamp_ms: u64,
8    pub actor: String,
9    pub action: String,
10}
11
12/// Append-only audit event log.
13pub struct AuditEventLog {
14    pub entries: Vec<AuditEventEntry>,
15}
16
17impl AuditEventLog {
18    pub fn new() -> Self {
19        AuditEventLog {
20            entries: Vec::new(),
21        }
22    }
23}
24
25impl Default for AuditEventLog {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31pub fn new_audit_event_log() -> AuditEventLog {
32    AuditEventLog::new()
33}
34
35pub fn audit_event_record(log: &mut AuditEventLog, ts: u64, actor: &str, action: &str) {
36    log.entries.push(AuditEventEntry {
37        timestamp_ms: ts,
38        actor: actor.to_string(),
39        action: action.to_string(),
40    });
41}
42
43pub fn audit_event_count(log: &AuditEventLog) -> usize {
44    log.entries.len()
45}
46
47pub fn audit_event_last(log: &AuditEventLog) -> Option<&AuditEventEntry> {
48    log.entries.last()
49}
50
51pub fn audit_event_clear(log: &mut AuditEventLog) {
52    log.entries.clear();
53}
54
55pub fn audit_event_by_actor<'a>(log: &'a AuditEventLog, actor: &str) -> Vec<&'a AuditEventEntry> {
56    log.entries.iter().filter(|e| e.actor == actor).collect()
57}
58
59pub fn audit_event_since(log: &AuditEventLog, ts: u64) -> Vec<&AuditEventEntry> {
60    log.entries
61        .iter()
62        .filter(|e| e.timestamp_ms >= ts)
63        .collect()
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_new_empty() {
72        /* new log has no entries */
73        let log = new_audit_event_log();
74        assert_eq!(audit_event_count(&log), 0);
75    }
76
77    #[test]
78    fn test_record_and_count() {
79        /* recording increments count */
80        let mut log = new_audit_event_log();
81        audit_event_record(&mut log, 1000, "alice", "login");
82        assert_eq!(audit_event_count(&log), 1);
83    }
84
85    #[test]
86    fn test_last_entry() {
87        /* last returns most recent entry */
88        let mut log = new_audit_event_log();
89        audit_event_record(&mut log, 100, "bob", "create");
90        audit_event_record(&mut log, 200, "alice", "delete");
91        let last = audit_event_last(&log).expect("should succeed");
92        assert_eq!(last.actor, "alice");
93        assert_eq!(last.action, "delete");
94    }
95
96    #[test]
97    fn test_clear() {
98        /* clear removes all entries */
99        let mut log = new_audit_event_log();
100        audit_event_record(&mut log, 1, "x", "y");
101        audit_event_clear(&mut log);
102        assert_eq!(audit_event_count(&log), 0);
103    }
104
105    #[test]
106    fn test_by_actor() {
107        /* by_actor filters correctly */
108        let mut log = new_audit_event_log();
109        audit_event_record(&mut log, 1, "alice", "a");
110        audit_event_record(&mut log, 2, "bob", "b");
111        audit_event_record(&mut log, 3, "alice", "c");
112        let alice_events = audit_event_by_actor(&log, "alice");
113        assert_eq!(alice_events.len(), 2);
114    }
115
116    #[test]
117    fn test_since() {
118        /* since filters by timestamp */
119        let mut log = new_audit_event_log();
120        audit_event_record(&mut log, 100, "x", "a");
121        audit_event_record(&mut log, 200, "x", "b");
122        audit_event_record(&mut log, 300, "x", "c");
123        let recent = audit_event_since(&log, 200);
124        assert_eq!(recent.len(), 2);
125    }
126
127    #[test]
128    fn test_last_empty() {
129        /* last on empty log returns None */
130        let log = new_audit_event_log();
131        assert!(audit_event_last(&log).is_none());
132    }
133
134    #[test]
135    fn test_multiple_actors() {
136        /* by_actor returns empty for unknown actor */
137        let mut log = new_audit_event_log();
138        audit_event_record(&mut log, 1, "alice", "x");
139        let result = audit_event_by_actor(&log, "ghost");
140        assert!(result.is_empty());
141    }
142}