Skip to content

Commit b9c9553

Browse files
Add federated multi search API
Fixes meilisearch#609
1 parent 8ce4d17 commit b9c9553

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)]
@@ -604,7 +607,6 @@ pub struct MultiSearchQuery<'a, 'b, Http: HttpClient = DefaultHttpClient> {
604607
pub queries: Vec<SearchQuery<'b, Http>>,
605608
}
606609

607-
608610
#[allow(missing_docs)]
609611
impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
610612
#[must_use]
@@ -622,6 +624,17 @@ impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
622624
self.queries.push(search_query);
623625
self
624626
}
627+
/// Adds the `federation` parameter, making the search a federated search.
628+
pub fn with_federation(
629+
self,
630+
federation: FederationOptions,
631+
) -> FederatedMultiSearchQuery<'a, 'b, Http> {
632+
FederatedMultiSearchQuery {
633+
client: self.client,
634+
queries: self.queries,
635+
federation: Some(federation),
636+
}
637+
}
625638

626639
/// Execute the query and fetch the results.
627640
pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
@@ -635,6 +648,77 @@ pub struct MultiSearchResponse<T> {
635648
pub results: Vec<SearchResults<T>>,
636649
}
637650

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

0 commit comments

Comments
 (0)