Skip to content

Commit 7f65654

Browse files
Add federated multi search API
Fixes meilisearch#609
1 parent f4d5758 commit 7f65654

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)]
@@ -600,7 +603,6 @@ pub struct MultiSearchQuery<'a, 'b, Http: HttpClient = DefaultHttpClient> {
600603
pub queries: Vec<SearchQuery<'b, Http>>,
601604
}
602605

603-
604606
#[allow(missing_docs)]
605607
impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
606608
#[must_use]
@@ -618,6 +620,17 @@ impl<'a, 'b, Http: HttpClient> MultiSearchQuery<'a, 'b, Http> {
618620
self.queries.push(search_query);
619621
self
620622
}
623+
/// Adds the `federation` parameter, making the search a federated search.
624+
pub fn with_federation(
625+
self,
626+
federation: FederationOptions,
627+
) -> FederatedMultiSearchQuery<'a, 'b, Http> {
628+
FederatedMultiSearchQuery {
629+
client: self.client,
630+
queries: self.queries,
631+
federation: Some(federation),
632+
}
633+
}
621634

622635
/// Execute the query and fetch the results.
623636
pub async fn execute<T: 'static + DeserializeOwned + Send + Sync>(
@@ -631,6 +644,77 @@ pub struct MultiSearchResponse<T> {
631644
pub results: Vec<SearchResults<T>>,
632645
}
633646

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

0 commit comments

Comments
 (0)