use serde_json::Value;
use crate::observable::event_stream::{EventSubscription, EventStream};
pub struct DataModel {
data: Value,
subscribers: EventStream<DataModelEvent>,
}
#[derive(Debug, Clone)]
pub struct DataModelEvent {
pub path: String,
pub new_value: Option<Value>,
}
impl Default for DataModel {
fn default() -> Self {
Self::new()
}
}
impl DataModel {
pub fn new() -> Self {
Self {
data: Value::Object(serde_json::Map::new()),
subscribers: EventStream::new(),
}
}
pub fn from_value(value: Value) -> Self {
Self {
data: if value.is_object() { value } else { Value::Object(serde_json::Map::new()) },
subscribers: EventStream::new(),
}
}
pub fn as_value(&self) -> &Value {
&self.data
}
pub fn get(&self, pointer: &str) -> Option<&Value> {
if pointer == "/" || pointer.is_empty() {
return Some(&self.data);
}
let tokens = parse_pointer(pointer);
resolve_value(&self.data, &tokens)
}
pub fn set(&mut self, pointer: &str, value: Value) {
if pointer == "/" || pointer.is_empty() {
if value.is_null() {
self.data = Value::Object(serde_json::Map::new());
} else {
self.data = value.clone();
}
self.notify("/", &value);
return;
}
let tokens = parse_pointer(pointer);
set_value(&mut self.data, &tokens, value.clone());
self.notify(pointer, &value);
}
pub fn replace_all(&mut self, value: Value) {
let new_data = if value.is_object() { value } else { Value::Object(serde_json::Map::new()) };
self.data = new_data.clone();
self.notify("/", &new_data);
}
pub fn subscribe<F>(&self, callback: F) -> EventSubscription
where
F: Fn(&DataModelEvent) + Send + Sync + 'static,
{
self.subscribers.on(callback)
}
fn notify(&self, changed_path: &str, new_value: &Value) {
let event = DataModelEvent {
path: changed_path.to_string(),
new_value: if new_value.is_null() { None } else { Some(new_value.clone()) },
};
self.subscribers.emit(&event);
let path = changed_path.trim_end_matches('/');
let mut parent = path;
while let Some(pos) = parent.rfind('/') {
parent = &parent[..pos];
if parent.is_empty() {
break;
}
let parent_event = DataModelEvent {
path: parent.to_string(),
new_value: self.get(parent).cloned(),
};
self.subscribers.emit(&parent_event);
}
}
}
fn parse_pointer(pointer: &str) -> Vec<String> {
let trimmed = pointer.strip_prefix('/').unwrap_or(pointer);
if trimmed.is_empty() {
return Vec::new();
}
trimmed
.split('/')
.map(|token| token.replace("~1", "/").replace("~0", "~"))
.collect()
}
fn resolve_value<'a>(value: &'a Value, tokens: &[String]) -> Option<&'a Value> {
let mut current = value;
for token in tokens {
match current {
Value::Object(map) => {
current = map.get(token)?;
}
Value::Array(arr) => {
let idx: usize = token.parse().ok()?;
current = arr.get(idx)?;
}
_ => return None,
}
}
Some(current)
}
fn set_value(root: &mut Value, tokens: &[String], value: Value) {
if tokens.is_empty() {
*root = value;
return;
}
if value.is_null() && tokens.len() == 1 {
remove_value(root, tokens);
return;
}
let mut current = root;
for (i, token) in tokens.iter().enumerate() {
if i == tokens.len() - 1 {
set_at(current, token, value);
return;
}
current = vivify(current, token);
}
}
fn vivify<'a>(parent: &'a mut Value, token: &str) -> &'a mut Value {
if !parent.is_object() {
*parent = Value::Object(serde_json::Map::new());
}
let map = parent.as_object_mut().unwrap();
if !map.contains_key(token) {
map.insert(token.to_string(), Value::Object(serde_json::Map::new()));
}
map.get_mut(token).unwrap()
}
fn set_at(parent: &mut Value, token: &str, value: Value) {
match parent {
Value::Object(map) => {
if value.is_null() {
map.remove(token);
} else {
map.insert(token.to_string(), value);
}
}
Value::Array(arr) => {
if let Ok(idx) = token.parse::<usize>() {
while arr.len() <= idx {
arr.push(Value::Null);
}
arr[idx] = value;
}
}
_ => {}
}
}
fn remove_value(root: &mut Value, tokens: &[String]) {
if tokens.len() == 1 {
match root {
Value::Object(map) => {
map.remove(&tokens[0]);
}
Value::Array(arr) => {
if let Ok(idx) = tokens[0].parse::<usize>() {
if idx < arr.len() {
arr[idx] = Value::Null; }
}
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_get_simple() {
let mut dm = DataModel::new();
dm.set("/name", json!("Alice"));
assert_eq!(dm.get("/name"), Some(&json!("Alice")));
}
#[test]
fn test_get_nested() {
let mut dm = DataModel::new();
dm.set("/user/name/first", json!("Bob"));
assert_eq!(dm.get("/user/name/first"), Some(&json!("Bob")));
assert_eq!(dm.get("/user/name"), Some(&json!({"first": "Bob"})));
}
#[test]
fn test_set_array() {
let mut dm = DataModel::new();
dm.set("/items/0", json!("first"));
dm.set("/items/1", json!("second"));
assert_eq!(dm.get("/items/0"), Some(&json!("first")));
assert_eq!(dm.get("/items/1"), Some(&json!("second")));
}
#[test]
fn test_replace_root() {
let mut dm = DataModel::new();
dm.set("/a", json!(1));
dm.replace_all(json!({"x": 10, "y": 20}));
assert_eq!(dm.get("/a"), None);
assert_eq!(dm.get("/x"), Some(&json!(10)));
}
#[test]
fn test_remove_key() {
let mut dm = DataModel::new();
dm.set("/name", json!("Alice"));
dm.set("/name", Value::Null);
assert_eq!(dm.get("/name"), None);
}
#[test]
fn test_notification() {
let dm = DataModel::new();
let received = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let r = received.clone();
let _sub = dm.subscribe(move |event: &DataModelEvent| {
r.lock().unwrap().push(event.path.clone());
});
}
#[test]
fn test_parse_pointer() {
assert_eq!(parse_pointer("/a/b/c"), vec!["a", "b", "c"]);
assert_eq!(parse_pointer("/a~1b"), vec!["a/b"]);
assert_eq!(parse_pointer("/a~0b"), vec!["a~b"]);
assert_eq!(parse_pointer("/"), Vec::<String>::new());
}
#[test]
fn test_auto_vivification() {
let mut dm = DataModel::new();
dm.set("/a/b/0/c", json!(42));
assert_eq!(dm.get("/a/b/0/c"), Some(&json!(42)));
assert!(dm.get("/a").unwrap().is_object());
assert!(dm.get("/a/b").unwrap().is_object());
}
#[test]
fn test_from_initial_data() {
let dm = DataModel::from_value(json!({
"user": {"name": "Alice", "age": 30},
"items": ["a", "b", "c"]
}));
assert_eq!(dm.get("/user/name"), Some(&json!("Alice")));
assert_eq!(dm.get("/items/1"), Some(&json!("b")));
assert_eq!(dm.get("/items/2"), Some(&json!("c")));
}
}