use crate::{SearchCriteria, SearchOptions, ServerConfig, SortOrder, ToolSearchMatch, ToolSearchError};
use std::time::Duration;
pub struct SearchBuilder {
servers: Vec<ServerConfig>,
query: Option<String>,
keywords: Option<Vec<String>>,
options: SearchOptions,
}
impl SearchBuilder {
pub fn new(servers: Vec<ServerConfig>) -> Self {
Self {
servers,
query: None,
keywords: None,
options: SearchOptions::default(),
}
}
pub fn query(mut self, query: impl Into<String>) -> Self {
self.query = Some(query.into());
self
}
pub fn keywords(mut self, keywords: Vec<String>) -> Self {
self.keywords = Some(keywords);
self.query = None; self
}
pub fn limit(mut self, max: usize) -> Self {
self.options.max_results = Some(max);
self
}
pub fn timeout(mut self, seconds: u64) -> Self {
self.options.timeout = Some(Duration::from_secs(seconds));
self
}
pub fn sort_by_tool(mut self) -> Self {
self.options.sort_order = SortOrder::ToolThenServer;
self
}
pub fn sort_by_server(mut self) -> Self {
self.options.sort_order = SortOrder::ServerThenTool;
self
}
pub async fn search(self) -> Result<Vec<ToolSearchMatch>, ToolSearchError> {
use crate::search_tools_with_options;
let criteria = if let Some(ref keywords) = self.keywords {
SearchCriteria::with_keywords(keywords.clone())
} else if let Some(ref query) = self.query {
if is_likely_regex(query) {
SearchCriteria::with_regex(query.clone())
} else if query.contains(',') {
let keywords: Vec<String> = query
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
SearchCriteria::with_keywords(keywords)
} else {
SearchCriteria::with_query(query.clone())
}
} else {
SearchCriteria::match_all()
};
search_tools_with_options(&self.servers, &criteria, &self.options).await
}
}
fn is_likely_regex(query: &str) -> bool {
query.contains('^') || query.contains('$') || query.contains('*') ||
query.contains('+') || query.contains('?') || query.contains('|') ||
query.contains('[') || query.contains('(')
}
pub async fn simple_search(
servers: &[ServerConfig],
query: &str,
) -> Result<Vec<ToolSearchMatch>, ToolSearchError> {
SearchBuilder::new(servers.to_vec())
.query(query)
.search()
.await
}
pub fn load_servers(config_path: &str) -> Result<Vec<ServerConfig>, Box<dyn std::error::Error>> {
use std::fs;
use serde_json;
let config_data = fs::read_to_string(config_path)?;
let servers: Vec<ServerConfig> = serde_json::from_str(&config_data)?;
for server in &servers {
server.validate()
.map_err(|e| format!("Invalid server configuration '{}': {}", server.name, e))?;
}
Ok(servers)
}