ddex_builder/linker/
auto_linker.rs

1//! Automatic linking logic for DDEX entities
2
3use super::{EntityType, LinkerError, LinkingReport, ReferenceGenerator, RelationshipManager};
4use crate::builder::BuildRequest;
5use indexmap::IndexSet;
6
7/// Handles automatic linking of entities in a build request
8#[derive(Debug, Clone, Default)]
9pub struct AutoLinker {
10    /// Configuration for auto-linking behavior
11    config: AutoLinkerConfig,
12}
13
14/// Configuration for auto-linking
15#[derive(Debug, Clone)]
16pub struct AutoLinkerConfig {
17    /// Automatically generate missing references
18    pub generate_missing_refs: bool,
19    
20    /// Automatically link releases to all their tracks
21    pub auto_link_tracks: bool,
22    
23    /// Automatically link deals to releases
24    pub auto_link_deals: bool,
25    
26    /// Validate reference integrity
27    pub validate_references: bool,
28}
29
30impl Default for AutoLinkerConfig {
31    fn default() -> Self {
32        Self {
33            generate_missing_refs: true,
34            auto_link_tracks: true,
35            auto_link_deals: true,
36            validate_references: true,
37        }
38    }
39}
40
41impl AutoLinker {
42    /// Create a new auto-linker
43    pub fn new() -> Self {
44        Self::with_config(AutoLinkerConfig::default())
45    }
46    
47    /// Create auto-linker with custom configuration
48    pub fn with_config(config: AutoLinkerConfig) -> Self {
49        Self { config }
50    }
51    
52    /// Process a build request and automatically link entities
53    pub fn process_request(
54        &self,
55        request: &mut BuildRequest,
56        generator: &mut ReferenceGenerator,
57        relationships: &mut RelationshipManager,
58    ) -> Result<LinkingReport, LinkerError> {
59        let mut report = LinkingReport::default();
60        
61        // Phase 1: Register all resources (tracks) first
62        for release in &mut request.releases {
63            for track in &mut release.tracks {
64                if track.resource_reference.is_none() {
65                    let reference = generator.generate(EntityType::Resource);
66                    track.resource_reference = Some(reference.clone());
67                    relationships.register(
68                        EntityType::Resource,
69                        track.track_id.clone(),
70                        reference,
71                    );
72                    report.generated_refs += 1;
73                }
74            }
75        }
76        
77        // Phase 2: Register and link releases
78        for release in &mut request.releases {
79            // Generate release reference if missing
80            if release.release_reference.is_none() {
81                let reference = generator.generate(EntityType::Release);
82                release.release_reference = Some(reference.clone());
83                relationships.register(
84                    EntityType::Release,
85                    release.release_id.clone(),
86                    reference,
87                );
88                report.generated_refs += 1;
89            }
90            
91            // Auto-link tracks to release
92            if self.config.auto_link_tracks {
93                let release_ref = release.release_reference.as_ref().unwrap();
94                let mut track_refs = IndexSet::new();
95                
96                for track in &release.tracks {
97                    if let Some(track_ref) = &track.resource_reference {
98                        track_refs.insert(track_ref.clone());
99                        relationships.add_relationship(
100                            release_ref.clone(),
101                            track_ref.clone(),
102                        );
103                        report.linked_resources += 1;
104                    }
105                }
106                
107                // Update release with resource references
108                release.resource_references = Some(track_refs.into_iter().collect());
109            }
110        }
111        
112        // Phase 3: Register parties (sender/recipient)
113        if request.header.message_sender.party_reference.is_none() {
114            let sender_ref = generator.generate(EntityType::Party);
115            request.header.message_sender.party_reference = Some(sender_ref.clone());
116            relationships.register(
117                EntityType::Party,
118                request.header.message_sender.party_id.clone().unwrap_or_default(),
119                sender_ref,
120            );
121            report.generated_refs += 1;
122        }
123        
124        if request.header.message_recipient.party_reference.is_none() {
125            let recipient_ref = generator.generate(EntityType::Party);
126            request.header.message_recipient.party_reference = Some(recipient_ref.clone());
127            relationships.register(
128                EntityType::Party,
129                request.header.message_recipient.party_id.clone().unwrap_or_default(),
130                recipient_ref,
131            );
132            report.generated_refs += 1;
133        }
134        
135        // Phase 4: Link deals
136        if self.config.auto_link_deals {
137            for deal in &mut request.deals {
138                if deal.deal_reference.is_none() {
139                    let deal_ref = generator.generate(EntityType::Deal);
140                    deal.deal_reference = Some(deal_ref.clone());
141                    report.generated_refs += 1;
142                }
143                
144                // Link deal to releases
145                if let Some(ref deal_ref) = deal.deal_reference {
146                    for release_ref in &deal.release_references {
147                        relationships.add_relationship(
148                            deal_ref.clone(),
149                            release_ref.clone(),
150                        );
151                        report.linked_deals += 1;
152                    }
153                }
154            }
155        }
156        
157        // Phase 5: Validate if configured
158        if self.config.validate_references {
159            match relationships.validate() {
160                Ok(()) => report.validation_passed = true,
161                Err(errors) => {
162                    // Convert validation errors to warnings
163                    for error in errors {
164                        report.warnings.push(format!("Validation: {}", error));
165                    }
166                    // Still mark as passed if we only have warnings
167                    report.validation_passed = true;
168                }
169            }
170        }
171        
172        Ok(report)
173    }
174}