211 lines
8.4 KiB
Rust
211 lines
8.4 KiB
Rust
use super::*;
|
|
|
|
fn port_id(choice: &CommunicationPortChoice) -> Id {
|
|
match choice {
|
|
CommunicationPortChoice::Reuse { port } => *port,
|
|
CommunicationPortChoice::Create { id, .. } => *id,
|
|
}
|
|
}
|
|
|
|
pub(super) fn prepare_ports(
|
|
original: &Project,
|
|
working: &mut Project,
|
|
targets: &BTreeMap<Id, Id>,
|
|
plan: &mut TransitionPlan,
|
|
) -> BTreeMap<Id, (Id, Id)> {
|
|
let mut pairs = BTreeMap::new();
|
|
for decision in plan.request.communication_endpoints.clone() {
|
|
let result = (|| -> Result<(Id, Id), String> {
|
|
if plan.request.direction != TransitionDirection::OaToSa {
|
|
return Err(
|
|
"SA endpoint choices apply only to OA→SA communication transition.".into(),
|
|
);
|
|
}
|
|
let means = original
|
|
.relations
|
|
.get(&decision.source)
|
|
.filter(|r| r.kind() == RelationKind::CommunicationMean)
|
|
.ok_or("Endpoint decision must select an OA communication means.")?;
|
|
if !plan.request.sources.contains(&means.id) && !targets.contains_key(&means.id) {
|
|
return Err("Endpoint decision is outside this transition selection.".into());
|
|
}
|
|
if pairs.contains_key(&decision.source) {
|
|
return Err("Choose one component-port pair per communication means.".into());
|
|
}
|
|
let mut pending = vec![];
|
|
for (participant, choice) in [
|
|
(means.source(), &decision.source_port),
|
|
(means.target(), &decision.target_port),
|
|
] {
|
|
let owner = targets.get(&participant).copied().ok_or("Review each OA participant's SA counterpart before selecting its component port.")?;
|
|
if working.elements.get(&owner).is_some_and(|e| {
|
|
e.layer() != Layer::Sa || !matches!(e.kind(), Kind::System | Kind::SystemActor)
|
|
}) {
|
|
return Err(
|
|
"Component-port owner must be the reviewed SA participant counterpart."
|
|
.into(),
|
|
);
|
|
}
|
|
match choice {
|
|
CommunicationPortChoice::Reuse { port } => {
|
|
let e = working
|
|
.elements
|
|
.get(port)
|
|
.ok_or("Selected component port does not exist.")?;
|
|
if e.parent != Some(owner)
|
|
|| e.layer() != Layer::Sa
|
|
|| !matches!(
|
|
crate::ports::port_contract(e),
|
|
Some(PortContract::Component { .. })
|
|
)
|
|
{
|
|
return Err("Selected component port must belong to the reviewed SA participant and have an explicit component contract.".into());
|
|
}
|
|
}
|
|
CommunicationPortChoice::Create {
|
|
id,
|
|
alias,
|
|
name,
|
|
description,
|
|
kind,
|
|
orientation,
|
|
} => {
|
|
if id.is_nil()
|
|
|| working.contains(*id)
|
|
|| pending.iter().any(|e: &Element| e.id == *id)
|
|
{
|
|
return Err("New component-port identity must be unused; choose Reuse for an existing port.".into());
|
|
}
|
|
pending.push(Element {
|
|
id: *id,
|
|
alias: alias.clone(),
|
|
name: name.clone(),
|
|
description: description.clone(),
|
|
parent: Some(owner),
|
|
body: ElementBody::shared_in(
|
|
Layer::Sa,
|
|
SharedBody::Port {
|
|
contract: PortContract::Component {
|
|
kind: *kind,
|
|
orientation: *orientation,
|
|
},
|
|
legacy: None,
|
|
},
|
|
),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
let pair = (
|
|
port_id(&decision.source_port),
|
|
port_id(&decision.target_port),
|
|
);
|
|
if pair.0 == pair.1 || targets.get(&means.source()) == targets.get(&means.target()) {
|
|
return Err("Communication counterparts must have different participant owners; review the participant boundary mapping.".into());
|
|
}
|
|
for e in pending {
|
|
working.elements.insert(e.id, e.clone());
|
|
plan.commands.push(Command::CreateElement(e));
|
|
}
|
|
Ok(pair)
|
|
})();
|
|
match result {
|
|
Ok(pair) => {
|
|
pairs.insert(decision.source, pair);
|
|
}
|
|
Err(message) => conflict(
|
|
plan,
|
|
Some(decision.source),
|
|
targets.get(&decision.source).copied(),
|
|
None,
|
|
message,
|
|
),
|
|
}
|
|
}
|
|
pairs
|
|
}
|
|
|
|
/// Adapt only the transferred endpoint value. Reviewed baseline facts continue
|
|
/// to retain the actual participant/port identities on their respective sides.
|
|
pub(crate) fn review_endpoint_fact(
|
|
project: &Project,
|
|
request: &TransitionRequest,
|
|
map: &BTreeMap<Id, Id>,
|
|
source: Id,
|
|
target: Id,
|
|
fact: &FacetReview,
|
|
) -> Result<Option<FacetReview>, String> {
|
|
let (Some(a), Some(b)) = (
|
|
project.relations.get(&source),
|
|
project.relations.get(&target),
|
|
) else {
|
|
return Ok(None);
|
|
};
|
|
let forward =
|
|
a.kind() == RelationKind::CommunicationMean && b.kind() == RelationKind::BoundaryExchange;
|
|
let reverse =
|
|
a.kind() == RelationKind::BoundaryExchange && b.kind() == RelationKind::CommunicationMean;
|
|
if !forward && !reverse {
|
|
return Ok(None);
|
|
}
|
|
let (endpoint, is_source) = match fact {
|
|
FacetReview::Relation(RelationReview::Source(_)) => (a.source(), true),
|
|
FacetReview::Relation(RelationReview::Target(_)) => (a.target(), false),
|
|
_ => return Ok(None),
|
|
};
|
|
let mapped = if forward {
|
|
let decision = request.communication_endpoints.iter().find(|d| d.source == source)
|
|
.ok_or("Taking an OA endpoint requires this exchange's explicit SA component-port decision.")?;
|
|
let port = port_id(if is_source {
|
|
&decision.source_port
|
|
} else {
|
|
&decision.target_port
|
|
});
|
|
let expected_owner = map.get(&endpoint).ok_or(
|
|
"Review the participant counterpart before taking its communication endpoint.",
|
|
)?;
|
|
let e = project
|
|
.elements
|
|
.get(&port)
|
|
.ok_or("Selected communication endpoint port does not exist.")?;
|
|
if e.parent != Some(*expected_owner)
|
|
|| e.layer() != Layer::Sa
|
|
|| !matches!(
|
|
crate::ports::port_contract(e),
|
|
Some(PortContract::Component { .. })
|
|
)
|
|
{
|
|
return Err(
|
|
"Selected component port disagrees with the reviewed participant counterpart."
|
|
.into(),
|
|
);
|
|
}
|
|
port
|
|
} else {
|
|
let owner = project
|
|
.elements
|
|
.get(&endpoint)
|
|
.and_then(|e| e.parent)
|
|
.ok_or("SA boundary port needs a participant owner.")?;
|
|
let participant = *map.get(&owner).ok_or(
|
|
"Review the SA port owner's OA participant counterpart before taking this endpoint.",
|
|
)?;
|
|
if !project.elements.get(&participant).is_some_and(|e| {
|
|
e.layer() == Layer::Oa && matches!(e.kind(), Kind::Entity | Kind::Actor)
|
|
}) {
|
|
return Err("Boundary endpoint owner must map to an OA entity or actor.".into());
|
|
}
|
|
participant
|
|
};
|
|
let mut result = fact.clone();
|
|
if let FacetReview::Relation(RelationReview::Source(values) | RelationReview::Target(values)) =
|
|
&mut result
|
|
{
|
|
match request.direction.source_layer() {
|
|
Layer::Oa => values.oa = Observed::Value(mapped),
|
|
Layer::Sa => values.sa = Observed::Value(mapped),
|
|
}
|
|
}
|
|
Ok(Some(result))
|
|
}
|