ddex_builder/linker/
relationship_manager.rs

1//! Manages relationships between DDEX entities
2
3use super::types::{EntityType, LinkerError};
4use indexmap::{IndexMap, IndexSet};
5
6/// Manages entity relationships and reference lookups
7#[derive(Debug, Clone, Default)]
8pub struct RelationshipManager {
9    /// Maps from entity type to (id -> reference) mappings
10    /// Using IndexMap for deterministic iteration
11    registry: IndexMap<EntityType, IndexMap<String, String>>,
12    
13    /// Reverse lookup: reference -> (entity_type, id)
14    reverse_lookup: IndexMap<String, (EntityType, String)>,
15    
16    /// Track relationships between entities
17    relationships: IndexMap<String, IndexSet<String>>,
18}
19
20impl RelationshipManager {
21    /// Create a new relationship manager
22    pub fn new() -> Self {
23        Self::default()
24    }
25    
26    /// Register an entity with its reference
27    pub fn register(&mut self, entity_type: EntityType, id: String, reference: String) {
28        // Forward lookup
29        self.registry
30            .entry(entity_type)
31            .or_insert_with(IndexMap::new)
32            .insert(id.clone(), reference.clone());
33        
34        // Reverse lookup
35        self.reverse_lookup.insert(reference, (entity_type, id));
36    }
37    
38    /// Get reference for an entity
39    pub fn get_reference(&self, entity_type: EntityType, id: &str) -> Option<String> {
40        self.registry
41            .get(&entity_type)
42            .and_then(|refs| refs.get(id))
43            .cloned()
44    }
45    
46    /// Get entity info by reference
47    pub fn get_entity_by_reference(&self, reference: &str) -> Option<(EntityType, String)> {
48        self.reverse_lookup.get(reference).cloned()
49    }
50    
51    /// Check if a reference exists
52    pub fn reference_exists(&self, reference: &str) -> bool {
53        self.reverse_lookup.contains_key(reference)
54    }
55    
56    /// Add a relationship between two entities
57    pub fn add_relationship(&mut self, from_ref: String, to_ref: String) {
58        self.relationships
59            .entry(from_ref)
60            .or_insert_with(IndexSet::new)
61            .insert(to_ref);
62    }
63    
64    /// Get all related references for an entity
65    pub fn get_relationships(&self, reference: &str) -> Option<&IndexSet<String>> {
66        self.relationships.get(reference)
67    }
68    
69    /// Get all registered references (for debugging)
70    pub fn get_all(&self) -> IndexMap<EntityType, IndexMap<String, String>> {
71        self.registry.clone()
72    }
73    
74    /// Validate all references
75    pub fn validate(&self) -> Result<(), Vec<LinkerError>> {
76        let mut errors = Vec::new();
77        
78        // Check for orphaned relationships
79        for (from_ref, to_refs) in &self.relationships {
80            if !self.reference_exists(from_ref) {
81                errors.push(LinkerError::OrphanedReference(from_ref.clone()));
82            }
83            
84            for to_ref in to_refs {
85                if !self.reference_exists(to_ref) {
86                    errors.push(LinkerError::BrokenReference {
87                        from: from_ref.clone(),
88                        to: to_ref.clone(),
89                    });
90                }
91            }
92        }
93        
94        if errors.is_empty() {
95            Ok(())
96        } else {
97            Err(errors)
98        }
99    }
100    
101    /// Get statistics about registered entities
102    pub fn get_statistics(&self) -> RelationshipStatistics {
103        RelationshipStatistics {
104            total_entities: self.reverse_lookup.len(),
105            releases: self.registry.get(&EntityType::Release).map(|m| m.len()).unwrap_or(0),
106            resources: self.registry.get(&EntityType::Resource).map(|m| m.len()).unwrap_or(0),
107            parties: self.registry.get(&EntityType::Party).map(|m| m.len()).unwrap_or(0),
108            deals: self.registry.get(&EntityType::Deal).map(|m| m.len()).unwrap_or(0),
109            total_relationships: self.relationships.values().map(|s| s.len()).sum(),
110        }
111    }
112}
113
114/// Statistics about managed relationships
115#[derive(Debug, Clone)]
116pub struct RelationshipStatistics {
117    pub total_entities: usize,
118    pub releases: usize,
119    pub resources: usize,
120    pub parties: usize,
121    pub deals: usize,
122    pub total_relationships: usize,
123}