use std::{collections::HashMap, sync::Arc}; 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 id: String, pub content_address: String, pub name: String, #[serde(skip_serializing_if = "Vec::is_empty")] pub dependents: Vec>, } impl Reference { /// 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 { id: calculated_id, content_address, 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(); } fn initial_hash(content_address: &String, path: &String) -> Blake2b512 { let mut hasher = Blake2b512::new(); hasher.update(content_address); hasher.update(path); hasher } } pub struct Graph { pub root: Arc, pub refs: Arc>>, 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<&String> { self.objects.get(content_address) } } pub fn random_object() -> (String, String) { let mut rng = rand::rng(); let random_size = rng.random_range(50..=4096); let random_string: String = (0..random_size) .map(|_| rng.sample(rand::distr::Alphanumeric) as char) .collect(); let mut hasher = Blake2b512::new(); hasher.update(&random_string); let hash = format!("{:x}", hasher.finalize()); (hash, random_string) } impl Graph { /// 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() { refs.insert(k.clone(), v.clone()); } let mut objects = HashMap::new(); for (k, v) in self.objects.as_ref() { objects.insert(k.clone(), v.clone()); } // Find the reference to update 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 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(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(); } // Update the Arc maps self.refs = Arc::new(refs); self.objects = Arc::new(objects); Ok(()) } /// Recursively updates parent references when a child reference changes 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_names: Vec = refs .iter() .filter(|(_, r)| r.dependents.iter().any(|dep| dep.name == updated_name)) .map(|(name, _)| name.clone()) .collect(); 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.name == updated_name { // Use the updated reference updated_parent = updated_parent.add_dep(refs.get(updated_name).unwrap().clone()); } else { // Keep the existing dependent updated_parent = updated_parent.add_dep(dep.clone()); } } // The ID is automatically calculated in the add_dep method let updated_parent = Arc::new(updated_parent); // Update the references map refs.insert(parent_name.clone(), updated_parent); // Recursively update parents of this parent self.update_parent_references(refs, &parent_name)?; } } Ok(()) } pub fn random_graph() -> Graph { 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( String::from("root_content"), root_name.clone(), ); // Create 10 item references for i in 1..=10 { let item_name = format!("/item/{}", i); let mut item_ref = Reference::new( format!("item_content_{}", i), item_name.clone(), ); // Create 10 subitems for each item for j in 1..=10 { let (address, content) = random_object(); let subitem_name = format!("/item/{}/subitem/{}", i, j); // Create a leaf reference let leaf_ref = Reference::new( 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); // Store the leaf reference in the refs map refs.insert(leaf_ref.name.clone(), leaf_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); } // 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: arc_root_ref, refs: Arc::new(refs), objects: Arc::new(objects), } } }