a2ui_base/model/
data_model.rs1use serde_json::Value;
10
11use crate::observable::event_stream::{EventSubscription, EventStream};
12
13pub struct DataModel {
18 data: Value,
19 subscribers: EventStream<DataModelEvent>,
20}
21
22#[derive(Debug, Clone)]
24pub struct DataModelEvent {
25 pub path: String,
27 pub new_value: Option<Value>,
29}
30
31impl Default for DataModel {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl DataModel {
38 pub fn new() -> Self {
40 Self {
41 data: Value::Object(serde_json::Map::new()),
42 subscribers: EventStream::new(),
43 }
44 }
45
46 pub fn from_value(value: Value) -> Self {
49 Self {
50 data: if value.is_object() { value } else { Value::Object(serde_json::Map::new()) },
51 subscribers: EventStream::new(),
52 }
53 }
54
55 pub fn as_value(&self) -> &Value {
57 &self.data
58 }
59
60 pub fn get(&self, pointer: &str) -> Option<&Value> {
63 if pointer == "/" || pointer.is_empty() {
64 return Some(&self.data);
65 }
66 let tokens = parse_pointer(pointer);
67 resolve_value(&self.data, &tokens)
68 }
69
70 pub fn set(&mut self, pointer: &str, value: Value) {
76 if pointer == "/" || pointer.is_empty() {
77 if value.is_null() {
79 self.data = Value::Object(serde_json::Map::new());
80 } else {
81 self.data = value.clone();
82 }
83 self.notify("/", &value);
84 return;
85 }
86
87 let tokens = parse_pointer(pointer);
88 set_value(&mut self.data, &tokens, value.clone());
89 self.notify(pointer, &value);
90 }
91
92 pub fn replace_all(&mut self, value: Value) {
94 let new_data = if value.is_object() { value } else { Value::Object(serde_json::Map::new()) };
95 self.data = new_data.clone();
96 self.notify("/", &new_data);
97 }
98
99 pub fn subscribe<F>(&self, callback: F) -> EventSubscription
101 where
102 F: Fn(&DataModelEvent) + Send + Sync + 'static,
103 {
104 self.subscribers.on(callback)
105 }
106
107 fn notify(&self, changed_path: &str, new_value: &Value) {
109 let event = DataModelEvent {
110 path: changed_path.to_string(),
111 new_value: if new_value.is_null() { None } else { Some(new_value.clone()) },
112 };
113 self.subscribers.emit(&event);
114
115 let path = changed_path.trim_end_matches('/');
117 let mut parent = path;
118 while let Some(pos) = parent.rfind('/') {
119 parent = &parent[..pos];
120 if parent.is_empty() {
121 break;
122 }
123 let parent_event = DataModelEvent {
124 path: parent.to_string(),
125 new_value: self.get(parent).cloned(),
126 };
127 self.subscribers.emit(&parent_event);
128 }
129 }
130}
131
132fn parse_pointer(pointer: &str) -> Vec<String> {
139 let trimmed = pointer.strip_prefix('/').unwrap_or(pointer);
140 if trimmed.is_empty() {
141 return Vec::new();
142 }
143 trimmed
144 .split('/')
145 .map(|token| token.replace("~1", "/").replace("~0", "~"))
146 .collect()
147}
148
149fn resolve_value<'a>(value: &'a Value, tokens: &[String]) -> Option<&'a Value> {
151 let mut current = value;
152 for token in tokens {
153 match current {
154 Value::Object(map) => {
155 current = map.get(token)?;
156 }
157 Value::Array(arr) => {
158 let idx: usize = token.parse().ok()?;
159 current = arr.get(idx)?;
160 }
161 _ => return None,
162 }
163 }
164 Some(current)
165}
166
167fn set_value(root: &mut Value, tokens: &[String], value: Value) {
169 if tokens.is_empty() {
170 *root = value;
171 return;
172 }
173
174 if value.is_null() && tokens.len() == 1 {
176 remove_value(root, tokens);
177 return;
178 }
179
180 let mut current = root;
181 for (i, token) in tokens.iter().enumerate() {
182 if i == tokens.len() - 1 {
183 set_at(current, token, value);
185 return;
186 }
187 current = vivify(current, token);
189 }
190}
191
192fn vivify<'a>(parent: &'a mut Value, token: &str) -> &'a mut Value {
194 if !parent.is_object() {
196 *parent = Value::Object(serde_json::Map::new());
197 }
198 let map = parent.as_object_mut().unwrap();
200 if !map.contains_key(token) {
201 map.insert(token.to_string(), Value::Object(serde_json::Map::new()));
202 }
203 map.get_mut(token).unwrap()
204}
205
206fn set_at(parent: &mut Value, token: &str, value: Value) {
208 match parent {
209 Value::Object(map) => {
210 if value.is_null() {
211 map.remove(token);
212 } else {
213 map.insert(token.to_string(), value);
214 }
215 }
216 Value::Array(arr) => {
217 if let Ok(idx) = token.parse::<usize>() {
218 while arr.len() <= idx {
219 arr.push(Value::Null);
220 }
221 arr[idx] = value;
222 }
223 }
224 _ => {}
225 }
226}
227
228fn remove_value(root: &mut Value, tokens: &[String]) {
230 if tokens.len() == 1 {
231 match root {
232 Value::Object(map) => {
233 map.remove(&tokens[0]);
234 }
235 Value::Array(arr) => {
236 if let Ok(idx) = tokens[0].parse::<usize>() {
237 if idx < arr.len() {
238 arr[idx] = Value::Null; }
240 }
241 }
242 _ => {}
243 }
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use serde_json::json;
251
252 #[test]
253 fn test_get_simple() {
254 let mut dm = DataModel::new();
255 dm.set("/name", json!("Alice"));
256 assert_eq!(dm.get("/name"), Some(&json!("Alice")));
257 }
258
259 #[test]
260 fn test_get_nested() {
261 let mut dm = DataModel::new();
262 dm.set("/user/name/first", json!("Bob"));
263 assert_eq!(dm.get("/user/name/first"), Some(&json!("Bob")));
264 assert_eq!(dm.get("/user/name"), Some(&json!({"first": "Bob"})));
265 }
266
267 #[test]
268 fn test_set_array() {
269 let mut dm = DataModel::new();
270 dm.set("/items/0", json!("first"));
271 dm.set("/items/1", json!("second"));
272 assert_eq!(dm.get("/items/0"), Some(&json!("first")));
273 assert_eq!(dm.get("/items/1"), Some(&json!("second")));
274 }
275
276 #[test]
277 fn test_replace_root() {
278 let mut dm = DataModel::new();
279 dm.set("/a", json!(1));
280 dm.replace_all(json!({"x": 10, "y": 20}));
281 assert_eq!(dm.get("/a"), None);
282 assert_eq!(dm.get("/x"), Some(&json!(10)));
283 }
284
285 #[test]
286 fn test_remove_key() {
287 let mut dm = DataModel::new();
288 dm.set("/name", json!("Alice"));
289 dm.set("/name", Value::Null);
290 assert_eq!(dm.get("/name"), None);
291 }
292
293 #[test]
294 fn test_notification() {
295 let dm = DataModel::new();
296 let received = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
297 let r = received.clone();
298
299 let _sub = dm.subscribe(move |event: &DataModelEvent| {
300 r.lock().unwrap().push(event.path.clone());
301 });
302
303 }
306
307 #[test]
308 fn test_parse_pointer() {
309 assert_eq!(parse_pointer("/a/b/c"), vec!["a", "b", "c"]);
310 assert_eq!(parse_pointer("/a~1b"), vec!["a/b"]);
311 assert_eq!(parse_pointer("/a~0b"), vec!["a~b"]);
312 assert_eq!(parse_pointer("/"), Vec::<String>::new());
313 }
314
315 #[test]
316 fn test_auto_vivification() {
317 let mut dm = DataModel::new();
318 dm.set("/a/b/0/c", json!(42));
319 assert_eq!(dm.get("/a/b/0/c"), Some(&json!(42)));
320 assert!(dm.get("/a").unwrap().is_object());
321 assert!(dm.get("/a/b").unwrap().is_object());
322 }
323
324 #[test]
325 fn test_from_initial_data() {
326 let dm = DataModel::from_value(json!({
327 "user": {"name": "Alice", "age": 30},
328 "items": ["a", "b", "c"]
329 }));
330 assert_eq!(dm.get("/user/name"), Some(&json!("Alice")));
331 assert_eq!(dm.get("/items/1"), Some(&json!("b")));
332 assert_eq!(dm.get("/items/2"), Some(&json!("c")));
333 }
334}