Skip to main content

swink_agent_plugin_web/search/
mod.rs

1use std::pin::Pin;
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6#[cfg(feature = "brave")]
7mod brave;
8#[cfg(feature = "duckduckgo")]
9mod duckduckgo;
10#[cfg(feature = "tavily")]
11mod tavily;
12
13#[cfg(feature = "brave")]
14pub use brave::BraveProvider;
15#[cfg(feature = "duckduckgo")]
16pub use duckduckgo::DuckDuckGoProvider;
17#[cfg(feature = "tavily")]
18pub use tavily::TavilyProvider;
19
20/// A single search result.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct SearchResult {
23    pub title: String,
24    pub url: String,
25    pub snippet: String,
26}
27
28/// Errors from search providers.
29#[derive(Debug, Error)]
30pub enum SearchError {
31    #[error("Network error: {0}")]
32    NetworkError(String),
33    #[error("Failed to parse response: {0}")]
34    ParseError(String),
35    #[error("Rate limited by search provider")]
36    RateLimited,
37    #[error("API key is required but not configured")]
38    ApiKeyMissing,
39    #[error("Search provider unavailable: {0}")]
40    ProviderUnavailable(String),
41}
42
43/// Abstraction over different search backends.
44pub trait SearchProvider: Send + Sync {
45    fn name(&self) -> &str;
46    fn search(
47        &self,
48        query: &str,
49        max_results: usize,
50    ) -> Pin<
51        Box<dyn std::future::Future<Output = Result<Vec<SearchResult>, SearchError>> + Send + '_>,
52    >;
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn search_result_is_publicly_constructible() {
61        let result = SearchResult {
62            title: "Rust".to_string(),
63            url: "https://www.rust-lang.org".to_string(),
64            snippet: "Systems programming language".to_string(),
65        };
66        assert_eq!(result.title, "Rust");
67    }
68}