346 lines
13 KiB
Rust
346 lines
13 KiB
Rust
//! 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<FieldPathKey>,
|
|
pub scope: Option<FieldPathKey>,
|
|
pub page: usize,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct RowDraft {
|
|
pub expression: String,
|
|
pub editor: text_editor::Content,
|
|
pub local: Option<Choice>,
|
|
}
|
|
|
|
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<Element> {
|
|
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<Vec<FieldPathChoice>, 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<Choice> {
|
|
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<Id, String> {
|
|
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::<Result<_, _>>()?;
|
|
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(())
|
|
}
|
|
}
|