11//! Contains the generic `SearchResult` and `SearchResultHit` structs
22
33use serde:: { de:: DeserializeOwned , Deserialize , Serialize } ;
4+ use serde_json:: Value ;
45use typesense_codegen:: models as raw_models;
56
67/// Represents a single search result hit, with the document deserialized into a strongly-typed struct `T`.
@@ -105,24 +106,28 @@ where
105106 ) -> Result < Self , serde_json:: Error > {
106107 let typed_hits = match raw_result. hits {
107108 Some ( raw_hits) => {
108- let mut hits = Vec :: with_capacity ( raw_hits. len ( ) ) ;
109- for raw_hit in raw_hits {
110- let document: Option < T > = match raw_hit. document {
111- Some ( doc_value) => Some ( serde_json:: from_value ( doc_value) ?) ,
112- None => None ,
113- } ;
114-
115- hits. push ( SearchResultHit {
116- document,
117- highlights : raw_hit. highlights ,
118- highlight : raw_hit. highlight ,
119- text_match : raw_hit. text_match ,
120- text_match_info : raw_hit. text_match_info ,
121- geo_distance_meters : raw_hit. geo_distance_meters ,
122- vector_distance : raw_hit. vector_distance ,
123- } ) ;
124- }
125- Some ( hits)
109+ let hits_result: Result < Vec < SearchResultHit < T > > , _ > = raw_hits
110+ . into_iter ( )
111+ . map ( |raw_hit| {
112+ // Map each raw hit to a Result<SearchResultHit<T>, _>
113+ let document: Result < Option < T > , _ > = raw_hit
114+ . document
115+ . map ( |doc_value| serde_json:: from_value ( doc_value) )
116+ . transpose ( ) ;
117+
118+ Ok ( SearchResultHit {
119+ document : document?,
120+ highlights : raw_hit. highlights ,
121+ highlight : raw_hit. highlight ,
122+ text_match : raw_hit. text_match ,
123+ text_match_info : raw_hit. text_match_info ,
124+ geo_distance_meters : raw_hit. geo_distance_meters ,
125+ vector_distance : raw_hit. vector_distance ,
126+ } )
127+ } )
128+ . collect ( ) ;
129+
130+ Some ( hits_result?)
126131 }
127132 None => None ,
128133 } ;
@@ -142,3 +147,71 @@ where
142147 } )
143148 }
144149}
150+
151+ // This impl block specifically targets `SearchResult<serde_json::Value>`.
152+ // The methods inside will only be available on a search result of that exact type.
153+ impl SearchResult < Value > {
154+ /// Attempts to convert a `SearchResult<serde_json::Value>` into a `SearchResult<T>`.
155+ ///
156+ /// This method is useful after a `perform_union` call where you know all resulting
157+ /// documents share the same schema and can be deserialized into a single concrete type `T`.
158+ ///
159+ /// It iterates through each hit and tries to deserialize its `document` field. If any
160+ /// document fails to deserialize into type `T`, the entire conversion fails.
161+ ///
162+ /// # Type Parameters
163+ ///
164+ /// * `T` - The concrete, `DeserializeOwned` type you want to convert the documents into.
165+ ///
166+ /// # Errors
167+ ///
168+ /// Returns a `serde_json::Error` if any document in the hit list cannot be successfully
169+ /// deserialized into `T`.
170+ pub fn try_into_typed < T : DeserializeOwned > ( self ) -> Result < SearchResult < T > , serde_json:: Error > {
171+ // This logic is very similar to `from_raw`, but it converts between generic types
172+ // instead of from a raw model.
173+ let typed_hits = match self . hits {
174+ Some ( value_hits) => {
175+ let hits_result: Result < Vec < SearchResultHit < T > > , _ > = value_hits
176+ . into_iter ( )
177+ . map ( |value_hit| {
178+ // `value_hit` here is `SearchResultHit<serde_json::Value>`
179+ let document: Option < T > = match value_hit. document {
180+ Some ( doc_value) => Some ( serde_json:: from_value ( doc_value) ?) ,
181+ None => None ,
182+ } ;
183+
184+ // Construct the new, strongly-typed hit.
185+ Ok ( SearchResultHit {
186+ document,
187+ highlights : value_hit. highlights ,
188+ highlight : value_hit. highlight ,
189+ text_match : value_hit. text_match ,
190+ text_match_info : value_hit. text_match_info ,
191+ geo_distance_meters : value_hit. geo_distance_meters ,
192+ vector_distance : value_hit. vector_distance ,
193+ } )
194+ } )
195+ . collect ( ) ;
196+
197+ Some ( hits_result?)
198+ }
199+ None => None ,
200+ } ;
201+
202+ // Construct the final, strongly-typed search result, carrying over all metadata.
203+ Ok ( SearchResult {
204+ hits : typed_hits,
205+ found : self . found ,
206+ found_docs : self . found_docs ,
207+ out_of : self . out_of ,
208+ page : self . page ,
209+ search_time_ms : self . search_time_ms ,
210+ facet_counts : self . facet_counts ,
211+ grouped_hits : self . grouped_hits ,
212+ search_cutoff : self . search_cutoff ,
213+ request_params : self . request_params ,
214+ conversation : self . conversation ,
215+ } )
216+ }
217+ }
0 commit comments