ddex_builder/messages/
update_release.rs

1//! UpdateReleaseMessage implementation for DDEX Builder
2//! 
3//! This module provides support for generating and applying incremental DDEX updates
4//! using the UpdateReleaseMessage format, allowing efficient partial updates to
5//! existing DDEX messages without resending entire catalogs.
6
7use crate::error::BuildError;
8use crate::builder::MessageHeaderRequest;
9use crate::diff::DiffEngine;
10use crate::diff::types::{ChangeSet, SemanticChange, ChangeType};
11use serde::{Serialize, Deserialize};
12use indexmap::{IndexMap, IndexSet};
13use chrono::{DateTime, Utc};
14
15/// Complete UpdateReleaseMessage structure
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct UpdateReleaseMessage {
18    /// Message header information
19    pub header: MessageHeaderRequest,
20    
21    /// List of update operations to perform
22    pub update_list: Vec<UpdateOperation>,
23    
24    /// Resources that need to be updated
25    pub resource_updates: IndexMap<String, ResourceUpdate>,
26    
27    /// Releases that need to be updated  
28    pub release_updates: IndexMap<String, ReleaseUpdate>,
29    
30    /// Deal updates (if any)
31    pub deal_updates: IndexMap<String, DealUpdate>,
32    
33    /// Metadata about this update
34    pub update_metadata: UpdateMetadata,
35}
36
37/// Individual update operation
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct UpdateOperation {
40    /// Unique identifier for this update operation
41    pub operation_id: String,
42    
43    /// Type of update action
44    pub action: UpdateAction,
45    
46    /// Path to the element being updated
47    pub target_path: String,
48    
49    /// Entity type being updated
50    pub entity_type: EntityType,
51    
52    /// Entity ID being updated
53    pub entity_id: String,
54    
55    /// Old value (for Replace operations)
56    pub old_value: Option<String>,
57    
58    /// New value (for Add/Replace operations)
59    pub new_value: Option<String>,
60    
61    /// Whether this update is critical
62    pub is_critical: bool,
63    
64    /// Human-readable description
65    pub description: String,
66    
67    /// Dependencies on other operations
68    pub dependencies: Vec<String>,
69}
70
71/// Types of update actions
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73pub enum UpdateAction {
74    /// Add a new element or attribute
75    Add,
76    /// Delete an existing element or attribute
77    Delete,
78    /// Replace an existing element or attribute
79    Replace,
80    /// Move an element to a different location
81    Move,
82}
83
84/// Entity types that can be updated
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
86pub enum EntityType {
87    /// Sound recording resource
88    Resource,
89    /// Release
90    Release,
91    /// Deal
92    Deal,
93    /// Party information
94    Party,
95    /// Message metadata
96    Metadata,
97}
98
99/// Resource update information
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ResourceUpdate {
102    /// Resource identifier
103    pub resource_id: String,
104    
105    /// Resource reference (may change)
106    pub resource_reference: String,
107    
108    /// Update action for this resource
109    pub action: UpdateAction,
110    
111    /// Updated resource data (for Add/Replace)
112    pub resource_data: Option<ResourceData>,
113    
114    /// Technical details updates
115    pub technical_updates: Vec<TechnicalUpdate>,
116    
117    /// Metadata updates
118    pub metadata_updates: IndexMap<String, String>,
119}
120
121/// Release update information
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ReleaseUpdate {
124    /// Release identifier
125    pub release_id: String,
126    
127    /// Release reference (may change)
128    pub release_reference: String,
129    
130    /// Update action for this release
131    pub action: UpdateAction,
132    
133    /// Updated release data (for Add/Replace)
134    pub release_data: Option<ReleaseData>,
135    
136    /// Track updates within this release
137    pub track_updates: Vec<TrackUpdate>,
138    
139    /// Resource reference updates
140    pub resource_reference_updates: Vec<ReferenceUpdate>,
141    
142    /// Metadata updates
143    pub metadata_updates: IndexMap<String, String>,
144}
145
146/// Deal update information
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct DealUpdate {
149    /// Deal identifier
150    pub deal_id: String,
151    
152    /// Deal reference
153    pub deal_reference: String,
154    
155    /// Update action for this deal
156    pub action: UpdateAction,
157    
158    /// Updated deal data
159    pub deal_data: Option<DealData>,
160    
161    /// Terms updates
162    pub terms_updates: Vec<TermsUpdate>,
163}
164
165/// Resource data for updates
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct ResourceData {
168    pub resource_type: String,
169    pub title: String,
170    pub artist: String,
171    pub isrc: Option<String>,
172    pub duration: Option<String>,
173    pub file_path: Option<String>,
174    pub technical_details: Option<TechnicalDetails>,
175}
176
177/// Release data for updates
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct ReleaseData {
180    pub release_type: String,
181    pub title: String,
182    pub artist: String,
183    pub label: Option<String>,
184    pub upc: Option<String>,
185    pub release_date: Option<String>,
186    pub genre: Option<String>,
187    pub resource_references: Vec<String>,
188}
189
190/// Deal data for updates
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct DealData {
193    pub commercial_model_type: String,
194    pub territory_codes: Vec<String>,
195    pub start_date: Option<String>,
196    pub end_date: Option<String>,
197    pub price: Option<PriceData>,
198}
199
200/// Technical update information
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct TechnicalUpdate {
203    pub field_name: String,
204    pub old_value: Option<String>,
205    pub new_value: Option<String>,
206    pub update_action: UpdateAction,
207}
208
209/// Track update within a release
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct TrackUpdate {
212    pub track_id: String,
213    pub action: UpdateAction,
214    pub old_resource_reference: Option<String>,
215    pub new_resource_reference: Option<String>,
216    pub position_change: Option<PositionChange>,
217}
218
219/// Reference update information
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct ReferenceUpdate {
222    pub old_reference: String,
223    pub new_reference: String,
224    pub reference_type: String,
225    pub update_reason: String,
226}
227
228/// Terms update for deals
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct TermsUpdate {
231    pub field_name: String,
232    pub old_value: Option<String>,
233    pub new_value: Option<String>,
234    pub effective_date: Option<String>,
235}
236
237/// Position change information
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct PositionChange {
240    pub old_position: usize,
241    pub new_position: usize,
242}
243
244/// Technical details
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct TechnicalDetails {
247    pub file_name: Option<String>,
248    pub codec_type: Option<String>,
249    pub bit_rate: Option<String>,
250    pub sample_rate: Option<String>,
251}
252
253/// Price data
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct PriceData {
256    pub amount: String,
257    pub currency_code: String,
258    pub price_type: Option<String>,
259}
260
261/// Metadata about the update
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct UpdateMetadata {
264    /// Original message ID being updated
265    pub original_message_id: String,
266    
267    /// Version of the original message
268    pub original_message_version: Option<String>,
269    
270    /// Timestamp of the original message
271    pub original_message_timestamp: Option<DateTime<Utc>>,
272    
273    /// Update creation timestamp
274    pub update_created_timestamp: DateTime<Utc>,
275    
276    /// Update sequence number (for ordering)
277    pub update_sequence: u64,
278    
279    /// Total number of operations in this update
280    pub total_operations: usize,
281    
282    /// Estimated impact level
283    pub impact_level: String,
284    
285    /// Update validation status
286    pub validation_status: ValidationStatus,
287    
288    /// Additional metadata
289    pub custom_metadata: IndexMap<String, String>,
290}
291
292/// Validation status for updates
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
294pub enum ValidationStatus {
295    /// Update has been validated and is safe to apply
296    Validated,
297    /// Update has validation warnings but can be applied
298    WarningsOnly,
299    /// Update has validation errors and should not be applied
300    Invalid,
301    /// Update validation is pending
302    Pending,
303}
304
305/// Configuration for update generation
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub struct UpdateConfig {
308    /// Include non-critical changes in updates
309    pub include_non_critical: bool,
310    
311    /// Maximum number of operations per update message
312    pub max_operations_per_update: usize,
313    
314    /// Whether to validate references during update generation
315    pub validate_references: bool,
316    
317    /// Whether to optimize reference updates
318    pub optimize_references: bool,
319    
320    /// Fields to exclude from updates
321    pub excluded_fields: IndexSet<String>,
322    
323    /// Custom update priorities
324    pub update_priorities: IndexMap<String, u8>,
325}
326
327impl Default for UpdateConfig {
328    fn default() -> Self {
329        let mut excluded_fields = IndexSet::new();
330        excluded_fields.insert("MessageId".to_string());
331        excluded_fields.insert("MessageCreatedDateTime".to_string());
332        
333        Self {
334            include_non_critical: true,
335            max_operations_per_update: 1000,
336            validate_references: true,
337            optimize_references: true,
338            excluded_fields,
339            update_priorities: IndexMap::new(),
340        }
341    }
342}
343
344/// Update generation engine
345pub struct UpdateGenerator {
346    config: UpdateConfig,
347    diff_engine: DiffEngine,
348    operation_counter: u64,
349}
350
351impl UpdateGenerator {
352    /// Create a new update generator
353    pub fn new() -> Self {
354        Self {
355            config: UpdateConfig::default(),
356            diff_engine: DiffEngine::new(),
357            operation_counter: 0,
358        }
359    }
360    
361    /// Create a new update generator with custom configuration
362    pub fn new_with_config(config: UpdateConfig) -> Self {
363        Self {
364            config,
365            diff_engine: DiffEngine::new(),
366            operation_counter: 0,
367        }
368    }
369    
370    /// Generate an UpdateReleaseMessage from two DDEX messages
371    pub fn create_update(
372        &mut self,
373        original_xml: &str,
374        updated_xml: &str,
375        original_message_id: &str,
376    ) -> Result<UpdateReleaseMessage, BuildError> {
377        // Parse both messages to ASTs
378        let original_ast = self.parse_xml_to_ast(original_xml)?;
379        let updated_ast = self.parse_xml_to_ast(updated_xml)?;
380        
381        // Generate semantic diff
382        let changeset = self.diff_engine.diff(&original_ast, &updated_ast)?;
383        
384        // Convert changeset to update operations
385        let update_operations = self.changeset_to_operations(&changeset)?;
386        
387        // Group operations by entity type
388        let (resource_updates, release_updates, deal_updates) = 
389            self.group_operations_by_entity(&update_operations)?;
390        
391        // Create update metadata
392        let metadata = self.create_update_metadata(
393            original_message_id,
394            &update_operations,
395            &changeset,
396        );
397        
398        // Generate message header
399        let header = self.create_update_header(original_message_id, &metadata);
400        
401        let update_message = UpdateReleaseMessage {
402            header,
403            update_list: update_operations,
404            resource_updates,
405            release_updates,
406            deal_updates,
407            update_metadata: metadata,
408        };
409        
410        // Validate the update
411        self.validate_update(&update_message)?;
412        
413        Ok(update_message)
414    }
415    
416    /// Apply an update to a base message to produce a new complete message
417    pub fn apply_update(
418        &self,
419        base_xml: &str,
420        update: &UpdateReleaseMessage,
421    ) -> Result<String, BuildError> {
422        // Parse base message
423        let mut base_ast = self.parse_xml_to_ast(base_xml)?;
424        
425        // Apply each operation in dependency order
426        let ordered_operations = self.order_operations_by_dependencies(&update.update_list)?;
427        
428        for operation in &ordered_operations {
429            self.apply_operation_to_ast(&mut base_ast, operation)?;
430        }
431        
432        // Apply entity-level updates
433        self.apply_resource_updates(&mut base_ast, &update.resource_updates)?;
434        self.apply_release_updates(&mut base_ast, &update.release_updates)?;
435        self.apply_deal_updates(&mut base_ast, &update.deal_updates)?;
436        
437        // Serialize back to XML
438        self.ast_to_xml(&base_ast)
439    }
440    
441    /// Validate an update for consistency and safety
442    pub fn validate_update(&self, update: &UpdateReleaseMessage) -> Result<ValidationStatus, BuildError> {
443        let mut errors = Vec::new();
444        let mut warnings = Vec::new();
445        
446        // Validate operations
447        for operation in &update.update_list {
448            if let Err(e) = self.validate_operation(operation, update) {
449                errors.push(format!("Operation {}: {}", operation.operation_id, e));
450            }
451        }
452        
453        // Validate references
454        if self.config.validate_references {
455            if let Err(e) = self.validate_references(update) {
456                errors.push(format!("Reference validation: {}", e));
457            }
458        }
459        
460        // Validate dependencies
461        if let Err(e) = self.validate_dependencies(&update.update_list) {
462            errors.push(format!("Dependency validation: {}", e));
463        }
464        
465        // Check for conflicts
466        let conflicts = self.detect_conflicts(&update.update_list)?;
467        if !conflicts.is_empty() {
468            warnings.push(format!("Found {} potential conflicts", conflicts.len()));
469        }
470        
471        if !errors.is_empty() {
472            Err(BuildError::ValidationFailed { errors })
473        } else if !warnings.is_empty() {
474            Ok(ValidationStatus::WarningsOnly)
475        } else {
476            Ok(ValidationStatus::Validated)
477        }
478    }
479    
480    // Private helper methods
481    
482    fn parse_xml_to_ast(&self, xml: &str) -> Result<crate::ast::AST, BuildError> {
483        // Simplified XML parsing - in production, use proper DDEX parser
484        let root = crate::ast::Element::new("NewReleaseMessage").with_text(xml);
485        Ok(crate::ast::AST {
486            root,
487            namespaces: IndexMap::new(),
488            schema_location: None,
489        })
490    }
491    
492    fn changeset_to_operations(&mut self, changeset: &ChangeSet) -> Result<Vec<UpdateOperation>, BuildError> {
493        let mut operations = Vec::new();
494        
495        for change in &changeset.changes {
496            let operation = self.semantic_change_to_operation(change)?;
497            operations.push(operation);
498        }
499        
500        Ok(operations)
501    }
502    
503    fn semantic_change_to_operation(&mut self, change: &SemanticChange) -> Result<UpdateOperation, BuildError> {
504        self.operation_counter += 1;
505        
506        let action = match change.change_type {
507            ChangeType::ElementAdded | ChangeType::AttributeAdded => UpdateAction::Add,
508            ChangeType::ElementRemoved | ChangeType::AttributeRemoved => UpdateAction::Delete,
509            ChangeType::ElementMoved => UpdateAction::Move,
510            _ => UpdateAction::Replace,
511        };
512        
513        let entity_type = self.determine_entity_type(&change.path);
514        let entity_id = self.extract_entity_id(&change.path)?;
515        
516        Ok(UpdateOperation {
517            operation_id: format!("OP{:06}", self.operation_counter),
518            action,
519            target_path: change.path.to_string(),
520            entity_type,
521            entity_id,
522            old_value: change.old_value.clone(),
523            new_value: change.new_value.clone(),
524            is_critical: change.is_critical,
525            description: change.description.clone(),
526            dependencies: Vec::new(), // Will be filled in later pass
527        })
528    }
529    
530    fn determine_entity_type(&self, path: &crate::diff::types::DiffPath) -> EntityType {
531        let path_str = path.to_string().to_lowercase();
532        
533        if path_str.contains("resource") {
534            EntityType::Resource
535        } else if path_str.contains("release") {
536            EntityType::Release
537        } else if path_str.contains("deal") {
538            EntityType::Deal
539        } else if path_str.contains("party") {
540            EntityType::Party
541        } else {
542            EntityType::Metadata
543        }
544    }
545    
546    fn extract_entity_id(&self, path: &crate::diff::types::DiffPath) -> Result<String, BuildError> {
547        // Extract entity ID from path - simplified implementation
548        let path_str = path.to_string();
549        if let Some(id_start) = path_str.find("Id=") {
550            let id_part = &path_str[id_start + 3..];
551            if let Some(id_end) = id_part.find(&[']', '/', '@'][..]) {
552                Ok(id_part[..id_end].to_string())
553            } else {
554                Ok(id_part.to_string())
555            }
556        } else {
557            let uuid_str = uuid::Uuid::new_v4().to_string();
558            Ok(format!("unknown_{}", &uuid_str[..8]))
559        }
560    }
561    
562    fn group_operations_by_entity(
563        &self,
564        operations: &[UpdateOperation],
565    ) -> Result<(IndexMap<String, ResourceUpdate>, IndexMap<String, ReleaseUpdate>, IndexMap<String, DealUpdate>), BuildError> {
566        let mut resource_updates = IndexMap::new();
567        let mut release_updates = IndexMap::new();
568        let mut deal_updates = IndexMap::new();
569        
570        for operation in operations {
571            match operation.entity_type {
572                EntityType::Resource => {
573                    let resource_update = self.operation_to_resource_update(operation)?;
574                    resource_updates.insert(operation.entity_id.clone(), resource_update);
575                },
576                EntityType::Release => {
577                    let release_update = self.operation_to_release_update(operation)?;
578                    release_updates.insert(operation.entity_id.clone(), release_update);
579                },
580                EntityType::Deal => {
581                    let deal_update = self.operation_to_deal_update(operation)?;
582                    deal_updates.insert(operation.entity_id.clone(), deal_update);
583                },
584                _ => {}, // Handle metadata and party updates separately if needed
585            }
586        }
587        
588        Ok((resource_updates, release_updates, deal_updates))
589    }
590    
591    fn operation_to_resource_update(&self, operation: &UpdateOperation) -> Result<ResourceUpdate, BuildError> {
592        Ok(ResourceUpdate {
593            resource_id: operation.entity_id.clone(),
594            resource_reference: format!("R{:06}", operation.operation_id[2..].parse::<u32>().unwrap_or(0)),
595            action: operation.action,
596            resource_data: None, // Would be populated from operation details
597            technical_updates: Vec::new(),
598            metadata_updates: IndexMap::new(),
599        })
600    }
601    
602    fn operation_to_release_update(&self, operation: &UpdateOperation) -> Result<ReleaseUpdate, BuildError> {
603        Ok(ReleaseUpdate {
604            release_id: operation.entity_id.clone(),
605            release_reference: format!("REL{:06}", operation.operation_id[2..].parse::<u32>().unwrap_or(0)),
606            action: operation.action,
607            release_data: None, // Would be populated from operation details
608            track_updates: Vec::new(),
609            resource_reference_updates: Vec::new(),
610            metadata_updates: IndexMap::new(),
611        })
612    }
613    
614    fn operation_to_deal_update(&self, operation: &UpdateOperation) -> Result<DealUpdate, BuildError> {
615        Ok(DealUpdate {
616            deal_id: operation.entity_id.clone(),
617            deal_reference: format!("D{:06}", operation.operation_id[2..].parse::<u32>().unwrap_or(0)),
618            action: operation.action,
619            deal_data: None, // Would be populated from operation details
620            terms_updates: Vec::new(),
621        })
622    }
623    
624    fn create_update_metadata(
625        &self,
626        original_message_id: &str,
627        operations: &[UpdateOperation],
628        changeset: &ChangeSet,
629    ) -> UpdateMetadata {
630        UpdateMetadata {
631            original_message_id: original_message_id.to_string(),
632            original_message_version: None,
633            original_message_timestamp: None,
634            update_created_timestamp: Utc::now(),
635            update_sequence: 1,
636            total_operations: operations.len(),
637            impact_level: changeset.impact_level().to_string(),
638            validation_status: ValidationStatus::Pending,
639            custom_metadata: IndexMap::new(),
640        }
641    }
642    
643    fn create_update_header(&self, original_message_id: &str, metadata: &UpdateMetadata) -> MessageHeaderRequest {
644        MessageHeaderRequest {
645            message_id: Some(format!("UPD-{}-{:04}", original_message_id, metadata.update_sequence)),
646            message_sender: crate::builder::PartyRequest {
647                party_name: vec![crate::builder::LocalizedStringRequest {
648                    text: "DDEX Builder Update Engine".to_string(),
649                    language_code: None,
650                }],
651                party_id: None,
652                party_reference: None,
653            },
654            message_recipient: crate::builder::PartyRequest {
655                party_name: vec![crate::builder::LocalizedStringRequest {
656                    text: "Update Recipient".to_string(),
657                    language_code: None,
658                }],
659                party_id: None,
660                party_reference: None,
661            },
662            message_control_type: Some("UpdateMessage".to_string()),
663            message_created_date_time: Some(metadata.update_created_timestamp.to_rfc3339()),
664        }
665    }
666    
667    fn order_operations_by_dependencies(&self, operations: &[UpdateOperation]) -> Result<Vec<UpdateOperation>, BuildError> {
668        // Simplified topological sort - in production, implement proper dependency resolution
669        let mut ordered = operations.to_vec();
670        
671        // Sort by operation ID as a simple ordering
672        ordered.sort_by(|a, b| a.operation_id.cmp(&b.operation_id));
673        
674        Ok(ordered)
675    }
676    
677    fn apply_operation_to_ast(&self, _ast: &mut crate::ast::AST, operation: &UpdateOperation) -> Result<(), BuildError> {
678        // Simplified AST modification - in production, implement proper AST updates
679        match operation.action {
680            UpdateAction::Add => {
681                // Add logic here
682            },
683            UpdateAction::Delete => {
684                // Delete logic here
685            },
686            UpdateAction::Replace => {
687                // Replace logic here
688            },
689            UpdateAction::Move => {
690                // Move logic here
691            },
692        }
693        Ok(())
694    }
695    
696    fn apply_resource_updates(&self, _ast: &mut crate::ast::AST, updates: &IndexMap<String, ResourceUpdate>) -> Result<(), BuildError> {
697        // Apply resource-specific updates
698        for (_resource_id, _update) in updates {
699            // Implementation would modify AST based on resource update
700        }
701        Ok(())
702    }
703    
704    fn apply_release_updates(&self, _ast: &mut crate::ast::AST, updates: &IndexMap<String, ReleaseUpdate>) -> Result<(), BuildError> {
705        // Apply release-specific updates
706        for (_release_id, _update) in updates {
707            // Implementation would modify AST based on release update
708        }
709        Ok(())
710    }
711    
712    fn apply_deal_updates(&self, _ast: &mut crate::ast::AST, updates: &IndexMap<String, DealUpdate>) -> Result<(), BuildError> {
713        // Apply deal-specific updates
714        for (_deal_id, _update) in updates {
715            // Implementation would modify AST based on deal update
716        }
717        Ok(())
718    }
719    
720    fn ast_to_xml(&self, _ast: &crate::ast::AST) -> Result<String, BuildError> {
721        // Simplified XML serialization - in production, use proper XML generation
722        Ok(format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Updated DDEX Message -->\n"))
723    }
724    
725    fn validate_operation(&self, operation: &UpdateOperation, _update: &UpdateReleaseMessage) -> Result<(), BuildError> {
726        // Validate individual operation
727        if operation.entity_id.is_empty() {
728            return Err(BuildError::InvalidFormat {
729                field: "entity_id".to_string(),
730                message: "Entity ID cannot be empty".to_string(),
731            });
732        }
733        
734        // Validate action compatibility
735        match operation.action {
736            UpdateAction::Add => {
737                if operation.new_value.is_none() {
738                    return Err(BuildError::InvalidFormat {
739                        field: "new_value".to_string(),
740                        message: "Add operation requires new_value".to_string(),
741                    });
742                }
743            },
744            UpdateAction::Delete => {
745                if operation.old_value.is_none() {
746                    return Err(BuildError::InvalidFormat {
747                        field: "old_value".to_string(),
748                        message: "Delete operation requires old_value".to_string(),
749                    });
750                }
751            },
752            UpdateAction::Replace => {
753                if operation.old_value.is_none() || operation.new_value.is_none() {
754                    return Err(BuildError::InvalidFormat {
755                        field: "values".to_string(),
756                        message: "Replace operation requires both old_value and new_value".to_string(),
757                    });
758                }
759            },
760            UpdateAction::Move => {
761                // Move operations have specific validation requirements
762            },
763        }
764        
765        Ok(())
766    }
767    
768    fn validate_references(&self, update: &UpdateReleaseMessage) -> Result<(), BuildError> {
769        // Validate that all referenced entities exist
770        let mut referenced_resources = IndexSet::new();
771        let mut referenced_releases = IndexSet::new();
772        
773        // Collect all references
774        for operation in &update.update_list {
775            match operation.entity_type {
776                EntityType::Resource => {
777                    referenced_resources.insert(operation.entity_id.clone());
778                },
779                EntityType::Release => {
780                    referenced_releases.insert(operation.entity_id.clone());
781                },
782                _ => {},
783            }
784        }
785        
786        // Check that referenced entities have corresponding updates
787        for resource_id in &referenced_resources {
788            if !update.resource_updates.contains_key(resource_id) {
789                return Err(BuildError::InvalidReference {
790                    reference: resource_id.clone(),
791                });
792            }
793        }
794        
795        Ok(())
796    }
797    
798    fn validate_dependencies(&self, operations: &[UpdateOperation]) -> Result<(), BuildError> {
799        let operation_ids: IndexSet<_> = operations.iter().map(|op| &op.operation_id).collect();
800        
801        for operation in operations {
802            for dependency in &operation.dependencies {
803                if !operation_ids.contains(&dependency) {
804                    return Err(BuildError::InvalidReference {
805                        reference: format!("Missing dependency: {}", dependency),
806                    });
807                }
808            }
809        }
810        
811        Ok(())
812    }
813    
814    fn detect_conflicts(&self, operations: &[UpdateOperation]) -> Result<Vec<String>, BuildError> {
815        let mut conflicts = Vec::new();
816        
817        // Check for operations targeting the same path
818        let mut path_operations: IndexMap<String, Vec<&UpdateOperation>> = IndexMap::new();
819        
820        for operation in operations {
821            path_operations.entry(operation.target_path.clone())
822                .or_default()
823                .push(operation);
824        }
825        
826        for (path, ops) in path_operations {
827            if ops.len() > 1 {
828                let conflicting_ops: Vec<_> = ops.iter().map(|op| &op.operation_id).collect();
829                conflicts.push(format!("Path {} has conflicting operations: {:?}", path, conflicting_ops));
830            }
831        }
832        
833        Ok(conflicts)
834    }
835}
836
837impl Default for UpdateGenerator {
838    fn default() -> Self {
839        Self::new()
840    }
841}
842
843// Display implementations for better debugging
844impl std::fmt::Display for UpdateAction {
845    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
846        match self {
847            UpdateAction::Add => write!(f, "Add"),
848            UpdateAction::Delete => write!(f, "Delete"),
849            UpdateAction::Replace => write!(f, "Replace"),
850            UpdateAction::Move => write!(f, "Move"),
851        }
852    }
853}
854
855impl std::fmt::Display for EntityType {
856    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
857        match self {
858            EntityType::Resource => write!(f, "Resource"),
859            EntityType::Release => write!(f, "Release"),
860            EntityType::Deal => write!(f, "Deal"),
861            EntityType::Party => write!(f, "Party"),
862            EntityType::Metadata => write!(f, "Metadata"),
863        }
864    }
865}
866
867impl std::fmt::Display for ValidationStatus {
868    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
869        match self {
870            ValidationStatus::Validated => write!(f, "Validated"),
871            ValidationStatus::WarningsOnly => write!(f, "Warnings Only"),
872            ValidationStatus::Invalid => write!(f, "Invalid"),
873            ValidationStatus::Pending => write!(f, "Pending"),
874        }
875    }
876}
877
878#[cfg(test)]
879mod tests {
880    use super::*;
881    
882    #[test]
883    fn test_update_generator_creation() {
884        let generator = UpdateGenerator::new();
885        assert_eq!(generator.operation_counter, 0);
886    }
887    
888    #[test]
889    fn test_update_config_defaults() {
890        let config = UpdateConfig::default();
891        assert!(config.include_non_critical);
892        assert_eq!(config.max_operations_per_update, 1000);
893        assert!(config.validate_references);
894    }
895    
896    #[test]
897    fn test_operation_validation() {
898        let generator = UpdateGenerator::new();
899        
900        let operation = UpdateOperation {
901            operation_id: "OP000001".to_string(),
902            action: UpdateAction::Add,
903            target_path: "/Release/Title".to_string(),
904            entity_type: EntityType::Release,
905            entity_id: "release-001".to_string(),
906            old_value: None,
907            new_value: Some("New Title".to_string()),
908            is_critical: false,
909            description: "Update title".to_string(),
910            dependencies: Vec::new(),
911        };
912        
913        let update = UpdateReleaseMessage {
914            header: MessageHeaderRequest {
915                message_id: Some("TEST-001".to_string()),
916                message_sender: crate::builder::PartyRequest {
917                    party_name: vec![crate::builder::LocalizedStringRequest {
918                        text: "Test".to_string(),
919                        language_code: None,
920                    }],
921                    party_id: None,
922                    party_reference: None,
923                },
924                message_recipient: crate::builder::PartyRequest {
925                    party_name: vec![crate::builder::LocalizedStringRequest {
926                        text: "Test".to_string(),
927                        language_code: None,
928                    }],
929                    party_id: None,
930                    party_reference: None,
931                },
932                message_control_type: None,
933                message_created_date_time: None,
934            },
935            update_list: vec![operation.clone()],
936            resource_updates: IndexMap::new(),
937            release_updates: IndexMap::new(),
938            deal_updates: IndexMap::new(),
939            update_metadata: UpdateMetadata {
940                original_message_id: "ORIG-001".to_string(),
941                original_message_version: None,
942                original_message_timestamp: None,
943                update_created_timestamp: Utc::now(),
944                update_sequence: 1,
945                total_operations: 1,
946                impact_level: "Low".to_string(),
947                validation_status: ValidationStatus::Pending,
948                custom_metadata: IndexMap::new(),
949            },
950        };
951        
952        assert!(generator.validate_operation(&operation, &update).is_ok());
953    }
954    
955    #[test]
956    fn test_entity_type_determination() {
957        let generator = UpdateGenerator::new();
958        
959        let resource_path = crate::diff::types::DiffPath::root()
960            .with_element("ResourceList")
961            .with_element("SoundRecording");
962        
963        let release_path = crate::diff::types::DiffPath::root()
964            .with_element("ReleaseList")
965            .with_element("Release");
966        
967        assert_eq!(generator.determine_entity_type(&resource_path), EntityType::Resource);
968        assert_eq!(generator.determine_entity_type(&release_path), EntityType::Release);
969    }
970}