use anyhow::{Result, anyhow};
use serde_json::Value;
use super::{
WorkspaceContext, execute_complexity_metrics_for_daemon, execute_dependency_impact_for_daemon,
execute_direct_callees_for_daemon, execute_direct_callers_for_daemon,
execute_export_graph_for_daemon, execute_find_cycles_for_daemon,
execute_find_unused_for_daemon, execute_is_node_in_cycle_for_daemon,
execute_relation_query_for_daemon, execute_semantic_diff_for_daemon,
execute_semantic_search_for_daemon, execute_show_dependencies_for_daemon,
execute_subgraph_for_daemon, execute_trace_path_for_daemon, tool_response_json,
};
use crate::daemon_params::{
params_to_complexity_metrics_args, params_to_dependency_impact_args,
params_to_direct_callees_args, params_to_direct_callers_args, params_to_export_graph_args,
params_to_find_cycles_args, params_to_find_unused_args, params_to_is_node_in_cycle_args,
params_to_relation_query_args, params_to_semantic_diff_args, params_to_semantic_search_args,
params_to_show_dependencies_args, params_to_subgraph_args, params_to_trace_path_args,
};
pub fn dispatch_by_name(name: &str, wctx: &WorkspaceContext, args_value: &Value) -> Result<Value> {
match name {
"semantic_search" => {
let args = params_to_semantic_search_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_semantic_search_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"relation_query" => {
let args = params_to_relation_query_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_relation_query_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"direct_callers" => {
let args = params_to_direct_callers_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_direct_callers_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"direct_callees" => {
let args = params_to_direct_callees_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_direct_callees_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"find_unused" => {
let args = params_to_find_unused_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_find_unused_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"find_cycles" => {
let args = params_to_find_cycles_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_find_cycles_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"is_node_in_cycle" => {
let args = params_to_is_node_in_cycle_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_is_node_in_cycle_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"trace_path" => {
let args = params_to_trace_path_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_trace_path_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"subgraph" => {
let args = params_to_subgraph_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_subgraph_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"export_graph" => {
let args = params_to_export_graph_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_export_graph_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"complexity_metrics" => {
let args = params_to_complexity_metrics_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_complexity_metrics_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"semantic_diff" => {
let args = params_to_semantic_diff_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_semantic_diff_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"dependency_impact" => {
let args = params_to_dependency_impact_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_dependency_impact_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"show_dependencies" => {
let args = params_to_show_dependencies_args(args_value.clone())
.map_err(|e| anyhow!("invalid arguments: {e}"))?;
let exec = execute_show_dependencies_for_daemon(wctx, &args)?;
tool_response_json(exec).map_err(|e| anyhow!("response build: {e:?}"))
}
"rebuild_index" => Err(anyhow!(
"dispatch_by_name: rebuild_index must be handled by the caller \
(DaemonMcpHandler::handle_rebuild_index), not routed through dispatch_by_name"
)),
other => Err(anyhow!(
"dispatch_by_name: unknown tool name {other:?} (not in DAEMON_SUPPORTED_TOOL_NAMES)"
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dispatch_by_name_rejects_unknown_tool_name() {
use std::path::PathBuf;
use std::sync::Arc;
use sqry_core::graph::unified::concurrent::CodeGraph;
use sqry_core::query::executor::QueryExecutor;
let graph = Arc::new(CodeGraph::new());
let executor = Arc::new(QueryExecutor::new());
let wctx = WorkspaceContext {
workspace_root: PathBuf::from("/nonexistent/workspace"),
graph,
executor,
};
let err = dispatch_by_name("not_a_real_tool", &wctx, &serde_json::json!({})).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("unknown tool name"),
"error should mention 'unknown tool name', got: {msg}"
);
assert!(
msg.contains("not_a_real_tool"),
"error should include offending tool name, got: {msg}"
);
assert!(
msg.contains("DAEMON_SUPPORTED_TOOL_NAMES"),
"error should reference the constant, got: {msg}"
);
}
#[test]
fn dispatch_by_name_rejects_empty_name() {
use std::path::PathBuf;
use std::sync::Arc;
use sqry_core::graph::unified::concurrent::CodeGraph;
use sqry_core::query::executor::QueryExecutor;
let graph = Arc::new(CodeGraph::new());
let executor = Arc::new(QueryExecutor::new());
let wctx = WorkspaceContext {
workspace_root: PathBuf::from("/nonexistent/workspace"),
graph,
executor,
};
let err = dispatch_by_name("", &wctx, &serde_json::json!({})).unwrap_err();
assert!(
err.to_string().contains("unknown tool name"),
"empty name should error with 'unknown tool name', got: {err}"
);
}
}