use super::anchor_index::is_anchor_target_capability;
use super::workspace_graph::{CallGraph, WalkState};
use std::collections::HashSet;
pub(crate) struct TouchpointContext<'a> {
pub graph: &'a CallGraph,
pub target_layer: &'a str,
pub call_depth: usize,
pub origin_adapter: &'a str,
pub adapter_layers: &'a [String],
}
pub(crate) fn compute_touchpoints(handler: &str, ctx: &TouchpointContext<'_>) -> HashSet<String> {
TouchpointWalk { ctx }.run(handler)
}
struct TouchpointWalk<'a> {
ctx: &'a TouchpointContext<'a>,
}
impl TouchpointWalk<'_> {
fn run(&self, start: &str) -> HashSet<String> {
let mut touchpoints = HashSet::new();
let Some(direct) = self.ctx.graph.forward.get(start) else {
return touchpoints;
};
let mut state = WalkState::seeded(start, direct);
while let Some((node, depth)) = state.queue.pop_front() {
if self.is_peer_adapter(&node) {
continue;
}
if self.is_target_boundary(&node) {
touchpoints.insert(node);
continue;
}
if depth < self.ctx.call_depth {
if let Some(callees) = self.ctx.graph.forward.get(&node) {
state.enqueue_unvisited(callees, depth + 1);
}
}
}
touchpoints
}
fn is_target_boundary(&self, node: &str) -> bool {
if let Some(info) = self.ctx.graph.trait_method_anchors.get(node) {
return is_anchor_target_capability(
info,
self.ctx.target_layer,
self.ctx.adapter_layers,
);
}
self.ctx.graph.layer_of(node) == Some(self.ctx.target_layer)
&& self.ctx.graph.forward.contains_key(node)
}
fn is_peer_adapter(&self, node: &str) -> bool {
let Some(layer) = self.ctx.graph.layer_of(node) else {
return false;
};
if layer == self.ctx.origin_adapter {
return false;
}
self.ctx.adapter_layers.iter().any(|a| a == layer)
}
}