Skip to main content

backbone_search/
types.rs

1//! Search types and structures
2
3use serde::{Serialize, Deserialize};
4use std::collections::HashMap;
5use chrono::{DateTime, Utc};
6use uuid::Uuid;
7
8/// Search document
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SearchDocument {
11    /// Document identifier
12    pub id: String,
13
14    /// Document content (full text)
15    pub content: String,
16
17    /// Document title
18    pub title: Option<String>,
19
20    /// Document fields
21    pub fields: HashMap<String, serde_json::Value>,
22
23    /// Document metadata
24    pub metadata: DocumentMetadata,
25
26    /// Document timestamp
27    pub timestamp: DateTime<Utc>,
28
29    /// Document tags
30    pub tags: Vec<String>,
31
32    /// Document language
33    pub language: Option<String>,
34
35    /// Boost factor for search relevance
36    pub boost: f32,
37}
38
39/// Document metadata
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct DocumentMetadata {
42    /// Content type (MIME type)
43    pub content_type: Option<String>,
44
45    /// File size in bytes
46    pub file_size: Option<u64>,
47
48    /// Source URL or reference
49    pub source: Option<String>,
50
51    /// Author or creator
52    pub author: Option<String>,
53
54    /// Document category
55    pub category: Option<String>,
56
57    /// Keywords
58    pub keywords: Vec<String>,
59
60    /// Document permissions
61    pub permissions: Vec<String>,
62
63    /// Custom attributes
64    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/// Search query
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct SearchQuery {
85    /// Text search query
86    pub text: Option<String>,
87
88    /// Field-specific queries
89    pub fields: Vec<FieldQuery>,
90
91    /// Filters
92    pub filters: Vec<Filter>,
93
94    /// Sorting
95    pub sort: Vec<SortField>,
96
97    /// Pagination
98    pub pagination: Pagination,
99
100    /// Search options
101    pub options: SearchOptions,
102
103    /// Facets to compute
104    pub facets: Vec<FacetConfig>,
105
106    /// Aggregations
107    pub aggregations: Vec<Aggregation>,
108}
109
110/// Field-specific query
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct FieldQuery {
113    /// Field name
114    pub field: String,
115
116    /// Query text
117    pub query: String,
118
119    /// Query operator
120    pub operator: QueryOperator,
121
122    /// Boost factor
123    pub boost: Option<f32>,
124
125    /// Fuzziness
126    pub fuzziness: Option<f32>,
127}
128
129/// Query operator
130#[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/// Search filter
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct Filter {
144    /// Field name
145    pub field: String,
146
147    /// Filter value
148    pub value: FilterValue,
149
150    /// Filter operator
151    pub operator: FilterOperator,
152}
153
154/// Filter value
155#[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/// Range filter
167#[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/// Filter operator
176#[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/// Sort field
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct SortField {
198    /// Field name
199    pub field: String,
200
201    /// Sort order
202    pub order: SortOrder,
203
204    /// Sort mode
205    pub mode: Option<SortMode>,
206
207    /// Distance unit for geo sorting
208    pub unit: Option<String>,
209
210    /// Location for geo sorting
211    pub location: Option<GeoPoint>,
212}
213
214/// Sort order
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
216pub enum SortOrder {
217    Asc,
218    Desc,
219}
220
221/// Sort mode
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
223pub enum SortMode {
224    Min,
225    Max,
226    Sum,
227    Avg,
228}
229
230/// Geo point
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct GeoPoint {
233    pub lat: f64,
234    pub lon: f64,
235}
236
237/// Pagination
238#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
239pub struct Pagination {
240    /// Page number (1-based)
241    pub page: u32,
242
243    /// Page size
244    pub size: u32,
245
246    /// Offset (for offset-based pagination)
247    pub offset: Option<u32>,
248}
249
250/// Search options
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct SearchOptions {
253    /// Enable fuzzy search
254    pub fuzzy: bool,
255
256    /// Fuzziness level
257    pub fuzziness: f32,
258
259    /// Minimum should match
260    pub minimum_should_match: String,
261
262    /// Enable highlighting
263    pub highlight: bool,
264
265    /// Highlight configuration
266    pub highlight_config: Option<HighlightConfig>,
267
268    /// Enable snippets
269    pub snippets: bool,
270
271    /// Snippet length
272    pub snippet_length: usize,
273
274    /// Enable suggestions
275    pub suggestions: bool,
276
277    /// Suggestion limit
278    pub suggestion_limit: usize,
279
280    /// Boost query
281    pub boost_query: Option<Box<SearchQuery>>,
282
283    /// Search timeout in milliseconds
284    pub timeout_ms: Option<u64>,
285
286    /// Track total hits
287    pub track_total_hits: bool,
288
289    /// Explain scores
290    pub explain: bool,
291}
292
293/// Highlight configuration
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct HighlightConfig {
296    /// Pre tag
297    pub pre_tag: String,
298
299    /// Post tag
300    pub post_tag: String,
301
302    /// Fields to highlight
303    pub fields: Vec<String>,
304
305    /// Fragment size
306    pub fragment_size: usize,
307
308    /// Number of fragments
309    pub number_of_fragments: usize,
310}
311
312/// Facet configuration
313#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct FacetConfig {
315    /// Field name
316    pub field: String,
317
318    /// Facet type
319    pub facet_type: FacetType,
320
321    /// Size (number of buckets)
322    pub size: u32,
323
324    /// Filter for facet calculation
325    pub filter: Option<Box<Filter>>,
326
327    /// Sort order for facets
328    pub sort: Option<FacetSort>,
329}
330
331/// Facet type
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
333pub enum FacetType {
334    Terms,
335    Range,
336    DateRange,
337    Histogram,
338    Stats,
339}
340
341/// Facet sort
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
343pub enum FacetSort {
344    Count,
345    Key,
346    Alpha,
347}
348
349/// Aggregation
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct Aggregation {
352    /// Aggregation name
353    pub name: String,
354
355    /// Aggregation type
356    pub agg_type: AggregationType,
357
358    /// Field to aggregate
359    pub field: String,
360
361    /// Aggregation parameters
362    pub params: HashMap<String, serde_json::Value>,
363}
364
365/// Aggregation type
366#[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/// Search result
382#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct SearchResults {
384    /// Total number of hits
385    pub total_hits: u64,
386
387    /// Maximum score
388    pub max_score: Option<f64>,
389
390    /// Documents
391    pub hits: Vec<SearchHit>,
392
393    /// Aggregations
394    pub aggregations: HashMap<String, serde_json::Value>,
395
396    /// Facets
397    pub facets: HashMap<String, FacetResult>,
398
399    /// Suggestions
400    pub suggestions: Vec<Suggestion>,
401
402    /// Search metadata
403    pub metadata: SearchMetadata,
404}
405
406/// Search hit
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct SearchHit {
409    /// Document
410    pub document: SearchDocument,
411
412    /// Relevance score
413    pub score: Option<f64>,
414
415    /// Highlighted fragments
416    pub highlights: HashMap<String, Vec<String>>,
417
418    /// Snippets
419    pub snippets: HashMap<String, String>,
420
421    /// Sort values
422    pub sort_values: Vec<serde_json::Value>,
423
424    /// Matched queries
425    pub matched_queries: Vec<String>,
426
427    /// Inner hits (for nested documents)
428    pub inner_hits: HashMap<String, Vec<SearchHit>>,
429
430    /// Explanation
431    pub explanation: Option<serde_json::Value>,
432}
433
434/// Facet result
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct FacetResult {
437    /// Facet buckets
438    pub buckets: Vec<FacetBucket>,
439
440    /// Additional metadata
441    pub metadata: HashMap<String, serde_json::Value>,
442}
443
444/// Facet bucket
445#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct FacetBucket {
447    /// Bucket key
448    pub key: serde_json::Value,
449
450    /// Document count
451    pub count: u64,
452
453    /// Bucket sub-aggregations
454    pub sub_aggregations: HashMap<String, serde_json::Value>,
455}
456
457/// Search suggestion
458#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct Suggestion {
460    /// Suggestion text
461    pub text: String,
462
463    /// Suggestion type
464    pub suggestion_type: SuggestionType,
465
466    /// Score
467    pub score: f64,
468
469    /// Additional information
470    pub info: HashMap<String, serde_json::Value>,
471}
472
473/// Suggestion type
474#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
475pub enum SuggestionType {
476    Term,
477    Phrase,
478    Completion,
479    DidYouMean,
480}
481
482/// Search metadata
483#[derive(Debug, Clone, Serialize, Deserialize)]
484pub struct SearchMetadata {
485    /// Query time in milliseconds
486    pub query_time_ms: u64,
487
488    /// Number of shards searched
489    pub total_shards: u32,
490
491    /// Number of successful shards
492    pub successful_shards: u32,
493
494    /// Number of failed shards
495    pub failed_shards: u32,
496
497    /// Search scroll ID (if applicable)
498    pub scroll_id: Option<String>,
499
500    /// Backend-specific metadata
501    pub backend_metadata: HashMap<String, serde_json::Value>,
502}
503
504impl SearchDocument {
505    /// Create new document builder
506    pub fn builder() -> SearchDocumentBuilder {
507        SearchDocumentBuilder::new()
508    }
509
510    /// Get field value
511    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    /// Check if document has field
520    pub fn has_field(&self, field: &str) -> bool {
521        self.fields.contains_key(field)
522    }
523
524    /// Get all field names
525    pub fn field_names(&self) -> Vec<String> {
526        self.fields.keys().cloned().collect()
527    }
528}
529
530/// Search document builder
531pub struct SearchDocumentBuilder {
532    document: SearchDocument,
533}
534
535impl SearchDocumentBuilder {
536    /// Create new builder
537    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    /// Set document ID
554    pub fn id(mut self, id: impl Into<String>) -> Self {
555        self.document.id = id.into();
556        self
557    }
558
559    /// Set content
560    pub fn content(mut self, content: impl Into<String>) -> Self {
561        self.document.content = content.into();
562        self
563    }
564
565    /// Set title
566    pub fn title(mut self, title: impl Into<String>) -> Self {
567        self.document.title = Some(title.into());
568        self
569    }
570
571    /// Add field
572    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    /// Add JSON field
579    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    /// Set metadata
585    pub fn metadata(mut self, metadata: DocumentMetadata) -> Self {
586        self.document.metadata = metadata;
587        self
588    }
589
590    /// Add tag
591    pub fn tag(mut self, tag: impl Into<String>) -> Self {
592        self.document.tags.push(tag.into());
593        self
594    }
595
596    /// Add multiple tags
597    pub fn tags(mut self, tags: Vec<String>) -> Self {
598        self.document.tags.extend(tags);
599        self
600    }
601
602    /// Set language
603    pub fn language(mut self, language: impl Into<String>) -> Self {
604        self.document.language = Some(language.into());
605        self
606    }
607
608    /// Set boost
609    pub fn boost(mut self, boost: f32) -> Self {
610        self.document.boost = boost;
611        self
612    }
613
614    /// Build the search document
615    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    /// Create new query builder
628    pub fn builder() -> SearchQueryBuilder {
629        SearchQueryBuilder::new()
630    }
631}
632
633/// Search query builder
634pub struct SearchQueryBuilder {
635    query: SearchQuery,
636}
637
638impl SearchQueryBuilder {
639    /// Create new builder
640    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    /// Set text query
674    pub fn text(mut self, text: impl Into<String>) -> Self {
675        self.query.text = Some(text.into());
676        self
677    }
678
679    /// Add field query
680    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    /// Add filter
692    pub fn filter(mut self, filter: Filter) -> Self {
693        self.query.filters.push(filter);
694        self
695    }
696
697    /// Add simple filter
698    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    /// Add sort field
708    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    /// Set pagination
720    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    /// Set limit (for convenience)
730    pub fn limit(mut self, size: u32) -> Self {
731        self.query.pagination.size = size;
732        self
733    }
734
735    /// Enable fuzzy search
736    pub fn fuzzy(mut self, enabled: bool) -> Self {
737        self.query.options.fuzzy = enabled;
738        self
739    }
740
741    /// Set fuzziness
742    pub fn fuzziness(mut self, fuzziness: f32) -> Self {
743        self.query.options.fuzziness = fuzziness;
744        self
745    }
746
747    /// Enable highlighting
748    pub fn highlight(mut self, enabled: bool) -> Self {
749        self.query.options.highlight = enabled;
750        self
751    }
752
753    /// Add facet
754    pub fn facet(mut self, facet: FacetConfig) -> Self {
755        self.query.facets.push(facet);
756        self
757    }
758
759    /// Add aggregation
760    pub fn aggregation(mut self, aggregation: Aggregation) -> Self {
761        self.query.aggregations.push(aggregation);
762        self
763    }
764
765    /// Build the search query
766    pub fn build(self) -> SearchQuery {
767        self.query
768    }
769}
770
771impl Default for SearchQueryBuilder {
772    fn default() -> Self {
773        Self::new()
774    }
775}