rivox 1.0.0

Universal polyglot build coordination layer for Python, Rust, and Node monorepos
Documentation
use super::GraphNode;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct NodeDiff {
    pub package_name: String,
    pub ecosystem: String,
    pub old_version: Option<String>,
    pub new_version: Option<String>,
    pub old_hash: Option<String>,
    pub new_hash: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GraphDiffResult {
    pub added: Vec<GraphNode>,
    pub removed: Vec<GraphNode>,
    pub changed: Vec<NodeDiff>,
    pub unchanged_count: usize,
    pub rebuild_required: bool,
}

pub fn diff_graphs(old_nodes: &[GraphNode], new_nodes: &[GraphNode]) -> GraphDiffResult {
    let mut old_map: HashMap<(String, String), &GraphNode> = HashMap::new();
    for node in old_nodes {
        old_map.insert((node.ecosystem.clone(), node.package_name.clone()), node);
    }

    let mut new_map: HashMap<(String, String), &GraphNode> = HashMap::new();
    for node in new_nodes {
        new_map.insert((node.ecosystem.clone(), node.package_name.clone()), node);
    }

    let mut added = Vec::new();
    let mut removed = Vec::new();
    let mut changed = Vec::new();
    let mut unchanged_count = 0;

    for (key, new_node) in &new_map {
        match old_map.get(key) {
            Some(old_node) => {
                if old_node.resolved_version != new_node.resolved_version
                    || old_node.content_hash != new_node.content_hash
                {
                    changed.push(NodeDiff {
                        package_name: new_node.package_name.clone(),
                        ecosystem: new_node.ecosystem.clone(),
                        old_version: Some(old_node.resolved_version.clone()),
                        new_version: Some(new_node.resolved_version.clone()),
                        old_hash: Some(old_node.content_hash.clone()),
                        new_hash: Some(new_node.content_hash.clone()),
                    });
                } else {
                    unchanged_count += 1;
                }
            }
            None => {
                added.push((*new_node).clone());
            }
        }
    }

    for (key, old_node) in &old_map {
        if !new_map.contains_key(key) {
            removed.push((*old_node).clone());
        }
    }

    let rebuild_required = !added.is_empty() || !removed.is_empty() || !changed.is_empty();

    GraphDiffResult {
        added,
        removed,
        changed,
        unchanged_count,
        rebuild_required,
    }
}