From 780bb4e37226781b44c0ecd60e09646f7cb3d8b1 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 12 Jun 2025 20:50:39 -0400 Subject: [PATCH] wip: ensure root always get's updated --- offline-web-model/src/lib.rs | 87 +++++++++++++++++++++-------------- offline-web-model/src/test.rs | 26 +++++------ 2 files changed, 65 insertions(+), 48 deletions(-) diff --git a/offline-web-model/src/lib.rs b/offline-web-model/src/lib.rs index f094cff..350f510 100644 --- a/offline-web-model/src/lib.rs +++ b/offline-web-model/src/lib.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Reference { pub id: String, - pub content_address: String, + pub content_address: Option, pub name: String, #[serde(skip_serializing_if = "Vec::is_empty")] pub dependents: Vec>, @@ -33,7 +33,7 @@ impl Reference { /// /// # Returns /// A new Reference instance with no dependents - pub fn new(content_address: String, name: String) -> Self { + pub fn new(content_address: Option, name: String) -> Self { // Calculate the reference_id from the content_address and path let hasher = Self::initial_hash(&content_address, &name); let calculated_id = format!("{:x}", hasher.finalize()); @@ -57,15 +57,16 @@ impl Reference { /// /// # Returns /// The modified Reference with the new dependent added - pub fn add_dep(mut self, dep: Arc) -> Self { - self.dependents.push(dep); + pub fn add_dep(&self, dep: Arc) -> Self { + let mut cloned = self.clone(); + cloned.dependents.push(dep); // Recalculate the ID based on dependents, content_address, and path let mut hasher = Self::initial_hash(&self.content_address, &self.name); for dependent in &self.dependents { hasher.update(&dependent.id); } - self.id = format!("{:x}", hasher.finalize()); - self + cloned.id = format!("{:x}", hasher.finalize()); + cloned } @@ -91,9 +92,11 @@ impl Reference { return self.dependents.is_empty(); } - fn initial_hash(content_address: &String, path: &String) -> Blake2b512 { + fn initial_hash(content_address: &Option, path: &String) -> Blake2b512 { let mut hasher = Blake2b512::new(); - hasher.update(content_address); + if let Some(content_address) = content_address { + hasher.update(content_address); + } hasher.update(path); hasher } @@ -105,18 +108,6 @@ pub struct Graph { pub objects: Arc>>, } -impl Graph { - /// Gets a reference by its path - pub fn get_reference(&self, path: &str) -> Option> { - self.refs.get(path).cloned() - } - - /// Gets an object by its content address - pub fn get_object(&self, content_address: &str) -> Option<&Vec> { - self.objects.get(content_address) - } -} - pub fn random_object() -> (String, Vec) { let mut rng = rand::rng(); let random_size = rng.random_range(50..=4096); @@ -132,12 +123,39 @@ pub fn random_object() -> (String, Vec) { } impl Graph { + pub fn new(root: Arc) -> Self { + let mut refs = HashMap::new(); + refs.insert(root.name.clone(), root.clone()); + let refs = Arc::new(refs); + let objects = Arc::new(HashMap::new()); + Self { + root, + refs, + objects, + } + } + + /// Gets a reference by its path + pub fn get_reference(&self, path: &str) -> Option> { + self.refs.get(path).cloned() + } + + /// Gets an object by its content address + pub fn get_object(&self, content_address: &str) -> Option<&Vec> { + self.objects.get(content_address) + } + /// Updates a reference to point to a new object, recalculating content addresses and IDs /// for all affected references in the graph. /// /// The reference ID is calculated from the content address, name, and any dependents, /// ensuring that it's truly content-addressable. - pub fn update_reference(&mut self, name: &String, new_content: Vec) -> Result<(), String> { + pub fn update_object_reference(&mut self, name: &String, new_content: Vec) -> Result<(), String> { + // Update the root reference if needed + if name == &self.root.name { + self.root = self.refs.get(name).unwrap().clone(); + return Ok(()); + } // Create a mutable copy of our maps let mut refs = HashMap::new(); for (k, v) in self.refs.as_ref() { @@ -160,7 +178,7 @@ impl Graph { // Create a new reference with the updated content address // The ID will be calculated based on the content address, name, and dependents let mut updated_ref = Reference::new( - new_address.clone(), + Some(new_address.clone()), name.to_string() ); @@ -179,12 +197,7 @@ impl Graph { refs.insert(name.to_string(), updated_ref.clone()); // Find and update all parent references that contain this reference - self.update_parent_references(&mut refs, name)?; - - // Update the root reference if needed - if name == &self.root.name { - self.root = refs.get(name).unwrap().clone(); - } + self.update_parent_references(&mut refs, name); // Update the Arc maps self.refs = Arc::new(refs); @@ -192,9 +205,11 @@ impl Graph { Ok(()) } + + /// TODO(jwall): Add new reference /// Recursively updates parent references when a child reference changes - fn update_parent_references(&self, refs: &mut HashMap>, updated_name: &str) -> Result<(), String> { + fn update_parent_references(&mut self, refs: &mut HashMap>, updated_name: &str) { // Find all references that have the updated reference as a dependent let parent_names: Vec = refs .iter() @@ -212,6 +227,7 @@ impl Graph { // Add all dependents, replacing the updated one for dep in &parent_ref.dependents { + // Update the root reference if needed if dep.name == updated_name { // Use the updated reference updated_parent = updated_parent.add_dep(refs.get(updated_name).unwrap().clone()); @@ -223,16 +239,17 @@ impl Graph { // The ID is automatically calculated in the add_dep method let updated_parent = Arc::new(updated_parent); + if updated_parent.name == self.root.name { + self.root = updated_parent.clone(); + } // Update the references map refs.insert(parent_name.clone(), updated_parent); // Recursively update parents of this parent - self.update_parent_references(refs, &parent_name)?; + self.update_parent_references(refs, &parent_name); } } - - Ok(()) } pub fn random_graph() -> Graph { @@ -242,7 +259,7 @@ impl Graph { // Create the root reference let mut root_ref = Reference::new( - String::from("root_content"), + Some(String::from("root_content")), root_name.clone(), ); @@ -250,7 +267,7 @@ impl Graph { for i in 1..=10 { let item_name = format!("/item/{}", i); let mut item_ref = Reference::new( - format!("item_content_{}", i), + Some(format!("item_content_{}", i)), item_name.clone(), ); @@ -261,7 +278,7 @@ impl Graph { // Create a leaf reference let leaf_ref = Reference::new( - address.clone(), + Some(address.clone()), subitem_name, ).to_arc(); diff --git a/offline-web-model/src/test.rs b/offline-web-model/src/test.rs index 1c25290..a7e81aa 100644 --- a/offline-web-model/src/test.rs +++ b/offline-web-model/src/test.rs @@ -25,10 +25,10 @@ fn test_dependencies_updated_when_nodes_added() { let candidate = get_random_candidate(&graph); // Update the leaf node - graph.update_reference(&candidate.name, random_object().1).unwrap(); + graph.update_object_reference(&candidate.name, random_object().1).unwrap(); // Verify that the leaf node's ID has changed - let updated_leaf = graph.get_reference(&candidate.name).unwrap(); + let updated_leaf = dbg!(graph.get_reference(&candidate.name).unwrap()); assert_ne!(updated_leaf.id, candidate.id, "Leaf node ID should change when content is updated"); @@ -88,7 +88,7 @@ fn create_test_graph() -> Graph { // Create the root reference let mut root_ref = Reference::new( - String::from("root_content"), + Some(String::from("root_content")), root_name.clone(), ); @@ -96,7 +96,7 @@ fn create_test_graph() -> Graph { for i in 1..=3 { let item_name = format!("/item/{}", i); let mut item_ref = Reference::new( - format!("item_content_{}", i), + Some(format!("item_content_{}", i)), item_name.clone(), ); @@ -107,7 +107,7 @@ fn create_test_graph() -> Graph { // Create a leaf reference let leaf_ref = Reference::new( - address.clone(), + Some(address.clone()), subitem_name, ).to_arc(); @@ -150,13 +150,13 @@ fn test_content_addressable_properties() { // Update a leaf node with the same content let leaf_path = "/item/1/subitem/1".to_string(); let initial_leaf = graph.get_reference(&leaf_path).unwrap(); - let content_address = initial_leaf.content_address.clone(); - - // Get the content for this address - let content = graph.get_object(&content_address).unwrap().clone(); - - // Update with the same content - graph.update_reference(&leaf_path, content).unwrap(); + if let Some(content_address) = initial_leaf.content_address.clone() { + // Get the content for this address + let content = graph.get_object(&content_address).unwrap().clone(); + + // Update with the same content + graph.update_object_reference(&leaf_path, content).unwrap(); + } // Verify that nothing changed since the content is the same let updated_leaf = graph.get_reference(&leaf_path).unwrap(); @@ -173,7 +173,7 @@ fn test_id_calculation() { let leaf_path = "/item/1/subitem/1".to_string(); let initial_leaf = graph.get_reference(&leaf_path).unwrap(); - graph.update_reference(&leaf_path, "new content".as_bytes().to_vec()).unwrap(); + graph.update_object_reference(&leaf_path, "new content".as_bytes().to_vec()).unwrap(); // Verify that the ID changed let updated_leaf = graph.get_reference(&leaf_path).unwrap();