Skip to main content

opensearch_client/search/
mod.rs

1use std::sync::Arc;
2
3use crate::{Error, OsClient};
4use opensearch_dsl::{util::ShouldSkip, InnerHitsResult, Map};
5use opensearch_dsl::{
6    Explanation, NestedIdentity, Query, SearchResponse, ShardStatistics, SortCollection, Terms,
7    TotalHits,
8};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12/// Represents the state of a search operation that uses the "search after"
13/// feature.
14pub struct SearchAfterState {
15    pub client: Arc<OsClient>,
16    pub index: String,
17    pub stop: bool,
18    pub size: u64,
19    pub query: Query,
20    pub sort: SortCollection,
21    pub search_after: Option<Terms>,
22}
23
24#[derive(Clone, Debug, Deserialize, Serialize, Default)]
25pub struct TypedSearchResult<T> {
26    #[serde(default)]
27    pub hits: TypedHitsMetadata<T>,
28    #[serde(
29        rename = "_scroll_id",
30        default,
31        skip_serializing_if = "Option::is_none"
32    )]
33    pub scroll_id: Option<String>,
34    #[serde(rename = "_shards", default)]
35    pub shards: ShardStatistics,
36    #[serde(default)]
37    pub timed_out: bool,
38    #[serde(default)]
39    pub took: u32,
40}
41
42impl<T: serde::de::DeserializeOwned /*+ std::default::Default*/> TypedSearchResult<T> {
43    pub fn from_response(response: SearchResponse) -> Result<Self, crate::Error> {
44        // Implement conversion logic from SearchSuccess to TypedSearchResult<T>
45        // Example stub:
46        let hits: TypedHitsMetadata<T> = TypedHitsMetadata::from_response(response.hits)?;
47
48        Ok(TypedSearchResult {
49            // fill fields from response
50            hits,
51            scroll_id: response.scroll_id,
52            shards: response.shards,
53            timed_out: response.timed_out,
54            took: response.took,
55        })
56    }
57}
58
59#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
60pub struct TypedHit<T> {
61    /// Search explanation
62    #[serde(
63        default,
64        skip_serializing_if = "ShouldSkip::should_skip",
65        rename = "_explanation"
66    )]
67    pub explanation: Option<Explanation>,
68
69    /// Document index
70    #[serde(
71        default,
72        skip_serializing_if = "ShouldSkip::should_skip",
73        rename = "_index"
74    )]
75    pub index: String,
76
77    /// Document ID
78    #[serde(
79        default,
80        skip_serializing_if = "ShouldSkip::should_skip",
81        rename = "_id"
82    )]
83    pub id: String,
84
85    /// Document score. [`None`] when documents are implicitly sorted by a
86    /// field other than `_score`
87    #[serde(
88        default,
89        skip_serializing_if = "ShouldSkip::should_skip",
90        rename = "_score"
91    )]
92    pub score: Option<f32>,
93
94    /// Nested document identity
95    #[serde(
96        default,
97        skip_serializing_if = "ShouldSkip::should_skip",
98        rename = "_nested"
99    )]
100    pub nested: Option<NestedIdentity>,
101
102    /// Document source
103    #[serde(
104        default,
105        skip_serializing_if = "ShouldSkip::should_skip",
106        rename = "_source"
107    )]
108    pub source: Option<T>,
109
110    /// Highlighted matches
111    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
112    pub highlight: Map<String, Vec<String>>,
113
114    /// Inner hits
115    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
116    pub inner_hits: Map<String, InnerHitsResult>,
117
118    /// Matched queries
119    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
120    pub matched_queries: Vec<String>,
121
122    /// Values document was sorted by
123    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
124    pub sort: Vec<serde_json::Value>,
125
126    /// Field values for the documents. Need to be specified in the request
127    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
128    pub fields: Map<String, serde_json::Value>,
129}
130
131impl<T> From<&TypedHit<T>> for TypedHit<T> {
132    fn from(value: &TypedHit<T>) -> Self {
133        value.into()
134    }
135}
136
137impl<T: serde::de::DeserializeOwned> TypedHit<T> {
138    pub fn from_hit(hit: opensearch_dsl::Hit) -> TypedHit<T> {
139        let parsed: Result<T, serde_json::Error> = hit.source.parse();
140        let source: Option<T> = if let Ok(src) = parsed {
141            Some(src)
142        } else {
143            None
144        };
145        TypedHit {
146            explanation: hit.explanation,
147            index: hit.index,
148            id: hit.id,
149            score: hit.score,
150            nested: hit.nested,
151            source,
152            highlight: hit.highlight,
153            inner_hits: hit.inner_hits,
154            matched_queries: hit.matched_queries,
155            sort: hit.sort,
156            fields: hit.fields,
157        }
158    }
159}
160
161#[derive(Clone, Debug, Deserialize, Serialize)]
162pub struct TypedHitsMetadata<T> {
163    #[serde(default, skip_serializing_if = "Vec::is_empty")]
164    pub hits: Vec<TypedHit<T>>,
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub max_score: Option<f32>,
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub total: Option<TotalHits>,
169}
170
171impl<T> TypedHitsMetadata<T> {
172    pub fn get_total_value(&self) -> Option<u64> {
173        self.total.as_ref().map(|t| t.value)
174    }
175}
176
177impl<T> From<&TypedHitsMetadata<T>> for TypedHitsMetadata<T> {
178    fn from(value: &TypedHitsMetadata<T>) -> Self {
179        value.into()
180    }
181}
182
183impl<T> Default for TypedHitsMetadata<T> {
184    fn default() -> Self {
185        Self {
186            hits: Vec::new(),
187            max_score: None,
188            total: None,
189        }
190    }
191}
192
193impl<T: serde::de::DeserializeOwned> TypedHitsMetadata<T> {
194    pub fn from_response(hits: opensearch_dsl::HitsMetadata) -> Result<Self, crate::Error> {
195        let typed_hits = hits
196            .hits
197            .into_iter()
198            .map(|hit| TypedHit::from_hit(hit))
199            .collect::<Vec<_>>();
200
201        Ok(TypedHitsMetadata {
202            hits: typed_hits,
203            max_score: hits.max_score,
204            total: hits.total,
205        })
206    }
207}
208
209// impl<T> From<&TypedHitsMetadata<T>> for TypedHitsMetadata<T> {
210//     fn from(value: &TypedHitsMetadata<T>) -> Self {
211//         value.clone()
212//     }
213// }
214
215// impl<T> SearchResult<T> {
216//     pub fn builder() -> builder::SearchResult<T> {
217//         builder::SearchResult::default()
218//     }
219// }