Skip to content

Commit efd9dfb

Browse files
Add federated multi search API
Fixes #609
1 parent 922d264 commit efd9dfb

File tree

2 files changed

+117
-1
lines changed

2 files changed

+117
-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: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ pub struct SearchResult<T> {
6666
pub ranking_score: Option<f64>,
6767
#[serde(rename = "_rankingScoreDetails")]
6868
pub ranking_score_details: Option<Map<String, Value>>,
69+
/// Only returned for federated multi search.
70+
#[serde(rename = "_federation")]
71+
pub federation: Option<FederationHitInfo>,
6972
}
7073

7174
#[derive(Deserialize, Debug, Clone)]
@@ -624,7 +627,6 @@ pub struct MultiSearchQuery<'a, 'b, Http: HttpClient = DefaultHttpClient> {
624627
pub queries: Vec<SearchQuery<'b, Http>>,
625628
}
626629

627-
628630
#[allow(missing_docs)]
629631
impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
630632
#[must_use]
@@ -642,6 +644,17 @@ impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
642644
self.queries.push(search_query);
643645
self
644646
}
647+
/// Adds the `federation` parameter, making the search a federated search.
648+
pub fn with_federation(
649+
self,
650+
federation: FederationOptions,
651+
) -> FederatedMultiSearchQuery<'a, 'b, Http> {
652+
FederatedMultiSearchQuery {
653+
client: self.client,
654+
queries: self.queries,
655+
federation: Some(federation),
656+
}
657+
}
645658

646659
/// Execute the query and fetch the results.
647660
pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
@@ -655,6 +668,78 @@ pub struct MultiSearchResponse<T> {
655668
pub results: Vec<SearchResults<T>>,
656669
}
657670

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

0 commit comments

Comments
 (0)