relay-knowledge 1.1.16

Graph-database-based knowledge graph project.
Documentation
use crate::domain::{CodeRouteRecord, CodebaseViewCall, CodebaseViewSnapshot};

use super::builder::{SectionRefs, ViewBuilder};

const PROCESS_FLOW_CALL_LIMIT: usize = 8;

pub(super) fn derive_process_flow(builder: &mut ViewBuilder, snapshot: &CodebaseViewSnapshot) {
    for route in snapshot.routes.iter().take(builder.limit) {
        let route_node_key = format!("route:{}", route.route_id);
        let handler_node_key = route_handler_node_id(route);
        let required_nodes =
            usize::from(builder.existing_node_id(route_node_key.clone()).is_none())
                + usize::from(builder.existing_node_id(handler_node_key.clone()).is_none());
        let route_evidence = builder.evidence(
            "route",
            &route.path,
            Some(route.handler_name.clone()),
            Some(route.line_range.clone()),
            Some(route.http_method.clone()),
            format!("{} {}", route.http_method, route.url),
        );
        if !builder.can_insert_nodes(required_nodes) {
            builder.mark_node_budget_truncated();
            let route_id = builder.node(
                route_node_key,
                format!("{} {}", route.http_method.to_uppercase(), route.url),
                "route",
                Some(route.path.clone()),
                0.86,
                Some(route_evidence.clone()),
            );
            if let Some(route_id) = route_id {
                builder.section(
                    format!("section:route:{}", route.route_id),
                    format!("{} {}", route.http_method.to_uppercase(), route.url),
                    format!(
                        "Request flow starts at route {} {}; handler details were omitted by the node limit.",
                        route.http_method.to_uppercase(),
                        route.url
                    ),
                    0.72,
                    SectionRefs {
                        node_ids: vec![route_id],
                        evidence_ids: vec![route_evidence],
                        diagnostics: vec![
                            "process-flow handler details truncated by node limit".to_owned(),
                        ],
                        ..SectionRefs::default()
                    },
                );
            }
            break;
        }
        let route_id = builder.node(
            route_node_key,
            format!("{} {}", route.http_method.to_uppercase(), route.url),
            "route",
            Some(route.path.clone()),
            0.86,
            Some(route_evidence.clone()),
        );
        let Some(route_id) = route_id else {
            break;
        };
        let matching_calls = snapshot
            .calls
            .iter()
            .filter(|call| call_belongs_to_route_flow(call, route))
            .collect::<Vec<_>>();
        let handler_path = route_handler_path(route, snapshot, &matching_calls);
        let handler_id = builder.node(
            handler_node_key,
            route.handler_name.clone(),
            "handler",
            Some(handler_path),
            0.82,
            Some(route_evidence.clone()),
        );
        let Some(handler_id) = handler_id else {
            break;
        };
        let mut edge_ids = Vec::new();
        if let Some(edge_id) = builder.edge(
            &route_id,
            &handler_id,
            "handled_by",
            0.86,
            Some(route_evidence.clone()),
        ) {
            edge_ids.push(edge_id);
        }
        let mut node_ids = vec![route_id, handler_id.clone()];
        let mut evidence_ids = vec![route_evidence];
        let mut diagnostics = Vec::new();
        if matching_calls.len() > PROCESS_FLOW_CALL_LIMIT {
            builder.mark_edge_budget_truncated();
            diagnostics.push(format!(
                "route handler calls truncated to {PROCESS_FLOW_CALL_LIMIT} matching calls"
            ));
        }
        for call in matching_calls.into_iter().take(PROCESS_FLOW_CALL_LIMIT) {
            let call_evidence = builder.evidence(
                "call",
                &call.call.path,
                call.call.caller_name.clone(),
                Some(call.call.line_range.clone()),
                Some(call.call.resolution_state.clone()),
                format!("handler call to {}", call.call.callee_name),
            );
            let callee_id = builder.node(
                call_target_node_id(call),
                call.call.callee_name.clone(),
                "call_target",
                call.callee_path.clone(),
                0.68,
                Some(call_evidence.clone()),
            );
            if let Some(callee_id) = callee_id {
                if let Some(edge_id) = builder.edge(
                    &handler_id,
                    &callee_id,
                    "calls",
                    0.68,
                    Some(call_evidence.clone()),
                ) {
                    edge_ids.push(edge_id);
                    node_ids.push(callee_id);
                }
            }
            evidence_ids.push(call_evidence);
        }
        builder.section(
            format!("section:route:{}", route.route_id),
            format!("{} {}", route.http_method.to_uppercase(), route.url),
            format!(
                "Request flow starts at route {} {} and reaches handler {}.",
                route.http_method.to_uppercase(),
                route.url,
                route.handler_name
            ),
            0.78,
            SectionRefs {
                node_ids,
                edge_ids,
                evidence_ids,
                diagnostics,
            },
        );
    }
}

