use async_trait::async_trait;
use futures::future::BoxFuture;
use std::collections::HashMap;
use crate::error::{Error, Result};
use crate::schema::SchemaGenerator;
pub mod execution;
#[async_trait]
pub trait Tool: Send + Sync {
fn tool_name(&self) -> &str;
fn description(&self) -> &str;
fn execute(&self) -> BoxFuture<'_, Result<String>>;
fn schema(&self, generator: &dyn SchemaGenerator) -> serde_json::Value;
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn Tool>>,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
}
}
pub fn register<T: Tool + 'static>(&mut self, tool: T) -> Result<()> {
let name = tool.tool_name().to_string();
if self.tools.contains_key(&name) {
return Err(Error::NonRetryable(format!(
"Tool '{}' is already registered",
name
)));
}
self.tools.insert(name, Box::new(tool));
Ok(())
}
pub fn get(&self, name: &str) -> Option<&dyn Tool> {
self.tools.get(name).map(|b| b.as_ref())
}
pub fn tool_names(&self) -> Vec<&str> {
self.tools.keys().map(|s| s.as_str()).collect()
}
pub fn generate_schemas(&self, generator: &dyn SchemaGenerator) -> Vec<serde_json::Value> {
self.tools
.values()
.map(|tool| tool.schema(generator))
.collect()
}
pub fn contains(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
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()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockTool {
name: String,
description: String,
}
#[async_trait]
impl Tool for MockTool {
fn tool_name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn execute(&self) -> BoxFuture<'_, Result<String>> {
Box::pin(async { Ok("mock result".to_string()) })
}
fn schema(&self, _generator: &dyn SchemaGenerator) -> serde_json::Value {
serde_json::json!({
"name": self.name,
"description": self.description,
})
}
}
#[test]
fn test_registry_new() {
let registry = ToolRegistry::new();
assert_eq!(registry.len(), 0);
assert!(registry.is_empty());
}
#[test]
fn test_registry_register() {
let mut registry = ToolRegistry::new();
let tool = MockTool {
name: "test_tool".to_string(),
description: "A test tool".to_string(),
};
let result = registry.register(tool);
assert!(result.is_ok());
assert_eq!(registry.len(), 1);
assert!(registry.contains("test_tool"));
}
#[test]
fn test_registry_register_duplicate() {
let mut registry = ToolRegistry::new();
let tool1 = MockTool {
name: "test_tool".to_string(),
description: "First".to_string(),
};
registry.register(tool1).unwrap();
let tool2 = MockTool {
name: "test_tool".to_string(),
description: "Second".to_string(),
};
let result = registry.register(tool2);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("already registered"));
}
#[test]
fn test_registry_get() {
let mut registry = ToolRegistry::new();
let tool = MockTool {
name: "test_tool".to_string(),
description: "A test tool".to_string(),
};
registry.register(tool).unwrap();
let retrieved = registry.get("test_tool");
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().tool_name(), "test_tool");
}
#[test]
fn test_registry_get_nonexistent() {
let registry = ToolRegistry::new();
let retrieved = registry.get("nonexistent");
assert!(retrieved.is_none());
}
#[test]
fn test_registry_tool_names() {
let mut registry = ToolRegistry::new();
registry
.register(MockTool {
name: "tool1".to_string(),
description: "First".to_string(),
})
.unwrap();
registry
.register(MockTool {
name: "tool2".to_string(),
description: "Second".to_string(),
})
.unwrap();
let mut names = registry.tool_names();
names.sort();
assert_eq!(names, vec!["tool1", "tool2"]);
}
#[tokio::test]
async fn test_tool_execute() {
let tool = MockTool {
name: "test_tool".to_string(),
description: "A test tool".to_string(),
};
let result = tool.execute().await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "mock result");
}
}