//! 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, /// Which participant is "self" (index into participants) self_index: Option, } /// 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 "self" block pub self_fields: Vec, /// Fields from this participant's "other" block (about other participants) pub other_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()); // Determine which participant is "self" based on self/other blocks let self_index = rel .participants .iter() .position(|p| p.self_block.is_some() || p.other_block.is_some()); relationship_groups .entry(key) .or_default() .push(RelationshipDecl { relationship: rel.clone(), self_index, }); } } // 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(), self_fields: p.self_block.clone().unwrap_or_default(), other_fields: p.other_block.clone().unwrap_or_default(), }) .collect(); // Merge additional declarations for decl in decls.iter().skip(1) { // If this declaration specifies a different participant as "self", // merge their self/other blocks appropriately if let Some(self_idx) = decl.self_index { let participant_name = &decl.relationship.participants[self_idx].name; // Find this participant in our merged list if let Some(idx) = participant_fields .iter() .position(|pf| &pf.participant_name == participant_name) { // Merge self blocks let self_block = decl.relationship.participants[self_idx] .self_block .clone() .unwrap_or_default(); merge_fields(&mut participant_fields[idx].self_fields, self_block)?; // Merge other blocks let other_block = decl.relationship.participants[self_idx] .other_block .clone() .unwrap_or_default(); merge_fields(&mut participant_fields[idx].other_fields, other_block)?; } } } // Merge shared fields (fields outside self/other blocks) let mut merged_fields = base.fields.clone(); for decl in decls.iter().skip(1) { 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()), self_block: None, other_block: None, span: Span::new(0, 10), } } fn make_field(name: &str, value: i64) -> Field { Field { name: name.to_string(), value: Value::Int(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_bidirectional_relationship_merge() { let mut martha_participant = make_participant("Martha", Some("spouse")); martha_participant.self_block = Some(vec![make_field("bond", 90)]); martha_participant.other_block = Some(vec![make_field("trust", 85)]); let mut david_participant = make_participant("David", Some("spouse")); david_participant.self_block = Some(vec![make_field("bond", 90)]); david_participant.other_block = Some(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.self_block = Some(vec![make_field("bond", 80)]); let mut p2 = make_participant("Alice", None); p2.self_block = Some(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()); } }