chat_core/history/
append.rs

1use yrs::{Any, Map, MapRef, TransactionMut};
2
3use crate::error::ChatResult;
4use crate::history::types::ChatMessage;
5
6pub fn append_message(
7    txn: &mut TransactionMut,
8    map: &MapRef,
9    msg: &ChatMessage,
10) -> ChatResult<bool> {
11    if map.get(txn, &msg.id).is_some() {
12        return Ok(false);
13    }
14
15    let json = serde_json::to_value(msg)?;
16    let any = json_to_yrs_any(json);
17    map.insert(txn, msg.id.clone(), any);
18
19    Ok(true)
20}
21
22fn json_to_yrs_any(value: serde_json::Value) -> Any {
23    match value {
24        serde_json::Value::Null => Any::Null,
25        serde_json::Value::Bool(b) => Any::Bool(b),
26        serde_json::Value::Number(n) => Any::Number(n.as_f64().unwrap_or(0.0)),
27        serde_json::Value::String(s) => Any::String(s.into()),
28        serde_json::Value::Array(arr) => {
29            let items: Vec<Any> = arr.into_iter().map(json_to_yrs_any).collect();
30            Any::Array(items.into())
31        }
32        serde_json::Value::Object(map) => {
33            let mut result = std::collections::HashMap::new();
34            for (k, v) in map {
35                result.insert(k, json_to_yrs_any(v));
36            }
37            Any::Map(result.into())
38        }
39    }
40}
41
42use yrs::Out;
43
44pub fn message_from_any(out: &Out) -> ChatResult<ChatMessage> {
45    let any = out_to_any(out);
46    let json = yrs_any_to_json(&any);
47    let msg = serde_json::from_value(json)?;
48    Ok(msg)
49}
50
51fn out_to_any(out: &Out) -> Any {
52    match out {
53        Out::Any(any) => any.clone(),
54        Out::YXmlText(_) | Out::YXmlElement(_) | Out::YXmlFragment(_) | Out::YDoc(_) => Any::Null,
55        Out::YArray(_) => Any::Null,
56        Out::YMap(_) => Any::Null,
57        Out::YText(_) => Any::Null,
58        Out::UndefinedRef(_) => Any::Null,
59    }
60}
61
62fn yrs_any_to_json(any: &Any) -> serde_json::Value {
63    match any {
64        Any::Null => serde_json::Value::Null,
65        Any::Undefined => serde_json::Value::Null,
66        Any::Bool(b) => serde_json::Value::Bool(*b),
67        Any::Number(n) => serde_json::Value::Number(
68            serde_json::Number::from_f64(*n).unwrap_or(serde_json::Number::from(0)),
69        ),
70        Any::BigInt(n) => serde_json::Value::Number(serde_json::Number::from(*n)),
71        Any::String(s) => serde_json::Value::String(s.to_string()),
72        Any::Array(arr) => {
73            let items: Vec<serde_json::Value> = arr.iter().map(yrs_any_to_json).collect();
74            serde_json::Value::Array(items)
75        }
76        Any::Map(map) => {
77            let mut obj = serde_json::Map::new();
78            for (k, v) in map.iter() {
79                obj.insert(k.to_string(), yrs_any_to_json(v));
80            }
81            serde_json::Value::Object(obj)
82        }
83        Any::Buffer(buf) => serde_json::Value::Array(
84            buf.iter().map(|b| serde_json::Value::Number(serde_json::Number::from(*b))).collect(),
85        ),
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::history::types::ChatMessage;
93    use yrs::{Doc, Map, Transact};
94
95    fn create_message(id: &str, content: &str) -> ChatMessage {
96        ChatMessage {
97            id: id.into(),
98            peer_id: "peerA".into(),
99            stable_sender_id: "sender-a".into(),
100            nickname: "alice".into(),
101            content: content.into(),
102            timestamp: 1000.0,
103            seq: 0.0,
104            parent_ids: vec![],
105            circuit_address: None,
106            web_rtc_address: None,
107        }
108    }
109
110    #[test]
111    fn adds_new_message_to_map() {
112        let doc = Doc::new();
113        let map = doc.get_or_insert_map("messages");
114        let mut txn = doc.transact_mut();
115
116        let msg = create_message("msg1", "hello");
117        let inserted = append_message(&mut txn, &map, &msg).unwrap();
118        assert!(inserted);
119        assert_eq!(map.len(&txn), 1);
120
121        let stored_any = map.get(&txn, "msg1").unwrap();
122        let stored = message_from_any(&stored_any).unwrap();
123        assert_eq!(stored.content, "hello");
124        assert_eq!(stored.stable_sender_id, "sender-a");
125    }
126
127    #[test]
128    fn is_no_op_for_duplicate_id() {
129        let doc = Doc::new();
130        let map = doc.get_or_insert_map("messages");
131
132        let msg1 = create_message("msg1", "hello");
133        {
134            let mut txn = doc.transact_mut();
135            append_message(&mut txn, &map, &msg1).unwrap();
136        }
137
138        let msg2 = ChatMessage {
139            id: "msg1".into(),
140            peer_id: "peerB".into(),
141            stable_sender_id: "sender-b".into(),
142            nickname: "bob".into(),
143            content: "world".into(),
144            timestamp: 2000.0,
145            seq: 1.0,
146            parent_ids: vec![],
147            circuit_address: None,
148            web_rtc_address: None,
149        };
150        {
151            let mut txn = doc.transact_mut();
152            let inserted = append_message(&mut txn, &map, &msg2).unwrap();
153            assert!(!inserted);
154        }
155
156        let txn = doc.transact();
157        assert_eq!(map.len(&txn), 1);
158        let stored_any = map.get(&txn, "msg1").unwrap();
159        let stored = message_from_any(&stored_any).unwrap();
160        assert_eq!(stored.content, "hello");
161        assert_eq!(stored.stable_sender_id, "sender-a");
162    }
163
164    #[test]
165    fn stores_exact_message_fields() {
166        let doc = Doc::new();
167        let map = doc.get_or_insert_map("messages");
168        let mut txn = doc.transact_mut();
169
170        let msg = ChatMessage {
171            id: "msg2".into(),
172            peer_id: "peerA".into(),
173            stable_sender_id: "sender-a".into(),
174            nickname: "alice".into(),
175            content: "test".into(),
176            timestamp: 1000.0,
177            seq: 5.0,
178            parent_ids: vec!["msg1".into()],
179            circuit_address: None,
180            web_rtc_address: None,
181        };
182        append_message(&mut txn, &map, &msg).unwrap();
183
184        let stored_any = map.get(&txn, "msg2").unwrap();
185        let stored = message_from_any(&stored_any).unwrap();
186        assert_eq!(stored.seq, 5.0);
187        assert_eq!(stored.parent_ids, vec!["msg1"]);
188    }
189}