//! Bidirectional relationship resolution //! //! Handles relationships that can be declared from either participant's //! perspective, merging self/other blocks and validating consistency. use std::collections::HashMap; use crate::{ resolve::{ ResolveError, Result, }, syntax::ast::{ Declaration, Field, File, Participant, Relationship, }, }; /// A relationship key that's order-independent /// (Martha, David) and (David, Martha) map to the same key #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct RelationshipKey { participants: Vec, name: String, } impl RelationshipKey { fn new(mut participants: Vec, name: String) -> Self { // Sort participants to make key order-independent participants.sort(); Self { participants, name } } } /// Information about a relationship declaration #[derive(Debug, Clone)] struct RelationshipDecl { relationship: Relationship, } /// Resolved bidirectional relationship #[derive(Debug, Clone)] pub struct ResolvedRelationship { pub name: String, pub participants: Vec, pub fields: Vec, /// Merged self/other blocks for each participant pub participant_fields: Vec, } #[derive(Debug, Clone)] pub struct ParticipantFields { pub participant_name: Vec, pub role: Option, /// Fields from this participant's block pub fields: Vec, } /// Resolve bidirectional relationships in a file pub fn resolve_relationships(file: &File) -> Result> { // Group relationships by key let mut relationship_groups: HashMap> = HashMap::new(); for decl in &file.declarations { if let Declaration::Relationship(rel) = decl { // Extract participant names let participant_names: Vec = rel.participants.iter().map(|p| p.name.join("::")).collect(); let key = RelationshipKey::new(participant_names, rel.name.clone()); relationship_groups .entry(key) .or_default() .push(RelationshipDecl { relationship: rel.clone(), }); } } // Merge grouped relationships let mut resolved = Vec::new(); for (key, decls) in relationship_groups { let merged = merge_relationship_declarations(&key, decls)?; resolved.push(merged); } Ok(resolved) } /// Merge multiple declarations of the same relationship fn merge_relationship_declarations( key: &RelationshipKey, decls: Vec, ) -> Result { if decls.is_empty() { return Err(ResolveError::ValidationError { message: "Empty relationship group".to_string(), help: Some("This is an internal error - relationship groups should never be empty. Please report this as a bug.".to_string()), }); } // Start with the first declaration let base = &decls[0].relationship; let mut participant_fields: Vec = base .participants .iter() .map(|p| ParticipantFields { participant_name: p.name.clone(), role: p.role.clone(), fields: p.fields.clone(), }) .collect(); // Merge shared fields (fields outside participant blocks) let mut merged_fields = base.fields.clone(); // Merge additional declarations for decl in decls.iter().skip(1) { // Merge participant fields for participant in &decl.relationship.participants { // Find this participant in our merged list if let Some(pf_idx) = participant_fields .iter() .position(|pf| pf.participant_name == participant.name) { // Merge fields for this participant merge_fields( &mut participant_fields[pf_idx].fields, participant.fields.clone(), )?; } } // Merge shared relationship fields merge_fields(&mut merged_fields, decl.relationship.fields.clone())?; } Ok(ResolvedRelationship { name: key.name.clone(), participants: base.participants.clone(), fields: merged_fields, participant_fields, }) } /// Merge field lists, detecting conflicts fn merge_fields(target: &mut Vec, source: Vec) -> Result<()> { for new_field in source { // Check if field already exists if let Some(existing) = target.iter().find(|f| f.name == new_field.name) { // Fields must have the same value if existing.value != new_field.value { return Err(ResolveError::ValidationError { message: format!( "Conflicting values for field '{}' in relationship", new_field.name ), help: Some(format!( "The field '{}' has different values in different declarations of the same relationship. Make sure all declarations of this relationship use the same value for shared fields.", new_field.name )), }); } // Same value, no need to add again } else { // New field, add it target.push(new_field); } } Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::syntax::ast::{ Span, Value, }; fn make_participant(name: &str, role: Option<&str>) -> Participant { Participant { name: vec![name.to_string()], role: role.map(|s| s.to_string()), fields: vec![], span: Span::new(0, 10), } } fn make_field(name: &str, value: i64) -> Field { Field { name: name.to_string(), value: Value::Number(value), span: Span::new(0, 10), } } #[test] fn test_relationship_key_order_independent() { let key1 = RelationshipKey::new( vec!["Martha".to_string(), "David".to_string()], "Marriage".to_string(), ); let key2 = RelationshipKey::new( vec!["David".to_string(), "Martha".to_string()], "Marriage".to_string(), ); assert_eq!(key1, key2); } #[test] fn test_single_relationship_declaration() { let file = File { declarations: vec![Declaration::Relationship(Relationship { name: "Friendship".to_string(), participants: vec![ make_participant("Alice", None), make_participant("Bob", None), ], fields: vec![make_field("bond", 80)], span: Span::new(0, 10), })], }; let resolved = resolve_relationships(&file).unwrap(); assert_eq!(resolved.len(), 1); assert_eq!(resolved[0].name, "Friendship"); assert_eq!(resolved[0].participants.len(), 2); } #[test] fn test_relationship_merge() { let mut martha_participant = make_participant("Martha", Some("spouse")); martha_participant.fields = vec![make_field("commitment", 90)]; let mut david_participant = make_participant("David", Some("spouse")); david_participant.fields = vec![make_field("trust", 85)]; let file = File { declarations: vec![ Declaration::Relationship(Relationship { name: "Marriage".to_string(), participants: vec![ martha_participant.clone(), make_participant("David", Some("spouse")), ], fields: vec![], span: Span::new(0, 10), }), Declaration::Relationship(Relationship { name: "Marriage".to_string(), participants: vec![ david_participant.clone(), make_participant("Martha", Some("spouse")), ], fields: vec![], span: Span::new(20, 30), }), ], }; let resolved = resolve_relationships(&file).unwrap(); assert_eq!(resolved.len(), 1); assert_eq!(resolved[0].name, "Marriage"); } #[test] fn test_conflicting_field_values() { let mut p1 = make_participant("Alice", None); p1.fields = vec![make_field("bond", 80)]; let mut p2 = make_participant("Alice", None); p2.fields = vec![make_field("bond", 90)]; // Different value let file = File { declarations: vec![ Declaration::Relationship(Relationship { name: "Test".to_string(), participants: vec![p1, make_participant("Bob", None)], fields: vec![], span: Span::new(0, 10), }), Declaration::Relationship(Relationship { name: "Test".to_string(), participants: vec![p2, make_participant("Bob", None)], fields: vec![], span: Span::new(20, 30), }), ], }; let result = resolve_relationships(&file); assert!(result.is_err()); } }