use crate::error::SearchError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub url: String,
pub title: String,
pub snippet: Option<String>,
pub domain: Option<String>,
pub published_date: Option<String>,
pub provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raw: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Default)]
pub struct DebugOptions {
pub enabled: bool,
pub log_requests: bool,
pub log_responses: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SafeSearch {
Off,
Moderate,
Strict,
}
impl fmt::Display for SafeSearch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SafeSearch::Off => write!(f, "off"),
SafeSearch::Moderate => write!(f, "moderate"),
SafeSearch::Strict => write!(f, "strict"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SortBy {
Relevance,
LastUpdatedDate,
SubmittedDate,
}
impl fmt::Display for SortBy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SortBy::Relevance => write!(f, "relevance"),
SortBy::LastUpdatedDate => write!(f, "lastUpdatedDate"),
SortBy::SubmittedDate => write!(f, "submittedDate"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SortOrder {
Ascending,
Descending,
}
impl fmt::Display for SortOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SortOrder::Ascending => write!(f, "ascending"),
SortOrder::Descending => write!(f, "descending"),
}
}
}
#[derive(Debug)]
pub struct SearchOptions {
pub query: String,
pub id_list: Option<String>,
pub max_results: Option<u32>,
pub language: Option<String>,
pub region: Option<String>,
pub safe_search: Option<SafeSearch>,
pub page: Option<u32>,
pub start: Option<u32>,
pub sort_by: Option<SortBy>,
pub sort_order: Option<SortOrder>,
pub timeout: Option<u64>,
pub debug: Option<DebugOptions>,
pub provider: Box<dyn SearchProvider>,
}
impl Default for SearchOptions {
fn default() -> Self {
Self {
query: String::new(),
id_list: None,
max_results: Some(10),
language: None,
region: None,
safe_search: None,
page: Some(1),
start: None,
sort_by: None,
sort_order: None,
timeout: Some(15000), debug: None,
provider: Box::new(DummyProvider), }
}
}
#[async_trait::async_trait]
pub trait SearchProvider: Send + Sync + std::fmt::Debug {
fn name(&self) -> &str;
async fn search(&self, options: &SearchOptions) -> Result<Vec<SearchResult>, SearchError>;
fn config(&self) -> HashMap<String, String> {
HashMap::new()
}
}
#[derive(Debug)]
struct DummyProvider;
#[async_trait::async_trait]
impl SearchProvider for DummyProvider {
fn name(&self) -> &str {
"dummy"
}
async fn search(&self, _options: &SearchOptions) -> Result<Vec<SearchResult>, SearchError> {
Err(SearchError::InvalidInput(
"No provider configured".to_string(),
))
}
}
pub trait ProviderConfig {
fn validate(&self) -> Result<(), SearchError>;
fn base_url(&self) -> &str;
fn api_key(&self) -> Option<&str> {
None
}
}