1use serde::{Serialize, Deserialize};
4use std::collections::HashMap;
5use chrono::{DateTime, Utc};
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SearchDocument {
11 pub id: String,
13
14 pub content: String,
16
17 pub title: Option<String>,
19
20 pub fields: HashMap<String, serde_json::Value>,
22
23 pub metadata: DocumentMetadata,
25
26 pub timestamp: DateTime<Utc>,
28
29 pub tags: Vec<String>,
31
32 pub language: Option<String>,
34
35 pub boost: f32,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct DocumentMetadata {
42 pub content_type: Option<String>,
44
45 pub file_size: Option<u64>,
47
48 pub source: Option<String>,
50
51 pub author: Option<String>,
53
54 pub category: Option<String>,
56
57 pub keywords: Vec<String>,
59
60 pub permissions: Vec<String>,
62
63 pub attributes: HashMap<String, String>,
65}
66
67impl Default for DocumentMetadata {
68 fn default() -> Self {
69 Self {
70 content_type: None,
71 file_size: None,
72 source: None,
73 author: None,
74 category: None,
75 keywords: Vec::new(),
76 permissions: Vec::new(),
77 attributes: HashMap::new(),
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct SearchQuery {
85 pub text: Option<String>,
87
88 pub fields: Vec<FieldQuery>,
90
91 pub filters: Vec<Filter>,
93
94 pub sort: Vec<SortField>,
96
97 pub pagination: Pagination,
99
100 pub options: SearchOptions,
102
103 pub facets: Vec<FacetConfig>,
105
106 pub aggregations: Vec<Aggregation>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct FieldQuery {
113 pub field: String,
115
116 pub query: String,
118
119 pub operator: QueryOperator,
121
122 pub boost: Option<f32>,
124
125 pub fuzziness: Option<f32>,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
131pub enum QueryOperator {
132 And,
133 Or,
134 Not,
135 Phrase,
136 Prefix,
137 Wildcard,
138 Fuzzy,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct Filter {
144 pub field: String,
146
147 pub value: FilterValue,
149
150 pub operator: FilterOperator,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(untagged)]
157pub enum FilterValue {
158 String(String),
159 Number(f64),
160 Boolean(bool),
161 Array(Vec<serde_json::Value>),
162 Range(FilterRange),
163 Exists(String),
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct FilterRange {
169 pub gte: Option<f64>,
170 pub gt: Option<f64>,
171 pub lte: Option<f64>,
172 pub lt: Option<f64>,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
177pub enum FilterOperator {
178 Equals,
179 NotEquals,
180 In,
181 NotIn,
182 GreaterThan,
183 GreaterThanOrEqual,
184 LessThan,
185 LessThanOrEqual,
186 Exists,
187 NotExists,
188 Contains,
189 StartsWith,
190 EndsWith,
191 GeoWithin,
192 GeoDistance,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct SortField {
198 pub field: String,
200
201 pub order: SortOrder,
203
204 pub mode: Option<SortMode>,
206
207 pub unit: Option<String>,
209
210 pub location: Option<GeoPoint>,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216pub enum SortOrder {
217 Asc,
218 Desc,
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
223pub enum SortMode {
224 Min,
225 Max,
226 Sum,
227 Avg,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct GeoPoint {
233 pub lat: f64,
234 pub lon: f64,
235}
236
237#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
239pub struct Pagination {
240 pub page: u32,
242
243 pub size: u32,
245
246 pub offset: Option<u32>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct SearchOptions {
253 pub fuzzy: bool,
255
256 pub fuzziness: f32,
258
259 pub minimum_should_match: String,
261
262 pub highlight: bool,
264
265 pub highlight_config: Option<HighlightConfig>,
267
268 pub snippets: bool,
270
271 pub snippet_length: usize,
273
274 pub suggestions: bool,
276
277 pub suggestion_limit: usize,
279
280 pub boost_query: Option<Box<SearchQuery>>,
282
283 pub timeout_ms: Option<u64>,
285
286 pub track_total_hits: bool,
288
289 pub explain: bool,
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct HighlightConfig {
296 pub pre_tag: String,
298
299 pub post_tag: String,
301
302 pub fields: Vec<String>,
304
305 pub fragment_size: usize,
307
308 pub number_of_fragments: usize,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct FacetConfig {
315 pub field: String,
317
318 pub facet_type: FacetType,
320
321 pub size: u32,
323
324 pub filter: Option<Box<Filter>>,
326
327 pub sort: Option<FacetSort>,
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
333pub enum FacetType {
334 Terms,
335 Range,
336 DateRange,
337 Histogram,
338 Stats,
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
343pub enum FacetSort {
344 Count,
345 Key,
346 Alpha,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct Aggregation {
352 pub name: String,
354
355 pub agg_type: AggregationType,
357
358 pub field: String,
360
361 pub params: HashMap<String, serde_json::Value>,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
367pub enum AggregationType {
368 Terms,
369 DateHistogram,
370 Histogram,
371 Stats,
372 ExtendedStats,
373 Min,
374 Max,
375 Sum,
376 Avg,
377 Cardinality,
378 Percentiles,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct SearchResults {
384 pub total_hits: u64,
386
387 pub max_score: Option<f64>,
389
390 pub hits: Vec<SearchHit>,
392
393 pub aggregations: HashMap<String, serde_json::Value>,
395
396 pub facets: HashMap<String, FacetResult>,
398
399 pub suggestions: Vec<Suggestion>,
401
402 pub metadata: SearchMetadata,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct SearchHit {
409 pub document: SearchDocument,
411
412 pub score: Option<f64>,
414
415 pub highlights: HashMap<String, Vec<String>>,
417
418 pub snippets: HashMap<String, String>,
420
421 pub sort_values: Vec<serde_json::Value>,
423
424 pub matched_queries: Vec<String>,
426
427 pub inner_hits: HashMap<String, Vec<SearchHit>>,
429
430 pub explanation: Option<serde_json::Value>,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct FacetResult {
437 pub buckets: Vec<FacetBucket>,
439
440 pub metadata: HashMap<String, serde_json::Value>,
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct FacetBucket {
447 pub key: serde_json::Value,
449
450 pub count: u64,
452
453 pub sub_aggregations: HashMap<String, serde_json::Value>,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct Suggestion {
460 pub text: String,
462
463 pub suggestion_type: SuggestionType,
465
466 pub score: f64,
468
469 pub info: HashMap<String, serde_json::Value>,
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
475pub enum SuggestionType {
476 Term,
477 Phrase,
478 Completion,
479 DidYouMean,
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize)]
484pub struct SearchMetadata {
485 pub query_time_ms: u64,
487
488 pub total_shards: u32,
490
491 pub successful_shards: u32,
493
494 pub failed_shards: u32,
496
497 pub scroll_id: Option<String>,
499
500 pub backend_metadata: HashMap<String, serde_json::Value>,
502}
503
504impl SearchDocument {
505 pub fn builder() -> SearchDocumentBuilder {
507 SearchDocumentBuilder::new()
508 }
509
510 pub fn get_field<T>(&self, field: &str) -> Option<T>
512 where
513 T: for<'de> Deserialize<'de>,
514 {
515 self.fields.get(field)
516 .and_then(|v| serde_json::from_value(v.clone()).ok())
517 }
518
519 pub fn has_field(&self, field: &str) -> bool {
521 self.fields.contains_key(field)
522 }
523
524 pub fn field_names(&self) -> Vec<String> {
526 self.fields.keys().cloned().collect()
527 }
528}
529
530pub struct SearchDocumentBuilder {
532 document: SearchDocument,
533}
534
535impl SearchDocumentBuilder {
536 pub fn new() -> Self {
538 Self {
539 document: SearchDocument {
540 id: Uuid::new_v4().to_string(),
541 content: String::new(),
542 title: None,
543 fields: HashMap::new(),
544 metadata: DocumentMetadata::default(),
545 timestamp: Utc::now(),
546 tags: Vec::new(),
547 language: None,
548 boost: 1.0,
549 },
550 }
551 }
552
553 pub fn id(mut self, id: impl Into<String>) -> Self {
555 self.document.id = id.into();
556 self
557 }
558
559 pub fn content(mut self, content: impl Into<String>) -> Self {
561 self.document.content = content.into();
562 self
563 }
564
565 pub fn title(mut self, title: impl Into<String>) -> Self {
567 self.document.title = Some(title.into());
568 self
569 }
570
571 pub fn field<T: Serialize>(mut self, name: impl Into<String>, value: T) -> Result<Self, serde_json::Error> {
573 let json_value = serde_json::to_value(value)?;
574 self.document.fields.insert(name.into(), json_value);
575 Ok(self)
576 }
577
578 pub fn json_field(mut self, name: impl Into<String>, value: serde_json::Value) -> Self {
580 self.document.fields.insert(name.into(), value);
581 self
582 }
583
584 pub fn metadata(mut self, metadata: DocumentMetadata) -> Self {
586 self.document.metadata = metadata;
587 self
588 }
589
590 pub fn tag(mut self, tag: impl Into<String>) -> Self {
592 self.document.tags.push(tag.into());
593 self
594 }
595
596 pub fn tags(mut self, tags: Vec<String>) -> Self {
598 self.document.tags.extend(tags);
599 self
600 }
601
602 pub fn language(mut self, language: impl Into<String>) -> Self {
604 self.document.language = Some(language.into());
605 self
606 }
607
608 pub fn boost(mut self, boost: f32) -> Self {
610 self.document.boost = boost;
611 self
612 }
613
614 pub fn build(self) -> SearchDocument {
616 self.document
617 }
618}
619
620impl Default for SearchDocumentBuilder {
621 fn default() -> Self {
622 Self::new()
623 }
624}
625
626impl SearchQuery {
627 pub fn builder() -> SearchQueryBuilder {
629 SearchQueryBuilder::new()
630 }
631}
632
633pub struct SearchQueryBuilder {
635 query: SearchQuery,
636}
637
638impl SearchQueryBuilder {
639 pub fn new() -> Self {
641 Self {
642 query: SearchQuery {
643 text: None,
644 fields: Vec::new(),
645 filters: Vec::new(),
646 sort: Vec::new(),
647 pagination: Pagination {
648 page: 1,
649 size: 20,
650 offset: None,
651 },
652 options: SearchOptions {
653 fuzzy: false,
654 fuzziness: 0.7,
655 minimum_should_match: "75%".to_string(),
656 highlight: true,
657 highlight_config: None,
658 snippets: true,
659 snippet_length: 200,
660 suggestions: false,
661 suggestion_limit: 10,
662 boost_query: None,
663 timeout_ms: None,
664 track_total_hits: true,
665 explain: false,
666 },
667 facets: Vec::new(),
668 aggregations: Vec::new(),
669 },
670 }
671 }
672
673 pub fn text(mut self, text: impl Into<String>) -> Self {
675 self.query.text = Some(text.into());
676 self
677 }
678
679 pub fn field_query(mut self, field: impl Into<String>, query: impl Into<String>) -> Self {
681 self.query.fields.push(FieldQuery {
682 field: field.into(),
683 query: query.into(),
684 operator: QueryOperator::And,
685 boost: None,
686 fuzziness: None,
687 });
688 self
689 }
690
691 pub fn filter(mut self, filter: Filter) -> Self {
693 self.query.filters.push(filter);
694 self
695 }
696
697 pub fn field_filter(mut self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
699 self.query.filters.push(Filter {
700 field: field.into(),
701 value: value.into(),
702 operator: FilterOperator::Equals,
703 });
704 self
705 }
706
707 pub fn sort(mut self, field: impl Into<String>, order: SortOrder) -> Self {
709 self.query.sort.push(SortField {
710 field: field.into(),
711 order,
712 mode: None,
713 unit: None,
714 location: None,
715 });
716 self
717 }
718
719 pub fn pagination(mut self, page: u32, size: u32) -> Self {
721 self.query.pagination = Pagination {
722 page,
723 size,
724 offset: None,
725 };
726 self
727 }
728
729 pub fn limit(mut self, size: u32) -> Self {
731 self.query.pagination.size = size;
732 self
733 }
734
735 pub fn fuzzy(mut self, enabled: bool) -> Self {
737 self.query.options.fuzzy = enabled;
738 self
739 }
740
741 pub fn fuzziness(mut self, fuzziness: f32) -> Self {
743 self.query.options.fuzziness = fuzziness;
744 self
745 }
746
747 pub fn highlight(mut self, enabled: bool) -> Self {
749 self.query.options.highlight = enabled;
750 self
751 }
752
753 pub fn facet(mut self, facet: FacetConfig) -> Self {
755 self.query.facets.push(facet);
756 self
757 }
758
759 pub fn aggregation(mut self, aggregation: Aggregation) -> Self {
761 self.query.aggregations.push(aggregation);
762 self
763 }
764
765 pub fn build(self) -> SearchQuery {
767 self.query
768 }
769}
770
771impl Default for SearchQueryBuilder {
772 fn default() -> Self {
773 Self::new()
774 }
775}