use std::pin::Pin;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "brave")]
mod brave;
#[cfg(feature = "duckduckgo")]
mod duckduckgo;
#[cfg(feature = "tavily")]
mod tavily;
#[cfg(feature = "brave")]
pub use brave::BraveProvider;
#[cfg(feature = "duckduckgo")]
pub use duckduckgo::DuckDuckGoProvider;
#[cfg(feature = "tavily")]
pub use tavily::TavilyProvider;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub title: String,
pub url: String,
pub snippet: String,
}
#[derive(Debug, Error)]
pub enum SearchError {
#[error("Network error: {0}")]
NetworkError(String),
#[error("Failed to parse response: {0}")]
ParseError(String),
#[error("Rate limited by search provider")]
RateLimited,
#[error("API key is required but not configured")]
ApiKeyMissing,
#[error("Search provider unavailable: {0}")]
ProviderUnavailable(String),
}
pub trait SearchProvider: Send + Sync {
fn name(&self) -> &str;
fn search(
&self,
query: &str,
max_results: usize,
) -> Pin<
Box<dyn std::future::Future<Output = Result<Vec<SearchResult>, SearchError>> + Send + '_>,
>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn search_result_is_publicly_constructible() {
let result = SearchResult {
title: "Rust".to_string(),
url: "https://www.rust-lang.org".to_string(),
snippet: "Systems programming language".to_string(),
};
assert_eq!(result.title, "Rust");
}
}