Skip to content

Commit 5709e19

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

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

0 commit comments

Comments
 (0)