wip: rewriting for clarity and accuracy
This commit is contained in:
parent
fe372b96fd
commit
eec0dab6f0
@ -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<Arc<Reference>>,
|
||||
}
|
||||
|
||||
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<Reference>) -> 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<Self> {
|
||||
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<String, Arc<Reference>>, updated_path: &str) -> Result<(), String> {
|
||||
fn update_parent_references(&self, refs: &mut HashMap<String, Arc<Reference>>, updated_name: &str) -> Result<(), String> {
|
||||
// Find all references that have the updated reference as a dependent
|
||||
let parent_paths: Vec<String> = refs
|
||||
let parent_names: Vec<String> = 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),
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user