use anyhow::{Context, Result};
use dashmap::DashMap;
use std::collections::HashMap;
use tokio::sync::RwLock;
use tracing::debug;
use crate::domain::tool::{CategoryPath, Tool};
pub struct ToolRegistry {
tools: DashMap<String, Tool>,
category_root: RwLock<CategoryNode>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: DashMap::new(),
category_root: RwLock::new(CategoryNode::new("root")),
}
}
pub async fn register(&self, tool: Tool) -> Result<()> {
let tool_id = tool.id.clone();
let category = tool.category.clone();
if self.tools.contains_key(&tool_id) {
return Err(anyhow::anyhow!("Tool already registered: {}", tool_id));
}
self.tools.insert(tool_id.clone(), tool);
let mut root = self.category_root.write().await;
root.add_tool(&category, &tool_id);
debug!(
"Registered tool: {} in category: {}",
tool_id,
category.to_path_string()
);
Ok(())
}
pub fn get(&self, id: &str) -> Option<Tool> {
self.tools.get(id).map(|t| t.clone())
}
pub fn contains(&self, id: &str) -> bool {
self.tools.contains_key(id)
}
pub async fn find_by_category(&self, path: &str) -> Vec<Tool> {
let path = CategoryPath::from_string(path);
let root = self.category_root.read().await;
let mut node = &*root;
for segment in path.segments() {
match node.children.get(segment) {
Some(child) => node = child,
None => return Vec::new(),
}
}
let mut tool_ids = Vec::new();
Self::collect_tool_ids(node, &mut tool_ids);
tool_ids
.iter()
.filter_map(|id| self.tools.get(id).map(|t| t.clone()))
.collect()
}
fn collect_tool_ids(node: &CategoryNode, result: &mut Vec<String>) {
result.extend(node.tools.iter().cloned());
for child in node.children.values() {
Self::collect_tool_ids(child, result);
}
}
pub async fn find_direct_by_category(&self, path: &str) -> Vec<Tool> {
let path = CategoryPath::from_string(path);
let root = self.category_root.read().await;
let mut node = &*root;
for segment in path.segments() {
match node.children.get(segment) {
Some(child) => node = child,
None => return Vec::new(),
}
}
node.tools
.iter()
.filter_map(|id| self.tools.get(id).map(|t| t.clone()))
.collect()
}
pub async fn list_subcategories(&self, path: &str) -> Vec<String> {
let path = CategoryPath::from_string(path);
let root = self.category_root.read().await;
let mut node = &*root;
for segment in path.segments() {
match node.children.get(segment) {
Some(child) => node = child,
None => return Vec::new(),
}
}
node.children.keys().cloned().collect()
}
pub fn get_tool_category(&self, tool_id: &str) -> Option<CategoryPath> {
self.tools.get(tool_id).map(|t| t.category.clone())
}
pub async fn get_category_tree(&self) -> CategoryNode {
let root = self.category_root.read().await;
root.clone()
}
pub fn list_all(&self) -> Vec<Tool> {
self.tools.iter().map(|t| t.clone()).collect()
}
pub async fn list_all_categories(&self) -> Vec<String> {
let root = self.category_root.read().await;
let mut paths = Vec::new();
Self::collect_category_paths(&root, String::new(), &mut paths);
paths
}
fn collect_category_paths(node: &CategoryNode, prefix: String, result: &mut Vec<String>) {
for (name, child) in &node.children {
let path = if prefix.is_empty() {
name.clone()
} else {
format!("{}/{}", prefix, name)
};
result.push(path.clone());
Self::collect_category_paths(child, path, result);
}
}
pub async fn unregister(&self, tool_id: &str) -> Result<()> {
let tool = self
.tools
.remove(tool_id)
.map(|(_, t)| t)
.context("Tool not found")?;
let mut root = self.category_root.write().await;
root.remove_tool(&tool.category, tool_id);
debug!("Unregistered tool: {}", tool_id);
Ok(())
}
pub fn len(&self) -> usize {
self.tools.len()
}
pub fn is_empty(&self) -> bool {
self.tools.is_empty()
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CategoryNode {
pub name: String,
tools: Vec<String>,
children: HashMap<String, CategoryNode>,
}
impl CategoryNode {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
tools: Vec::new(),
children: HashMap::new(),
}
}
pub fn add_tool(&mut self, path: &CategoryPath, tool_id: &str) {
let mut node = self;
for segment in path.segments() {
node = node
.children
.entry(segment.to_string())
.or_insert_with(|| CategoryNode::new(segment));
}
node.tools.push(tool_id.to_string());
}
pub fn remove_tool(&mut self, path: &CategoryPath, tool_id: &str) {
let mut node = self;
for segment in path.segments() {
match node.children.get_mut(segment) {
Some(child) => node = child,
None => return,
}
}
node.tools.retain(|id| id != tool_id);
}
pub fn children(&self) -> &HashMap<String, CategoryNode> {
&self.children
}
pub fn tools(&self) -> &[String] {
&self.tools
}
}