motosan_agent_loop/message/
meta.rs1use std::collections::BTreeMap;
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct MessageMeta {
17 pub created_at_ms: u64,
19 pub compacted_at_ms: Option<u64>,
24 pub cache_stable: bool,
28 pub tags: Vec<String>,
30 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
33 pub extensions: BTreeMap<String, serde_json::Value>,
34}
35
36impl Default for MessageMeta {
37 fn default() -> Self {
38 let created_at_ms = SystemTime::now()
39 .duration_since(UNIX_EPOCH)
40 .map(|d| d.as_millis() as u64)
41 .unwrap_or(0);
42 Self {
43 created_at_ms,
44 compacted_at_ms: None,
45 cache_stable: false,
46 tags: Vec::new(),
47 extensions: BTreeMap::new(),
48 }
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn default_has_nonzero_created_at() {
58 let m = MessageMeta::default();
59 assert!(m.created_at_ms > 0);
60 assert!(m.compacted_at_ms.is_none());
61 assert!(!m.cache_stable);
62 assert!(m.tags.is_empty());
63 assert!(m.extensions.is_empty());
64 }
65
66 #[test]
67 fn round_trips_json_without_extensions_noise() {
68 let m = MessageMeta::default();
69 let s = serde_json::to_string(&m).unwrap();
70 assert!(
71 !s.contains("extensions"),
72 "empty extensions should not serialize: {s}"
73 );
74 let back: MessageMeta = serde_json::from_str(&s).unwrap();
75 assert_eq!(m, back);
76 }
77
78 #[test]
79 fn extensions_slot_round_trips() {
80 let mut m = MessageMeta::default();
81 m.extensions
82 .insert("my_ext".into(), serde_json::json!({"score": 7}));
83 let s = serde_json::to_string(&m).unwrap();
84 let back: MessageMeta = serde_json::from_str(&s).unwrap();
85 assert_eq!(back.extensions["my_ext"]["score"], 7);
86 }
87}