ddex_builder/linker/
auto_linker.rs

1//! Automatic linking logic for DDEX entities
2
3use super::{EntityType, LinkingError, 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, LinkingError> {
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(EntityType::Resource, track.track_id.clone(), reference);
68                    report.generated_refs += 1;
69                }
70            }
71        }
72
73        // Phase 2: Register and link releases
74        for release in &mut request.releases {
75            // Generate release reference if missing
76            if release.release_reference.is_none() {
77                let reference = generator.generate(EntityType::Release);
78                release.release_reference = Some(reference.clone());
79                relationships.register(EntityType::Release, release.release_id.clone(), reference);
80                report.generated_refs += 1;
81            }
82
83            // Auto-link tracks to release
84            if self.config.auto_link_tracks {
85                let release_ref = release.release_reference.as_ref().unwrap();
86                let mut track_refs = IndexSet::new();
87
88                for track in &release.tracks {
89                    if let Some(track_ref) = &track.resource_reference {
90                        track_refs.insert(track_ref.clone());
91                        relationships.add_relationship(release_ref.clone(), track_ref.clone());
92                        report.linked_resources += 1;
93                    }
94                }
95
96                // Update release with resource references
97                release.resource_references = Some(track_refs.into_iter().collect());
98            }
99        }
100
101        // Phase 3: Register parties (sender/recipient)
102        if request.header.message_sender.party_reference.is_none() {
103            let sender_ref = generator.generate(EntityType::Party);
104            request.header.message_sender.party_reference = Some(sender_ref.clone());
105            relationships.register(
106                EntityType::Party,
107                request
108                    .header
109                    .message_sender
110                    .party_id
111                    .clone()
112                    .unwrap_or_default(),
113                sender_ref,
114            );
115            report.generated_refs += 1;
116        }
117
118        if request.header.message_recipient.party_reference.is_none() {
119            let recipient_ref = generator.generate(EntityType::Party);
120            request.header.message_recipient.party_reference = Some(recipient_ref.clone());
121            relationships.register(
122                EntityType::Party,
123                request
124                    .header
125                    .message_recipient
126                    .party_id
127                    .clone()
128                    .unwrap_or_default(),
129                recipient_ref,
130            );
131            report.generated_refs += 1;
132        }
133
134        // Phase 4: Link deals
135        if self.config.auto_link_deals {
136            for deal in &mut request.deals {
137                if deal.deal_reference.is_none() {
138                    let deal_ref = generator.generate(EntityType::Deal);
139                    deal.deal_reference = Some(deal_ref.clone());
140                    report.generated_refs += 1;
141                }
142
143                // Link deal to releases
144                if let Some(ref deal_ref) = deal.deal_reference {
145                    for release_ref in &deal.release_references {
146                        relationships.add_relationship(deal_ref.clone(), release_ref.clone());
147                        report.linked_deals += 1;
148                    }
149                }
150            }
151        }
152
153        // Phase 5: Validate if configured
154        if self.config.validate_references {
155            match relationships.validate() {
156                Ok(()) => report.validation_passed = true,
157                Err(errors) => {
158                    // Convert validation errors to warnings
159                    for error in errors {
160                        report.warnings.push(format!("Validation: {}", error));
161                    }
162                    // Still mark as passed if we only have warnings
163                    report.validation_passed = true;
164                }
165            }
166        }
167
168        Ok(report)
169    }
170}