use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum WebSearchError {
#[error("network error: {0}")]
Network(String),
#[error("authentication error: {0}")]
Auth(String),
#[error("rate limit exceeded: {0}")]
RateLimit(String),
#[error("parse error: {0}")]
Parse(String),
#[error("api error: {0}")]
Api(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SearchTopic {
#[default]
General,
News,
Finance,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SearchDepth {
#[default]
Basic,
Advanced,
Fast,
UltraFast,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct WebSearchRequest {
pub query: String,
pub max_results: Option<u32>,
pub country: Option<String>,
pub language: Option<String>,
pub time_range: Option<String>,
pub topic: SearchTopic,
pub search_depth: SearchDepth,
pub include_answer: bool,
pub include_raw_content: bool,
pub include_images: bool,
pub include_favicon: bool,
pub include_domains: Vec<String>,
pub exclude_domains: Vec<String>,
}
impl WebSearchRequest {
#[must_use]
pub fn new(query: impl Into<String>) -> Self {
Self {
query: query.into(),
..Self::default()
}
}
#[must_use]
pub fn with_max_results(mut self, max_results: u32) -> Self {
self.max_results = Some(max_results);
self
}
#[must_use]
pub fn with_country(mut self, country: impl Into<String>) -> Self {
self.country = Some(country.into());
self
}
#[must_use]
pub fn with_language(mut self, language: impl Into<String>) -> Self {
self.language = Some(language.into());
self
}
#[must_use]
pub fn with_time_range(mut self, time_range: impl Into<String>) -> Self {
self.time_range = Some(time_range.into());
self
}
#[must_use]
pub fn with_topic(mut self, topic: SearchTopic) -> Self {
self.topic = topic;
self
}
#[must_use]
pub fn with_search_depth(mut self, search_depth: SearchDepth) -> Self {
self.search_depth = search_depth;
self
}
#[must_use]
pub fn with_answer(mut self, include: bool) -> Self {
self.include_answer = include;
self
}
#[must_use]
pub fn with_raw_content(mut self, include: bool) -> Self {
self.include_raw_content = include;
self
}
#[must_use]
pub fn with_images(mut self, include: bool) -> Self {
self.include_images = include;
self
}
#[must_use]
pub fn with_favicon(mut self, include: bool) -> Self {
self.include_favicon = include;
self
}
#[must_use]
pub fn with_include_domains(mut self, domains: Vec<String>) -> Self {
self.include_domains = domains;
self
}
#[must_use]
pub fn with_exclude_domains(mut self, domains: Vec<String>) -> Self {
self.exclude_domains = domains;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WebSearchImage {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSearchResult {
pub title: String,
pub url: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub favicon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw_content: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSearchResponse {
pub provider: String,
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub answer: Option<String>,
pub results: Vec<WebSearchResult>,
pub images: Vec<WebSearchImage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_time: Option<f64>,
}
pub trait WebSearchBackend: Send + Sync {
fn provider_name(&self) -> &'static str;
fn search_web(&self, request: &WebSearchRequest) -> Result<WebSearchResponse, WebSearchError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebFetchRequest {
pub url: String,
#[serde(default)]
pub headers: Vec<(String, String)>,
#[serde(default = "default_max_bytes")]
pub max_bytes: usize,
#[serde(default = "default_timeout_ms")]
pub timeout_ms: u64,
}
fn default_max_bytes() -> usize {
1_048_576
}
fn default_timeout_ms() -> u64 {
30_000
}
impl WebFetchRequest {
#[must_use]
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
headers: Vec::new(),
max_bytes: default_max_bytes(),
timeout_ms: default_timeout_ms(),
}
}
#[must_use]
pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((name.into(), value.into()));
self
}
#[must_use]
pub fn with_max_bytes(mut self, max_bytes: usize) -> Self {
self.max_bytes = max_bytes;
self
}
#[must_use]
pub fn with_timeout_ms(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = timeout_ms;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebFetchResponse {
pub url: String,
pub status: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
pub body: String,
pub truncated: bool,
}
#[derive(Debug, thiserror::Error)]
pub enum WebFetchError {
#[error("network error: {0}")]
Network(String),
#[error("timeout after {0}ms")]
Timeout(u64),
#[error("response too large (>{0} bytes)")]
TooLarge(usize),
#[error("invalid url: {0}")]
InvalidUrl(String),
#[error("http {0}: {1}")]
Http(u16, String),
}
pub trait WebFetchBackend: Send + Sync {
fn provider_name(&self) -> &'static str;
fn fetch(&self, request: &WebFetchRequest) -> Result<WebFetchResponse, WebFetchError>;
}