diff --git a/offline-web-model/src/lib.rs b/offline-web-model/src/lib.rs index fbc1dd1..2a30c40 100644 --- a/offline-web-model/src/lib.rs +++ b/offline-web-model/src/lib.rs @@ -4,49 +4,98 @@ use blake2::{Blake2b512, Digest}; use rand::Rng; use serde::{Deserialize, Serialize}; +/// A `Reference` represents a node in a content-addressable graph structure. +/// +/// Each reference contains: +/// - An id that uniquely identifies the referenced object +/// - A content address hash that represents the content +/// - A path that provides a human-readable location for the reference +/// - A list of dependent references that this reference depends on +/// +/// References form a directed acyclic graph where each node can have multiple dependents. +/// When the content of a reference changes, the object id of all parent references +/// are automatically recalculated to maintain content addressability throughout the graph. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Reference { - pub object_id: String, + pub id: String, pub content_address: String, - pub path: String, + pub name: String, #[serde(skip_serializing_if = "Vec::is_empty")] pub dependents: Vec>, } impl Reference { - pub fn new(object_id: String, content_address: String, path: String) -> Self { + /// Creates a new Reference with the specified object ID, content address, and path. + /// + /// # Parameters + /// * `content_address` - The content-based hash address of the referenced object + /// * `name` - The name of the reference + /// + /// # Returns + /// A new Reference instance with no dependents + pub fn new(content_address: String, 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()); + Self { - object_id, + id: calculated_id, content_address, - path, + name, dependents: Vec::new(), } } + /// Adds a dependent reference to this reference. + /// + /// This establishes a parent-child relationship where this reference depends on + /// the provided reference. When a dependent reference changes, the parent's + /// id will be recalculated. + /// + /// # Parameters + /// * `dep` - An Arc-wrapped Reference to add as a dependent + /// + /// # Returns + /// The modified Reference with the new dependent added pub fn add_dep(mut self, dep: Arc) -> Self { self.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 } + + /// Converts this Reference into an Arc-wrapped Reference for shared ownership. + /// + /// This is useful when a Reference needs to be shared across multiple owners + /// in the graph structure. + /// + /// # Returns + /// An Arc-wrapped version of this Reference pub fn to_arc(self) -> Arc { Arc::new(self) } + /// Determines if this Reference is a leaf node (has no dependents). + /// + /// Leaf nodes directly represent content, while non-leaf nodes derive their + /// content address from their dependents. + /// + /// # Returns + /// `true` if this Reference has no dependents, `false` otherwise pub fn is_leaf(&self) -> bool { return self.dependents.is_empty(); } - /// Calculates a content address hash based on dependent references - pub fn calculate_content_address(&self) -> String { - if self.is_leaf() { - return self.content_address.clone(); - } - + fn initial_hash(content_address: &String, path: &String) -> Blake2b512 { let mut hasher = Blake2b512::new(); - for dep in &self.dependents { - hasher.update(&dep.content_address); - } - format!("{:x}", hasher.finalize()) + hasher.update(content_address); + hasher.update(path); + hasher } } @@ -83,9 +132,12 @@ pub fn random_object() -> (String, String) { } impl Graph { - /// Updates a reference to point to a new object, recalculating content addresses - /// for all affected references in the graph - pub fn update_reference(&mut self, path: &String, new_object_id: String, new_content: String) -> Result<(), String> { + /// 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: String) -> Result<(), String> { // Create a mutable copy of our maps let mut refs = HashMap::new(); for (k, v) in self.refs.as_ref() { @@ -98,33 +150,40 @@ impl Graph { } // Find the reference to update - let ref_to_update = refs.get(path).ok_or_else(|| format!("Reference with path {} not found", path))?; + let ref_to_update = refs.get(name).ok_or_else(|| format!("Reference with name {} not found", name))?; // Calculate hash for the new content let mut hasher = Blake2b512::new(); hasher.update(&new_content); let new_address = format!("{:x}", hasher.finalize()); - // Create updated reference - let updated_ref = Arc::new(Reference { - object_id: new_object_id, - content_address: new_address.clone(), - path: path.to_string(), - dependents: ref_to_update.dependents.clone(), - }); + // 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(), + name.to_string() + ); + + // Add all dependents to the updated reference + for dep in &ref_to_update.dependents { + updated_ref = updated_ref.add_dep(dep.clone()); + } + + + let updated_ref = updated_ref.to_arc(); // Update objects map with new content objects.insert(new_address.clone(), new_content); // Update references map with new reference - refs.insert(path.to_string(), updated_ref.clone()); + refs.insert(name.to_string(), updated_ref.clone()); // Find and update all parent references that contain this reference - self.update_parent_references(&mut refs, path)?; + self.update_parent_references(&mut refs, name)?; // Update the root reference if needed - if path == &self.root.path { - self.root = refs.get(path).unwrap().clone(); + if name == &self.root.name { + self.root = refs.get(name).unwrap().clone(); } // Update the Arc maps @@ -135,48 +194,41 @@ impl Graph { } /// Recursively updates parent references when a child reference changes - fn update_parent_references(&self, refs: &mut HashMap>, updated_path: &str) -> Result<(), String> { + fn update_parent_references(&self, refs: &mut HashMap>, updated_name: &str) -> Result<(), String> { // Find all references that have the updated reference as a dependent - let parent_paths: Vec = refs + let parent_names: Vec = refs .iter() - .filter(|(_, r)| r.dependents.iter().any(|dep| dep.path == updated_path)) - .map(|(path, _)| path.clone()) + .filter(|(_, r)| r.dependents.iter().any(|dep| dep.name == updated_name)) + .map(|(name, _)| name.clone()) .collect(); - for parent_path in parent_paths { - if let Some(parent_ref) = refs.get(&parent_path) { - // Create a new list of dependents with the updated reference - let mut new_dependents = Vec::new(); + for parent_name in parent_names { + if let Some(parent_ref) = refs.get(&parent_name) { + // Create a new reference with the same content address and name + let mut updated_parent = Reference::new( + parent_ref.content_address.clone(), + parent_ref.name.clone() + ); + + // Add all dependents, replacing the updated one for dep in &parent_ref.dependents { - if dep.path == updated_path { + if dep.name == updated_name { // Use the updated reference - new_dependents.push(refs.get(updated_path).unwrap().clone()); + updated_parent = updated_parent.add_dep(refs.get(updated_name).unwrap().clone()); } else { // Keep the existing dependent - new_dependents.push(dep.clone()); + updated_parent = updated_parent.add_dep(dep.clone()); } } - // Calculate new content address based on updated dependents - let mut hasher = Blake2b512::new(); - for dep in &new_dependents { - hasher.update(&dep.content_address); - } - let new_address = format!("{:x}", hasher.finalize()); - - // Create updated parent reference - let updated_parent = Arc::new(Reference { - object_id: parent_ref.object_id.clone(), - content_address: new_address, - path: parent_ref.path.clone(), - dependents: new_dependents, - }); + // The ID is automatically calculated in the add_dep method + let updated_parent = Arc::new(updated_parent); // Update the references map - refs.insert(parent_path.clone(), updated_parent); + refs.insert(parent_name.clone(), updated_parent); // Recursively update parents of this parent - self.update_parent_references(refs, &parent_path)?; + self.update_parent_references(refs, &parent_name)?; } } @@ -184,49 +236,61 @@ impl Graph { } pub fn random_graph() -> Graph { - let path_root = String::from("ref/0"); + let root_name = String::from("ref/0"); let mut objects = HashMap::new(); let mut refs = HashMap::new(); + + // Create the root reference let mut root_ref = Reference::new( - "username:0".to_string(), - String::from("0"), - path_root.clone(), + String::from("root_content"), + root_name.clone(), ); - let mut root_hasher = Blake2b512::new(); + + // Create 10 item references for i in 1..=10 { + let item_name = format!("/item/{}", i); let mut item_ref = Reference::new( - format!("item:{}", i), - format!("0:{}", i), - format!("/item/{}", i), + format!("item_content_{}", i), + item_name.clone(), ); - let mut hasher = Blake2b512::new(); + + // Create 10 subitems for each item for j in 1..=10 { let (address, content) = random_object(); - hasher.update(&content); + let subitem_name = format!("/item/{}/subitem/{}", i, j); + + // Create a leaf reference let leaf_ref = Reference::new( - format!("item:{}:subitem:{}", i, j), - format!("{}", address), - format!("/item/{}/subitem/{}", i, j), - ) - .to_arc(); + address.clone(), + subitem_name, + ).to_arc(); + + // Add the leaf reference as a dependent to the item reference item_ref = item_ref.add_dep(leaf_ref.clone()); + + // Store the content in the objects map objects.insert(address.clone(), content); - hasher.update(&leaf_ref.content_address); - refs.insert(leaf_ref.path.clone(), leaf_ref); + + // Store the leaf reference in the refs map + refs.insert(leaf_ref.name.clone(), leaf_ref); } - let hash = format!("{:x}", hasher.finalize()); - item_ref.content_address = hash; - root_hasher.update(&item_ref.content_address); - let rc_ref = item_ref.to_arc(); - root_ref = root_ref.add_dep(rc_ref.clone()); - refs.insert(rc_ref.path.clone(), rc_ref); + + // Convert the item reference to Arc and add it to the root reference + let arc_item_ref = item_ref.to_arc(); + root_ref = root_ref.add_dep(arc_item_ref.clone()); + + // Store the item reference in the refs map + refs.insert(arc_item_ref.name.clone(), arc_item_ref); } - root_ref.content_address = format!("{:x}", root_hasher.finalize()); - let rc_root = root_ref.to_arc(); - refs.insert(rc_root.path.clone(), rc_root.clone()); - dbg!(&objects); + + // Convert the root reference to Arc + let arc_root_ref = root_ref.to_arc(); + + // Store the root reference in the refs map + refs.insert(arc_root_ref.name.clone(), arc_root_ref.clone()); + Graph { - root: rc_root, + root: arc_root_ref, refs: Arc::new(refs), objects: Arc::new(objects), }