Skip to main content

oxihuman_core/
message_log.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Message log: tagged message queue with filtering and JSON export.
6
7/// Message priority.
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
9#[allow(dead_code)]
10pub enum MsgPriority {
11    Low,
12    Normal,
13    High,
14}
15
16/// A single log message.
17#[derive(Debug, Clone)]
18#[allow(dead_code)]
19pub struct Message {
20    pub id: u64,
21    pub tag: String,
22    pub text: String,
23    pub priority: MsgPriority,
24}
25
26/// Message log.
27#[derive(Debug)]
28#[allow(dead_code)]
29pub struct MessageLog {
30    messages: Vec<Message>,
31    next_id: u64,
32    capacity: usize,
33}
34
35/// Create a new MessageLog with given capacity.
36#[allow(dead_code)]
37pub fn new_message_log(capacity: usize) -> MessageLog {
38    MessageLog {
39        messages: Vec::new(),
40        next_id: 1,
41        capacity,
42    }
43}
44
45/// Append a message; evicts oldest if at capacity.
46#[allow(dead_code)]
47pub fn ml_push(log: &mut MessageLog, tag: &str, text: &str, priority: MsgPriority) -> u64 {
48    if log.messages.len() >= log.capacity && !log.messages.is_empty() {
49        log.messages.remove(0);
50    }
51    let id = log.next_id;
52    log.next_id += 1;
53    log.messages.push(Message {
54        id,
55        tag: tag.to_string(),
56        text: text.to_string(),
57        priority,
58    });
59    id
60}
61
62/// Get message by id.
63#[allow(dead_code)]
64pub fn ml_get(log: &MessageLog, id: u64) -> Option<&Message> {
65    log.messages.iter().find(|m| m.id == id)
66}
67
68/// Messages with a given tag.
69#[allow(dead_code)]
70pub fn ml_by_tag<'a>(log: &'a MessageLog, tag: &str) -> Vec<&'a Message> {
71    log.messages.iter().filter(|m| m.tag == tag).collect()
72}
73
74/// Messages at or above a priority.
75#[allow(dead_code)]
76pub fn ml_by_priority(log: &MessageLog, min: MsgPriority) -> Vec<&Message> {
77    log.messages.iter().filter(|m| m.priority >= min).collect()
78}
79
80/// Total message count.
81#[allow(dead_code)]
82pub fn ml_len(log: &MessageLog) -> usize {
83    log.messages.len()
84}
85
86/// Clear all messages.
87#[allow(dead_code)]
88pub fn ml_clear(log: &mut MessageLog) {
89    log.messages.clear();
90}
91
92/// Latest message.
93#[allow(dead_code)]
94pub fn ml_last(log: &MessageLog) -> Option<&Message> {
95    log.messages.last()
96}
97
98/// Serialize to JSON.
99#[allow(dead_code)]
100pub fn ml_to_json(log: &MessageLog) -> String {
101    let items: Vec<String> = log
102        .messages
103        .iter()
104        .map(|m| format!(r#"{{"id":{},"tag":"{}","text":"{}"}}"#, m.id, m.tag, m.text))
105        .collect();
106    format!("[{}]", items.join(","))
107}
108
109/// Remove messages with a given tag.
110#[allow(dead_code)]
111pub fn ml_remove_tag(log: &mut MessageLog, tag: &str) {
112    log.messages.retain(|m| m.tag != tag);
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_push_and_len() {
121        let mut log = new_message_log(10);
122        ml_push(&mut log, "sys", "hello", MsgPriority::Normal);
123        assert_eq!(ml_len(&log), 1);
124    }
125
126    #[test]
127    fn test_capacity_eviction() {
128        let mut log = new_message_log(3);
129        let id1 = ml_push(&mut log, "a", "1", MsgPriority::Low);
130        ml_push(&mut log, "b", "2", MsgPriority::Low);
131        ml_push(&mut log, "c", "3", MsgPriority::Low);
132        ml_push(&mut log, "d", "4", MsgPriority::Low);
133        assert_eq!(ml_len(&log), 3);
134        assert!(ml_get(&log, id1).is_none());
135    }
136
137    #[test]
138    fn test_get_by_id() {
139        let mut log = new_message_log(10);
140        let id = ml_push(&mut log, "net", "connect", MsgPriority::High);
141        assert!(ml_get(&log, id).is_some_and(|m| m.tag == "net"));
142    }
143
144    #[test]
145    fn test_by_tag() {
146        let mut log = new_message_log(10);
147        ml_push(&mut log, "io", "read", MsgPriority::Normal);
148        ml_push(&mut log, "net", "send", MsgPriority::Normal);
149        assert_eq!(ml_by_tag(&log, "io").len(), 1);
150    }
151
152    #[test]
153    fn test_by_priority() {
154        let mut log = new_message_log(10);
155        ml_push(&mut log, "a", "x", MsgPriority::Low);
156        ml_push(&mut log, "b", "y", MsgPriority::High);
157        assert_eq!(ml_by_priority(&log, MsgPriority::High).len(), 1);
158    }
159
160    #[test]
161    fn test_clear() {
162        let mut log = new_message_log(10);
163        ml_push(&mut log, "t", "m", MsgPriority::Normal);
164        ml_clear(&mut log);
165        assert_eq!(ml_len(&log), 0);
166    }
167
168    #[test]
169    fn test_last() {
170        let mut log = new_message_log(10);
171        ml_push(&mut log, "a", "first", MsgPriority::Low);
172        ml_push(&mut log, "b", "last", MsgPriority::High);
173        assert!(ml_last(&log).is_some_and(|m| m.text == "last"));
174    }
175
176    #[test]
177    fn test_json() {
178        let mut log = new_message_log(10);
179        ml_push(&mut log, "sys", "ok", MsgPriority::Normal);
180        let j = ml_to_json(&log);
181        assert!(j.contains("sys"));
182    }
183
184    #[test]
185    fn test_remove_tag() {
186        let mut log = new_message_log(10);
187        ml_push(&mut log, "del", "x", MsgPriority::Normal);
188        ml_push(&mut log, "keep", "y", MsgPriority::Normal);
189        ml_remove_tag(&mut log, "del");
190        assert_eq!(ml_len(&log), 1);
191    }
192}