ddex_builder/linker/
relationship_manager.rs

1//! Manages relationships between DDEX entities
2
3use super::types::{EntityType, LinkingError};
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<LinkingError>> {
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(LinkingError::OrphanedReference(from_ref.clone()));
82            }
83
84            for to_ref in to_refs {
85                if !self.reference_exists(to_ref) {
86                    errors.push(LinkingError::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
106                .registry
107                .get(&EntityType::Release)
108                .map(|m| m.len())
109                .unwrap_or(0),
110            resources: self
111                .registry
112                .get(&EntityType::Resource)
113                .map(|m| m.len())
114                .unwrap_or(0),
115            parties: self
116                .registry
117                .get(&EntityType::Party)
118                .map(|m| m.len())
119                .unwrap_or(0),
120            deals: self
121                .registry
122                .get(&EntityType::Deal)
123                .map(|m| m.len())
124                .unwrap_or(0),
125            total_relationships: self.relationships.values().map(|s| s.len()).sum(),
126        }
127    }
128}
129
130/// Statistics about managed relationships
131#[derive(Debug, Clone)]
132pub struct RelationshipStatistics {
133    pub total_entities: usize,
134    pub releases: usize,
135    pub resources: usize,
136    pub parties: usize,
137    pub deals: usize,
138    pub total_relationships: usize,
139}