use serde::Serialize;
#[cfg(feature = "api-review")]
use crate::analysis::api_review::{ApiReviewer, ToolEntry};
#[cfg(feature = "api-review")]
use crate::kit::{AsyncKit, AsyncReady, StorageModule};
#[cfg(all(feature = "cli", feature = "api-review"))]
use crate::service::error::kit_not_initialized;
#[cfg(all(feature = "cli", feature = "api-review"))]
use crate::service::error::to_api_error;
#[cfg(feature = "api-review")]
use crate::service::error::CodeNexusError;
#[cfg(all(feature = "cli", feature = "api-review"))]
use crate::service::runtime::kit;
#[cfg(all(feature = "cli", feature = "api-review"))]
use sdforge::forge;
#[cfg(all(feature = "cli", feature = "api-review"))]
use sdforge::prelude::ApiError;
#[cfg(feature = "api-review")]
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct ToolMapOutput {
pub project: String,
pub tool_map: Vec<ToolEntry>,
}
#[cfg(feature = "api-review")]
fn tool_map_core(kit: &AsyncKit<AsyncReady>, project: &str) -> Result<(), CodeNexusError> {
let storage = kit.require::<StorageModule>()?;
let reviewer = ApiReviewer::new(&*storage);
let tool_map: Vec<ToolEntry> = reviewer.tool_map(project)?;
let output = ToolMapOutput {
project: project.to_string(),
tool_map,
};
let json = serde_json::to_string(&output)?;
println!("{json}");
Ok(())
}
#[cfg(all(feature = "cli", feature = "api-review"))]
#[forge(
name = "tool_map",
version = "0.3.3",
description = "List MCP tools and their handler functions.",
cli = true
)]
async fn tool_map(project: String) -> Result<(), ApiError> {
let kit = kit().ok_or_else(kit_not_initialized)?;
tool_map_core(&kit, &project).map_err(|e| to_api_error(e, "tool_map_error"))?;
Ok(())
}
#[cfg(all(test, feature = "cli", feature = "api-review"))]
mod tests {
use super::*;
use crate::kit::{build_kit, AsyncKit, AsyncReady, KitBootstrapConfig, StorageModule};
use tempfile::TempDir;
fn fresh_db_path() -> (TempDir, std::path::PathBuf) {
let dir = TempDir::new().unwrap();
let path = dir.path().join("svc_tool_map_testdb");
(dir, path)
}
fn build_kit_for_db(db: &std::path::Path) -> AsyncKit<AsyncReady> {
let config = KitBootstrapConfig::new(db.to_path_buf());
tokio::runtime::Runtime::new()
.unwrap()
.block_on(build_kit(&config))
.expect("build_kit")
}
#[test]
fn core_succeeds_on_empty_db() {
let (_dir, db) = fresh_db_path();
let kit = build_kit_for_db(&db);
let result = tool_map_core(&kit, "demo");
assert!(result.is_ok(), "core should succeed: {:?}", result.err());
}
#[test]
fn core_with_tool() {
let (_dir, db) = fresh_db_path();
let kit = build_kit_for_db(&db);
let storage = kit.require::<StorageModule>().expect("require_storage");
storage.execute("CREATE (:Tool {id: 't1', project: 'demo', name: 'query', qualifiedName: 'query', filePath: '', toolType: 'mcp', parentQn: ''});").expect("create tool");
storage.execute("CREATE (:Handler {id: 'h1', project: 'demo', name: 'query_handler', qualifiedName: 'query_handler', filePath: '', startLine: 0, endLine: 0, signature: '', returnType: '', isExported: false, docstring: '', content: '', parentQn: ''});").expect("create handler");
storage.execute("CREATE (:CodeRelation {id: 'e1', source: 'h1', target: 't1', type: 'HANDLES', confidence: 1.0, confidenceTier: 'High', reason: '', startLine: 1, project: 'demo'});").expect("create edge");
let result = tool_map_core(&kit, "demo");
assert!(result.is_ok(), "core should succeed: {:?}", result.err());
}
#[test]
fn output_serializes_to_json() {
let out = ToolMapOutput {
project: "demo".into(),
tool_map: vec![ToolEntry {
tool_name: "query".into(),
handler_id: "h1".into(),
handler_name: "query_handler".into(),
}],
};
let json = serde_json::to_string(&out).unwrap();
assert!(json.contains("\"project\":\"demo\""));
assert!(json.contains("\"tool_map\""));
assert!(json.contains("\"query\""));
}
#[serial_test::serial(kit_init)]
#[test]
fn tool_map_wrapper_succeeds_via_init_kit() {
use crate::service::runtime::{init_kit, reset_kit_for_testing};
reset_kit_for_testing();
let (_dir, db) = fresh_db_path();
let kit = build_kit_for_db(&db);
init_kit(kit).expect("init_kit");
let rt = tokio::runtime::Runtime::new().expect("runtime");
let result = rt.block_on(tool_map("demo".to_string()));
assert!(result.is_ok(), "wrapper should succeed: {:?}", result.err());
reset_kit_for_testing();
}
#[serial_test::serial(kit_init)]
#[test]
fn tool_map_wrapper_fails_when_kit_not_initialized() {
use crate::service::runtime::reset_kit_for_testing;
reset_kit_for_testing();
let rt = tokio::runtime::Runtime::new().expect("runtime");
let result = rt.block_on(tool_map("demo".to_string()));
assert!(result.is_err(), "wrapper should fail without kit");
reset_kit_for_testing();
}
}