//! Atomic message-value and lifeline-binding rows with exact, immutable keys. use super::*; use crate::data_values::{FieldPathChoice, FieldPathKey}; #[derive(Default)] pub struct AddressDraft { pub selected: Option, pub scope: Option, pub page: usize, } #[derive(Default)] pub struct RowDraft { pub expression: String, pub editor: text_editor::Content, pub local: Option, } pub(super) fn supported(facet: &FacetSelector) -> bool { matches!( facet, FacetSelector::Annotation(_) | FacetSelector::Binding(_) ) } pub(super) fn target_selector(facet: &FacetSelector, layer: Layer) -> FacetSelector { match facet { FacetSelector::Annotation(keys) => FacetSelector::Annotation(KeyPair { oa: (layer == Layer::Oa).then(|| keys.oa.clone()).flatten(), sa: (layer == Layer::Sa).then(|| keys.sa.clone()).flatten(), }), FacetSelector::Binding(keys) => FacetSelector::Binding(KeyPair { oa: if layer == Layer::Oa { keys.oa } else { None }, sa: if layer == Layer::Sa { keys.sa } else { None }, }), _ => facet.clone(), } } impl ReviewRow { pub(super) fn annotation_address(&self, layer: Layer) -> &AddressDraft { if layer == Layer::Oa { &self.annotation_oa } else { &self.annotation_sa } } pub(super) fn annotation_address_mut(&mut self, layer: Layer) -> &mut AddressDraft { if layer == Layer::Oa { &mut self.annotation_oa } else { &mut self.annotation_sa } } fn row_owner(&self, project: &Project, layer: Layer) -> Option { let trace = self.effective_trace(project)?; let id = if layer == Layer::Oa { trace.oa } else { trace.sa }; if layer == self.direction.target_layer() && let Ok(value) = self.combined_value(project, true) && let Ok(element) = serde_json::from_value(value) { return Some(element); } project.elements.get(&id).cloned() } pub(super) fn annotation_choices( &self, project: &Project, layer: Layer, ) -> Result, String> { let element = self.row_owner(project, layer).ok_or("Choose a trace.")?; let Some(SharedBody::Occurrence { body: OccurrenceBody::Exchange { exchange: Some(exchange), message_kind, .. }, .. }) = element.shared() else { return Err( "Choose an exchange on this message before selecting a carried field.".into(), ); }; let exchange = project .relations .get(exchange) .ok_or("The message exchange is unavailable.")?; crate::data_values::at_scope( project, exchange, message_kind, self.annotation_address(layer).scope.as_ref(), ) } pub(super) fn binding_choices( &self, project: &Project, layer: Layer, referenced: bool, ) -> Vec { let Some(element) = self.row_owner(project, layer) else { return vec![]; }; let scenario = if referenced { match element.shared() { Some(SharedBody::Occurrence { body: OccurrenceBody::ScenarioUse { scenario, .. }, .. }) => *scenario, _ => None, } } else { element .parent .and_then(|parent| validation::ancestor_of_kind(project, parent, Kind::Scenario)) }; let mut choices: Vec<_> = project .elements .values() .filter(|e| { e.kind() == Kind::Lifeline && e.layer() == layer && scenario.is_some() && e.parent == scenario }) .map(|e| view::choice(project, e.id)) .collect(); choices.sort_by_cached_key(|c| c.label.to_lowercase()); choices } pub(super) fn stage_row_references(&mut self, facet: &FacetSelector) -> Result<(), String> { let pairs: Vec<_> = match facet { FacetSelector::Annotation(KeyPair { oa: Some(oa), sa: Some(sa), }) => { if oa.path.len() != sa.path.len() { return Err("Paired annotation paths must have the same depth; review distinct addresses separately.".into()); } oa.path .iter() .chain(std::iter::once(&oa.field)) .copied() .zip(sa.path.iter().chain(std::iter::once(&sa.field)).copied()) .collect() } FacetSelector::Binding(KeyPair { oa: Some(oa), sa: Some(sa), }) => vec![(*oa, *sa)], _ => vec![], }; // A repeated recursive field may occur several times in one address. // Never let the last occurrence silently replace a conflicting pair. let mut supplied = BTreeMap::new(); for (oa, sa) in pairs { if supplied.insert(oa, sa).is_some_and(|prior| prior != sa) { return Err( "This address pairs the same Operational field with different System fields." .into(), ); } } self.references.extend(supplied); Ok(()) } pub(super) fn initialize_row(&mut self, project: &Project, facet: &FacetSelector) { if self.row_drafts.contains_key(facet) { return; } let value = self .effective_trace(project) .and_then(|t| capture_facet(project, t.oa, t.sa, facet).ok()) .and_then(|f| scalar::value(&f, self.direction.target_layer())); let present = value.as_ref().is_some_and(|v| !v.is_null()); let mut draft = RowDraft::default(); if let Some(value) = value { draft.expression = value["expression"].as_str().unwrap_or_default().into(); draft.editor = text_editor::Content::with_text(&draft.expression); draft.local = value["local"] .as_str() .and_then(|v| v.parse().ok()) .map(|id| view::choice(project, id)); } self.presence.entry(facet.clone()).or_insert(present); self.row_drafts.insert(facet.clone(), draft); } pub(super) fn edit_row( &mut self, facet: &FacetSelector, action: impl FnOnce(&mut RowDraft), ) -> Result<(), String> { if !supported(facet) || self.selected.as_ref() != Some(facet) || self.decisions.get(facet) != Some(&SemanticDecision::Candidate) { return Err("Select this row and choose Set final value before editing it.".into()); } action( self.row_drafts .get_mut(facet) .ok_or("The row draft is unavailable.")?, ); Ok(()) } pub(super) fn project_row( &self, project: &Project, trace: &Trace, value: &mut Value, facet: &FacetSelector, decision: SemanticDecision, ) -> Result<(), String> { if decision == SemanticDecision::KeepTarget { return Ok(()); } let source_layer = self.direction.source_layer(); let target_layer = self.direction.target_layer(); if decision == SemanticDecision::TakeSource && keyed::key(facet, source_layer).is_none() { return Ok(()); } let target_key = keyed::key(facet, target_layer) .ok_or("Choose the destination row address before setting or taking its value.")?; let row = if decision == SemanticDecision::Candidate { if !self.presence.get(facet).copied().unwrap_or(false) { Value::Null } else { let draft = self .row_drafts .get(facet) .ok_or("Set the final row value.")?; match facet { FacetSelector::Annotation(_) => { let key: AnnotationKey = serde_json::from_value(target_key.clone()) .map_err(|e| e.to_string())?; serde_json::to_value(MessageValue { path: key.path, field: key.field, expression: draft.expression.clone(), }) .expect("Message value") } FacetSelector::Binding(_) => { serde_json::json!({"referenced": target_key, "local": draft.local.as_ref().ok_or("Choose the destination scenario's local lifeline.")?.id}) } _ => return Err("Unsupported row.".into()), } } } else { let fact = capture_facet(project, trace.oa, trace.sa, facet)?; let row = scalar::value(&fact, source_layer) .ok_or("Source row collection is not applicable; review its constructor first.")?; if row.is_null() { row } else { let map = |id: Id| -> Result { if source_layer == Layer::Oa { self.references.get(&id).copied().ok_or_else(|| { format!("Choose a reference counterpart for {}.", project.named(id)) }) } else { let matches: Vec<_> = self .references .iter() .filter(|(_, sa)| **sa == id) .map(|(oa, _)| *oa) .collect(); if matches.len() == 1 { Ok(matches[0]) } else { Err(format!( "Choose one unambiguous reference counterpart for {}.", project.named(id) )) } } }; match facet { FacetSelector::Annotation(_) => { let mut row: MessageValue = serde_json::from_value(row).map_err(|e| e.to_string())?; row.path = row.path.into_iter().map(map).collect::>()?; row.field = map(row.field)?; if serde_json::to_value(AnnotationKey::from(&row)).expect("key") != target_key { return Err("Mapped annotation address disagrees with the selected destination address.".into()); } serde_json::to_value(row).expect("row") } FacetSelector::Binding(_) => { let row: LifelineBinding = serde_json::from_value(row).map_err(|e| e.to_string())?; let mapped = LifelineBinding { referenced: map(row.referenced)?, local: map(row.local)?, }; if serde_json::to_value(mapped.referenced).expect("Id") != target_key { return Err("Mapped binding key disagrees with the selected destination lifeline.".into()); } serde_json::to_value(mapped).expect("row") } _ => return Err("Unsupported row.".into()), } } }; let family = keyed::family(facet).ok_or("Unsupported row.")?; let path = keyed::path(value, family) .ok_or("The destination constructor has no collection for this row.")?; let rows = value .pointer_mut(&path) .and_then(Value::as_array_mut) .ok_or("Destination rows are unavailable.")?; let position = rows.iter().position(|row| match facet { FacetSelector::Annotation(_) => { serde_json::json!({"path": row["path"], "field": row["field"]}) == target_key } FacetSelector::Binding(_) => row["referenced"] == target_key, _ => false, }); match (position, row.is_null()) { (Some(index), false) => rows[index] = row, (Some(index), true) => { rows.remove(index); } (None, false) => rows.push(row), (None, true) => {} } Ok(()) } }