use async_trait::async_trait;
use std::fmt;
#[derive(Debug, Clone)]
pub enum ToolError {
NotFound(String),
ExecutionError(String),
InvalidArguments(String),
Timeout(String),
}
impl fmt::Display for ToolError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ToolError::NotFound(name) => write!(f, "Tool not found: {}", name),
ToolError::ExecutionError(msg) => write!(f, "Tool execution error: {}", msg),
ToolError::InvalidArguments(msg) => write!(f, "Invalid arguments: {}", msg),
ToolError::Timeout(msg) => write!(f, "Tool timeout: {}", msg),
}
}
}
impl std::error::Error for ToolError {}
#[async_trait]
pub trait ToolExecutor: Send + Sync {
async fn execute(
&self,
tool_name: &str,
tool_call_id: &str,
arguments: &serde_json::Value,
) -> Result<serde_json::Value, ToolError>;
fn can_handle(&self, tool_name: &str) -> bool;
}
#[derive(Debug, Clone, Default)]
pub struct NoOpToolExecutor;
#[async_trait]
impl ToolExecutor for NoOpToolExecutor {
async fn execute(
&self,
tool_name: &str,
_tool_call_id: &str,
_arguments: &serde_json::Value,
) -> Result<serde_json::Value, ToolError> {
Err(ToolError::NotFound(tool_name.to_string()))
}
fn can_handle(&self, _tool_name: &str) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_noop_executor_handles_nothing() {
let executor = NoOpToolExecutor;
assert!(!executor.can_handle("any_tool"));
}
#[tokio::test]
async fn test_noop_executor_returns_not_found() {
let executor = NoOpToolExecutor;
let result = executor
.execute("test_tool", "call_123", &serde_json::json!({}))
.await;
assert!(matches!(result, Err(ToolError::NotFound(_))));
}
}