fn call_target_node_id(call: &CodebaseViewCall) -> String {
    if let Some(symbol_id) = call.call.callee_symbol_snapshot_id.as_deref() {
        return format!("call_target:symbol:{symbol_id}");
    }
    if let Some(path) = call.callee_path.as_deref() {
        return format!("call_target:path:{path}:{}", call.call.callee_name);
    }
    format!("call_target:{}:{}", call.call.path, call.call.callee_name)
}

fn route_handler_node_id(route: &CodeRouteRecord) -> String {
    route
        .handler_symbol_snapshot_id
        .as_ref()
        .map(|symbol_id| format!("handler:symbol:{symbol_id}"))
        .unwrap_or_else(|| format!("handler:{}:{}", route.path, route.route_id))
}

fn route_handler_path(
    route: &CodeRouteRecord,
    snapshot: &CodebaseViewSnapshot,
    matching_calls: &[&CodebaseViewCall],
) -> String {
    if let Some(handler_symbol_id) = route.handler_symbol_snapshot_id.as_deref() {
        if let Some(symbol) = snapshot
            .symbols
            .iter()
            .find(|symbol| symbol.symbol_snapshot_id == handler_symbol_id)
        {
            return symbol.path.clone();
        }
        if let Some(call) = matching_calls
            .iter()
            .find(|call| call.call.caller_symbol_snapshot_id.as_deref() == Some(handler_symbol_id))
        {
            return call.call.path.clone();
        }
    }
    matching_calls
        .iter()
        .find(|call| call.call.path != route.path)
        .map(|call| call.call.path.clone())
        .unwrap_or_else(|| route.path.clone())
}

fn call_matches_route_handler(call: &CodebaseViewCall, route: &CodeRouteRecord) -> bool {
    if let (Some(caller_symbol_id), Some(handler_symbol_id)) = (
        call.call.caller_symbol_snapshot_id.as_deref(),
        route.handler_symbol_snapshot_id.as_deref(),
    ) {
        return caller_symbol_id == handler_symbol_id;
    }
    let Some(caller_name) = call.call.caller_name.as_deref() else {
        return false;
    };
    same_symbol_leaf(caller_name, &route.handler_name)
}

fn call_belongs_to_route_flow(call: &CodebaseViewCall, route: &CodeRouteRecord) -> bool {
    if call.call.path == route.path {
        return call_matches_route_handler(call, route);
    }
    if let (Some(caller_symbol_id), Some(handler_symbol_id)) = (
        call.call.caller_symbol_snapshot_id.as_deref(),
        route.handler_symbol_snapshot_id.as_deref(),
    ) {
        return caller_symbol_id == handler_symbol_id;
    }
    let Some(caller_name) = call.call.caller_name.as_deref() else {
        return false;
    };
    names_are_qualified(caller_name)
        && names_are_qualified(&route.handler_name)
        && symbol_leaf(caller_name) == symbol_leaf(&route.handler_name)
}

fn same_symbol_leaf(left: &str, right: &str) -> bool {
    left == right
        || symbol_leaf(left) == right
        || symbol_leaf(right) == left
        || symbol_leaf(left) == symbol_leaf(right)
}

fn symbol_leaf(name: &str) -> &str {
    name.rsplit([':', '.', '#', '/'])
        .find(|part| !part.is_empty())
        .unwrap_or(name)
}

fn names_are_qualified(name: &str) -> bool {
    name.contains([':', '.', '#', '/'])
}