Files
ARCADIA-Studio/crates/oa-core/src/transitions/communication.rs
T

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))
}