use crate::graph::GraphQuery;
use crate::graph::GraphQueryExt;
pub fn is_api_surface(graph: &dyn GraphQuery, file_path: &str, line: u32) -> bool {
let i = graph.interner();
let Some(func) = graph.find_function_at(file_path, line) else {
return false;
};
let is_exported = func.is_exported();
if !is_exported {
return false;
}
let fan_in = graph.call_fan_in(func.qn(i));
fan_in >= 3
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::builder::GraphBuilder;
use crate::graph::store_models::{CodeEdge, CodeNode};
#[test]
fn test_non_exported_not_api_surface() {
let store = GraphBuilder::new();
assert!(!is_api_surface(&store, "test.py", 5));
}
#[test]
fn test_exported_but_low_fan_in_not_api_surface() {
let mut store = GraphBuilder::new();
let func = CodeNode::function("handler", "app.py")
.with_qualified_name("app.handler")
.with_lines(1, 10)
.with_property("is_exported", true);
store.add_node(func);
assert!(!is_api_surface(&store, "app.py", 5));
}
#[test]
fn test_exported_with_high_fan_in_is_api_surface() {
let mut store = GraphBuilder::new();
let func = CodeNode::function("handler", "app.py")
.with_qualified_name("app.handler")
.with_lines(1, 10)
.with_property("is_exported", true);
store.add_node(func);
for idx in 0..3 {
let caller_name = format!("caller{}", idx);
let caller_file = format!("client{}.py", idx);
let caller_qn = format!("{}.{}", caller_file, caller_name);
let caller = CodeNode::function(&caller_name, &caller_file)
.with_qualified_name(&caller_qn)
.with_lines(1, 5);
store.add_node(caller);
store.add_edge_by_name(&caller_qn, "app.handler", CodeEdge::calls());
}
assert!(is_api_surface(&store, "app.py", 5));
}
#[test]
fn test_not_exported_with_high_fan_in_not_api_surface() {
let mut store = GraphBuilder::new();
let func = CodeNode::function("internal_fn", "app.py")
.with_qualified_name("app.internal_fn")
.with_lines(1, 10);
store.add_node(func);
for idx in 0..3 {
let caller_name = format!("caller{}", idx);
let caller_file = format!("client{}.py", idx);
let caller_qn = format!("{}.{}", caller_file, caller_name);
let caller = CodeNode::function(&caller_name, &caller_file)
.with_qualified_name(&caller_qn)
.with_lines(1, 5);
store.add_node(caller);
store.add_edge_by_name(&caller_qn, "app.internal_fn", CodeEdge::calls());
}
assert!(!is_api_surface(&store, "app.py", 5));
}
#[test]
fn test_line_outside_function_not_api_surface() {
let mut store = GraphBuilder::new();
let func = CodeNode::function("handler", "app.py")
.with_qualified_name("app.handler")
.with_lines(1, 10)
.with_property("is_exported", true);
store.add_node(func);
assert!(!is_api_surface(&store, "app.py", 20));
}
}