Skip to content

Commit 8a5f558

Browse files
Add federated multi search API
Fixes meilisearch#609
1 parent 8f044fc commit 8a5f558

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

src/client.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,21 @@ impl<Http: HttpClient> Client<Http> {
128128
.await
129129
}
130130

131+
pub async fn execute_federated_multi_search_query<
132+
T: 'static + DeserializeOwned + Send + Sync,
133+
>(
134+
&self,
135+
body: &FederatedMultiSearchQuery<'_, '_, Http>,
136+
) -> Result<FederatedMultiSearchResponse<T>, Error> {
137+
self.http_client
138+
.request::<(), &FederatedMultiSearchQuery<Http>, FederatedMultiSearchResponse<T>>(
139+
&format!("{}/multi-search", &self.host),
140+
Method::Post { body, query: () },
141+
200,
142+
)
143+
.await
144+
}
145+
131146
/// Make multiple search requests.
132147
///
133148
/// # Example
@@ -170,6 +185,22 @@ impl<Http: HttpClient> Client<Http> {
170185
/// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
171186
/// # });
172187
/// ```
188+
///
189+
/// # Federated Search
190+
///
191+
/// You can use [`MultiSearchQuery::with_federation`] to perform a [federated
192+
/// search][1] where results from different indexes are merged and returned as
193+
/// one list.
194+
///
195+
/// When executing a federated query, the type parameter `T` is less clear,
196+
/// as the documents in the different indexes potentially have different
197+
/// fields and you might have one Rust type per index. In most cases, you
198+
/// either want to create an enum with one variant per index and `#[serde
199+
/// (untagged)]` attribute, or if you need more control, just pass
200+
/// `serde_json::Map<String, serde_json::Value>` and then deserialize that
201+
/// into the appropriate target types later.
202+
///
203+
/// [1]: https://www.meilisearch.com/docs/learn/multi_search/multi_search_vs_federated_search#what-is-federated-search
173204
#[must_use]
174205
pub fn multi_search(&self) -> MultiSearchQuery<Http> {
175206
MultiSearchQuery::new(self)

src/search.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ pub struct SearchResult<T> {
5555
pub ranking_score: Option<f64>,
5656
#[serde(rename = "_rankingScoreDetails")]
5757
pub ranking_score_details: Option<Map<String, Value>>,
58+
/// Only returned for federated multi search.
59+
#[serde(rename = "_federation")]
60+
pub federation: Option<FederationHitInfo>,
5861
}
5962

6063
#[derive(Deserialize, Debug, Clone)]
@@ -613,7 +616,6 @@ pub struct MultiSearchQuery<'a, 'b, Http: HttpClient = DefaultHttpClient> {
613616
pub queries: Vec<SearchQuery<'b, Http>>,
614617
}
615618

616-
617619
#[allow(missing_docs)]
618620
impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
619621
#[must_use]
@@ -631,6 +633,17 @@ impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
631633
self.queries.push(search_query);
632634
self
633635
}
636+
/// Adds the `federation` parameter, making the search a federated search.
637+
pub fn with_federation(
638+
self,
639+
federation: FederationOptions,
640+
) -> FederatedMultiSearchQuery<'a, 'b, Http> {
641+
FederatedMultiSearchQuery {
642+
client: self.client,
643+
queries: self.queries,
644+
federation: Some(federation),
645+
}
646+
}
634647

635648
/// Execute the query and fetch the results.
636649
pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
@@ -644,6 +657,77 @@ pub struct MultiSearchResponse<T> {
644657
pub results: Vec<SearchResults<T>>,
645658
}
646659

660+
#[derive(Debug, Serialize, Clone)]
661+
#[serde(rename_all = "camelCase")]
662+
pub struct FederatedMultiSearchQuery<'a, 'b, Http: HttpClient = DefaultHttpClient> {
663+
#[serde(skip_serializing)]
664+
client: &'a Client<Http>,
665+
#[serde(bound(serialize = ""))]
666+
pub queries: Vec<SearchQuery<'b, Http>>,
667+
pub federation: Option<FederationOptions>,
668+
}
669+
670+
/// The `federation` field of the multi search API.
671+
/// See [the docs](https://www.meilisearch.com/docs/reference/api/multi_search#federation).
672+
#[derive(Debug, Serialize, Clone, Default)]
673+
#[serde(rename_all = "camelCase")]
674+
pub struct FederationOptions {
675+
#[serde(skip_serializing_if = "Option::is_none")]
676+
pub offset: Option<usize>,
677+
#[serde(skip_serializing_if = "Option::is_none")]
678+
pub limit: Option<usize>,
679+
#[serde(skip_serializing_if = "Option::is_none")]
680+
pub facets_by_index: Option<HashMap<String, Vec<String>>>,
681+
#[serde(skip_serializing_if = "Option::is_none")]
682+
pub merge_facets: Option<bool>,
683+
}
684+
685+
#[allow(missing_docs)]
686+
impl<'a, 'b, Http: HttpClient> FederatedMultiSearchQuery<'a, 'b, Http> {
687+
/// Execute the query and fetch the results.
688+
pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
689+
&'a self,
690+
) -> Result<FederatedMultiSearchResponse<T>, Error> {
691+
self.client
692+
.execute_federated_multi_search_query::<T>(self)
693+
.await
694+
}
695+
}
696+
697+
/// Returned by federated multi search.
698+
#[derive(Debug, Deserialize, Clone)]
699+
#[serde(rename_all = "camelCase")]
700+
pub struct FederatedMultiSearchResponse<T> {
701+
/// Merged results of the query.
702+
pub hits: Vec<SearchResult<T>>,
703+
704+
// TODO: are offset, limit and estimated_total_hits really non-optional? In
705+
// my tests they are always returned, but that's not a proof.
706+
/// Number of documents skipped.
707+
pub offset: usize,
708+
/// Number of results returned.
709+
pub limit: usize,
710+
/// Estimated total number of matches.
711+
pub estimated_total_hits: usize,
712+
713+
/// Distribution of the given facets.
714+
pub facet_distribution: Option<HashMap<String, HashMap<String, usize>>>,
715+
/// facet stats of the numerical facets requested in the `facet` search parameter.
716+
pub facet_stats: Option<HashMap<String, FacetStats>>,
717+
/// Processing time of the query.
718+
pub processing_time_ms: usize,
719+
}
720+
721+
/// Returned for each hit in `_federation` when doing federated multi search.
722+
#[derive(Debug, Deserialize, Clone)]
723+
#[serde(rename_all = "camelCase")]
724+
pub struct FederationHitInfo {
725+
pub index_uid: String,
726+
pub queries_position: usize,
727+
// TOOD: not mentioned in the docs, is that optional?
728+
pub weighted_ranking_score: f32,
729+
}
730+
647731
#[cfg(test)]
648732
mod tests {
649733
use crate::{

0 commit comments

Comments
 (0)