1pub mod elasticsearch;
47pub mod algolia;
48pub mod traits;
49pub mod types;
50
51pub use traits::*;
52pub use types::*;
53pub use elasticsearch::*;
54pub use algolia::*;
55
56pub const VERSION: &str = env!("CARGO_PKG_VERSION");
58
59pub const DEFAULT_INDEX_NAME: &str = "default";
61
62pub const MAX_DOCUMENT_SIZE: usize = 100 * 1024 * 1024;
64
65pub const DEFAULT_SEARCH_LIMIT: usize = 100;
67
68pub const MAX_SEARCH_LIMIT: usize = 10000;
70
71#[derive(thiserror::Error, Debug)]
73pub enum SearchError {
74 #[error("Elasticsearch connection error: {0}")]
75 ElasticsearchConnection(String),
76
77 #[error("Elasticsearch operation error: {0}")]
78 ElasticsearchOperation(String),
79
80 #[error("Algolia API error: {0}")]
81 AlgoliaError(String),
82
83 #[error("Index not found: {0}")]
84 IndexNotFound(String),
85
86 #[error("Document not found: {0}")]
87 DocumentNotFound(String),
88
89 #[error("Invalid query: {0}")]
90 InvalidQuery(String),
91
92 #[error("Document serialization error: {0}")]
93 Serialization(String),
94
95 #[error("Document deserialization error: {0}")]
96 Deserialization(String),
97
98 #[error("Document too large: {size} bytes (max: {max} bytes)")]
99 DocumentTooLarge { size: usize, max: usize },
100
101 #[error("Invalid index name: {0}")]
102 InvalidIndexName(String),
103
104 #[error("Configuration error: {0}")]
105 ConfigError(String),
106
107 #[error("Network error: {0}")]
108 NetworkError(String),
109
110 #[error("Search timeout error")]
111 TimeoutError,
112
113 #[error("Search rate limit exceeded")]
114 RateLimitExceeded,
115
116 #[error("Search error: {0}")]
117 Other(String),
118}
119
120pub type SearchResult<T> = Result<T, SearchError>;
122
123#[derive(Debug, Clone)]
125pub struct SearchConfig {
126 pub default_limit: usize,
128
129 pub max_limit: usize,
131
132 pub enable_analytics: bool,
134
135 pub timeout_ms: u64,
137
138 pub enable_fuzzy_search: bool,
140
141 pub fuzziness: f32,
143
144 pub minimum_should_match: String,
146
147 pub enable_highlighting: bool,
149
150 pub highlight_pre_tag: String,
152
153 pub highlight_post_tag: String,
155
156 pub enable_snippets: bool,
158
159 pub snippet_length: usize,
161
162 pub enable_suggestions: bool,
164
165 pub suggestion_limit: usize,
167
168 pub enable_geo_search: bool,
170
171 pub default_crs: String,
173}
174
175impl Default for SearchConfig {
176 fn default() -> Self {
177 Self {
178 default_limit: DEFAULT_SEARCH_LIMIT,
179 max_limit: MAX_SEARCH_LIMIT,
180 enable_analytics: true,
181 timeout_ms: 5000,
182 enable_fuzzy_search: true,
183 fuzziness: 0.7,
184 minimum_should_match: "75%".to_string(),
185 enable_highlighting: true,
186 highlight_pre_tag: "<mark>".to_string(),
187 highlight_post_tag: "</mark>".to_string(),
188 enable_snippets: true,
189 snippet_length: 200,
190 enable_suggestions: true,
191 suggestion_limit: 10,
192 enable_geo_search: false,
193 default_crs: "EPSG4326".to_string(),
194 }
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub enum SearchBackend {
201 Elasticsearch,
202 Algolia,
203}
204
205impl SearchBackend {
206 pub fn name(&self) -> &'static str {
208 match self {
209 Self::Elasticsearch => "elasticsearch",
210 Self::Algolia => "algolia",
211 }
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct SearchStats {
218 pub total_queries: u64,
220
221 pub total_documents: u64,
223
224 pub avg_query_time_ms: f64,
226
227 pub queries_per_second: f64,
229
230 pub index_size_bytes: Option<u64>,
232
233 pub total_indices: u64,
235
236 pub last_query_at: Option<chrono::DateTime<chrono::Utc>>,
238
239 pub backend_stats: std::collections::HashMap<String, serde_json::Value>,
241}
242
243impl Default for SearchStats {
244 fn default() -> Self {
245 Self {
246 total_queries: 0,
247 total_documents: 0,
248 avg_query_time_ms: 0.0,
249 queries_per_second: 0.0,
250 index_size_bytes: None,
251 total_indices: 0,
252 last_query_at: None,
253 backend_stats: std::collections::HashMap::new(),
254 }
255 }
256}
257
258impl SearchStats {
259 pub fn update_with_query(&mut self, query_time_ms: u64) {
261 self.total_queries += 1;
262
263 let total_time = self.avg_query_time_ms * (self.total_queries - 1) as f64 + query_time_ms as f64;
265 self.avg_query_time_ms = total_time / self.total_queries as f64;
266
267 use chrono::Utc;
269 self.last_query_at = Some(Utc::now());
270 }
271
272 pub fn update_queries_per_second(&mut self, time_window_seconds: u64) {
274 if time_window_seconds > 0 {
275 self.queries_per_second = self.total_queries as f64 / time_window_seconds as f64;
276 }
277 }
278}