pub mod elasticsearch;
pub mod algolia;
pub mod traits;
pub mod types;
pub use traits::*;
pub use types::*;
pub use elasticsearch::*;
pub use algolia::*;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const DEFAULT_INDEX_NAME: &str = "default";
pub const MAX_DOCUMENT_SIZE: usize = 100 * 1024 * 1024;
pub const DEFAULT_SEARCH_LIMIT: usize = 100;
pub const MAX_SEARCH_LIMIT: usize = 10000;
#[derive(thiserror::Error, Debug)]
pub enum SearchError {
#[error("Elasticsearch connection error: {0}")]
ElasticsearchConnection(String),
#[error("Elasticsearch operation error: {0}")]
ElasticsearchOperation(String),
#[error("Algolia API error: {0}")]
AlgoliaError(String),
#[error("Index not found: {0}")]
IndexNotFound(String),
#[error("Document not found: {0}")]
DocumentNotFound(String),
#[error("Invalid query: {0}")]
InvalidQuery(String),
#[error("Document serialization error: {0}")]
Serialization(String),
#[error("Document deserialization error: {0}")]
Deserialization(String),
#[error("Document too large: {size} bytes (max: {max} bytes)")]
DocumentTooLarge { size: usize, max: usize },
#[error("Invalid index name: {0}")]
InvalidIndexName(String),
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Search timeout error")]
TimeoutError,
#[error("Search rate limit exceeded")]
RateLimitExceeded,
#[error("Search error: {0}")]
Other(String),
}
pub type SearchResult<T> = Result<T, SearchError>;
#[derive(Debug, Clone)]
pub struct SearchConfig {
pub default_limit: usize,
pub max_limit: usize,
pub enable_analytics: bool,
pub timeout_ms: u64,
pub enable_fuzzy_search: bool,
pub fuzziness: f32,
pub minimum_should_match: String,
pub enable_highlighting: bool,
pub highlight_pre_tag: String,
pub highlight_post_tag: String,
pub enable_snippets: bool,
pub snippet_length: usize,
pub enable_suggestions: bool,
pub suggestion_limit: usize,
pub enable_geo_search: bool,
pub default_crs: String,
}
impl Default for SearchConfig {
fn default() -> Self {
Self {
default_limit: DEFAULT_SEARCH_LIMIT,
max_limit: MAX_SEARCH_LIMIT,
enable_analytics: true,
timeout_ms: 5000,
enable_fuzzy_search: true,
fuzziness: 0.7,
minimum_should_match: "75%".to_string(),
enable_highlighting: true,
highlight_pre_tag: "<mark>".to_string(),
highlight_post_tag: "</mark>".to_string(),
enable_snippets: true,
snippet_length: 200,
enable_suggestions: true,
suggestion_limit: 10,
enable_geo_search: false,
default_crs: "EPSG4326".to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SearchBackend {
Elasticsearch,
Algolia,
}
impl SearchBackend {
pub fn name(&self) -> &'static str {
match self {
Self::Elasticsearch => "elasticsearch",
Self::Algolia => "algolia",
}
}
}
#[derive(Debug, Clone)]
pub struct SearchStats {
pub total_queries: u64,
pub total_documents: u64,
pub avg_query_time_ms: f64,
pub queries_per_second: f64,
pub index_size_bytes: Option<u64>,
pub total_indices: u64,
pub last_query_at: Option<chrono::DateTime<chrono::Utc>>,
pub backend_stats: std::collections::HashMap<String, serde_json::Value>,
}
impl Default for SearchStats {
fn default() -> Self {
Self {
total_queries: 0,
total_documents: 0,
avg_query_time_ms: 0.0,
queries_per_second: 0.0,
index_size_bytes: None,
total_indices: 0,
last_query_at: None,
backend_stats: std::collections::HashMap::new(),
}
}
}
impl SearchStats {
pub fn update_with_query(&mut self, query_time_ms: u64) {
self.total_queries += 1;
let total_time = self.avg_query_time_ms * (self.total_queries - 1) as f64 + query_time_ms as f64;
self.avg_query_time_ms = total_time / self.total_queries as f64;
use chrono::Utc;
self.last_query_at = Some(Utc::now());
}
pub fn update_queries_per_second(&mut self, time_window_seconds: u64) {
if time_window_seconds > 0 {
self.queries_per_second = self.total_queries as f64 / time_window_seconds as f64;
}
}
}