use crate::error::Result;
use crate::tools::{Tool, ToolParameters, ToolResult};
use futures::future::BoxFuture;
use serde_json::{Value, json};
use std::collections::{HashMap, VecDeque};
use std::sync::{Arc, Mutex};
enum MockToolResponse {
Success(String),
Failure(String),
}
pub struct MockTool {
name: String,
description: String,
parameters: Value,
responses: Arc<Mutex<VecDeque<MockToolResponse>>>,
calls: Arc<Mutex<Vec<HashMap<String, Value>>>>,
}
impl MockTool {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: "A mock tool for testing".to_string(),
parameters: json!({
"type": "object",
"properties": {},
"required": []
}),
responses: Arc::new(Mutex::new(VecDeque::new())),
calls: Arc::new(Mutex::new(Vec::<HashMap<String, Value>>::new())),
}
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn with_parameters(mut self, schema: Value) -> Self {
self.parameters = schema;
self
}
pub fn with_response(self, text: impl Into<String>) -> Self {
self.responses
.lock()
.unwrap()
.push_back(MockToolResponse::Success(text.into()));
self
}
pub fn with_responses(self, texts: impl IntoIterator<Item = impl Into<String>>) -> Self {
{
let mut q = self.responses.lock().unwrap();
for t in texts {
q.push_back(MockToolResponse::Success(t.into()));
}
}
self
}
pub fn with_failure(self, msg: impl Into<String>) -> Self {
self.responses
.lock()
.unwrap()
.push_back(MockToolResponse::Failure(msg.into()));
self
}
pub fn call_count(&self) -> usize {
self.calls.lock().unwrap().len()
}
pub fn last_args(&self) -> Option<HashMap<String, Value>> {
self.calls.lock().unwrap().last().cloned()
}
pub fn all_calls(&self) -> Vec<HashMap<String, Value>> {
self.calls.lock().unwrap().clone()
}
pub fn reset_calls(&self) {
self.calls.lock().unwrap().clear();
}
}
impl Tool for MockTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
&self.description
}
fn parameters(&self) -> Value {
self.parameters.clone()
}
fn execute(&self, params: ToolParameters) -> BoxFuture<'_, Result<ToolResult>> {
Box::pin(async move {
self.calls.lock().unwrap().push(params.clone());
let response = self.responses.lock().unwrap().pop_front();
match response {
Some(MockToolResponse::Success(text)) => Ok(ToolResult::success(text)),
Some(MockToolResponse::Failure(msg)) => Ok(ToolResult::error(msg)),
None => Ok(ToolResult::success("mock response".to_string())),
}
})
}
}