Keep port endpoint feasibility independent of model-wide advisory scans
This commit is contained in:
+48
-28
@@ -847,6 +847,11 @@ fn legacy_allows(e: &Element, outgoing: bool) -> bool {
|
||||
matches!(e.shared(), Some(SharedBody::Port { contract: PortContract::Legacy, legacy: Some(LegacyPortContract { direction, .. }) }) if if outgoing { *direction != PortDirection::In } else { *direction != PortDirection::Out })
|
||||
}
|
||||
fn validate_carried_contract(p: &Project, r: &Relation, port: &Element, out: &mut Vec<Issue>) {
|
||||
// Every carried-item constraint is a subset check. An empty exchange cannot
|
||||
// violate one, so endpoint feasibility need not scan all interface roles.
|
||||
if r.body.items().is_empty() {
|
||||
return;
|
||||
}
|
||||
match port.shared() {
|
||||
Some(SharedBody::Port {
|
||||
contract: PortContract::FunctionInput { incoming_items },
|
||||
@@ -1060,34 +1065,6 @@ pub(crate) fn validate_port_relation(p: &Project, r: &Relation, out: &mut Vec<Is
|
||||
"This boundary exchange retains unresolved generic port semantics.",
|
||||
);
|
||||
}
|
||||
for (port, peer) in [(a, b), (b, a)] {
|
||||
if !matches!(
|
||||
port_contract(port),
|
||||
Some(PortContract::Component {
|
||||
kind: ComponentPortKind::Standard,
|
||||
..
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for required in p.relations.values().filter(|role| {
|
||||
role.kind() == RelationKind::InterfaceRequirement && role.source() == port.id
|
||||
}) {
|
||||
if !p.relations.values().any(|role| {
|
||||
role.kind() == RelationKind::InterfaceProvision
|
||||
&& role.source() == peer.id
|
||||
&& role.target() == required.target()
|
||||
}) {
|
||||
warning(
|
||||
out,
|
||||
required.id,
|
||||
"unsatisfied_interface_requirement",
|
||||
"target",
|
||||
"The connected partner has not declared this required interface as provided.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RelationBody::PortAllocation { source, target } => {
|
||||
let (Some(component), Some(function)) =
|
||||
@@ -1167,6 +1144,49 @@ pub(crate) fn validate_port_relation(p: &Project, r: &Relation, out: &mut Vec<Is
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
/// Whole-model advisory checks. Missing peer declarations are warnings, so they
|
||||
/// must not make endpoint feasibility scan the relation graph per candidate.
|
||||
pub(crate) fn validate_boundary_interface_requirements(
|
||||
p: &Project,
|
||||
r: &Relation,
|
||||
out: &mut Vec<Issue>,
|
||||
) {
|
||||
let RelationBody::BoundaryExchange { source, target, .. } = &r.body else {
|
||||
return;
|
||||
};
|
||||
let (Some(a), Some(b)) = (p.elements.get(source), p.elements.get(target)) else {
|
||||
return;
|
||||
};
|
||||
for (port, peer) in [(a, b), (b, a)] {
|
||||
if !matches!(
|
||||
port_contract(port),
|
||||
Some(PortContract::Component {
|
||||
kind: ComponentPortKind::Standard,
|
||||
..
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for required in p.relations.values().filter(|role| {
|
||||
role.kind() == RelationKind::InterfaceRequirement && role.source() == port.id
|
||||
}) {
|
||||
if !p.relations.values().any(|role| {
|
||||
role.kind() == RelationKind::InterfaceProvision
|
||||
&& role.source() == peer.id
|
||||
&& role.target() == required.target()
|
||||
}) {
|
||||
warning(
|
||||
out,
|
||||
required.id,
|
||||
"unsatisfied_interface_requirement",
|
||||
"target",
|
||||
"The connected partner has not declared this required interface as provided.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn validate_exchange_allocation(p: &Project, r: &Relation, out: &mut Vec<Issue>) {
|
||||
let (Some(boundary), Some(work)) = (p.relations.get(&r.source()), p.relations.get(&r.target()))
|
||||
else {
|
||||
|
||||
@@ -643,6 +643,7 @@ pub fn validate(p: &Project) -> Vec<Issue> {
|
||||
text(&mut out, r.id, "name", &r.name);
|
||||
text(&mut out, r.id, "description", &r.description);
|
||||
validate_relation(p, r, &mut out);
|
||||
crate::ports::validate_boundary_interface_requirements(p, r, &mut out);
|
||||
validate_relation_context(p, r, &mut out);
|
||||
}
|
||||
for (key, d) in &p.diagrams {
|
||||
@@ -2010,7 +2011,10 @@ fn relation_path_exists(p: &Project, kind: RelationKind, from: Id, to: Id) -> bo
|
||||
fn validate_relation(p: &Project, r: &Relation, out: &mut Vec<Issue>) {
|
||||
let (source, target) = r.body.endpoints();
|
||||
for id in crate::references::relation_references(r) {
|
||||
if !p.contains(id) {
|
||||
// Most relation references name elements. Avoid constructing all model
|
||||
// identities per endpoint while retaining the general lookup semantics
|
||||
// for other record kinds and malformed map keys.
|
||||
if !p.elements.get(&id).is_some_and(|element| element.id == id) && !p.contains(id) {
|
||||
error(
|
||||
out,
|
||||
r.id,
|
||||
|
||||
Reference in New Issue
Block a user