@@ -662,6 +662,153 @@ pub struct MultiSearchResponse<T> {
662662 pub results : Vec < SearchResults < T > > ,
663663}
664664
665+ /// A struct representing a facet-search query.
666+ ///
667+ /// You can add search parameters using the builder syntax.
668+ ///
669+ /// See [this page](https://www.meilisearch.com/docs/reference/api/facet_search) for the official list and description of all parameters.
670+ ///
671+ /// # Examples
672+ ///
673+ /// ```
674+ /// # use serde::{Serialize, Deserialize};
675+ /// # use meilisearch_sdk::{client::*, indexes::*, search::*};
676+ /// #
677+ /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
678+ /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
679+ /// #
680+ /// #[derive(Serialize)]
681+ /// struct Movie {
682+ /// name: String,
683+ /// genre: String,
684+ /// }
685+ /// # futures::executor::block_on(async move {
686+ /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY));
687+ /// let movies = client.index("execute_query");
688+ ///
689+ /// // add some documents
690+ /// # movies.add_or_replace(&[Movie{name:String::from("Interstellar"), genre:String::from("scifi")},Movie{name:String::from("Inception"), genre:String::from("drama")}], Some("name")).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
691+ /// # movies.set_filterable_attributes(["genre"]).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
692+ ///
693+ /// let query = FacetSearchQuery::new(&movies, "genre").with_facet_query("scifi").build();
694+ /// let res = movies.execute_facet_query(&query).await.unwrap();
695+ ///
696+ /// assert!(res.facet_hits.len() > 0);
697+ /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
698+ /// # });
699+ /// ```
700+ ///
701+ /// ```
702+ /// # use meilisearch_sdk::{Client, SearchQuery, Index};
703+ /// #
704+ /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
705+ /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
706+ /// #
707+ /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY));
708+ /// # let index = client.index("facet_search_query_builder_build");
709+ /// let query = index.facet_search("kind")
710+ /// .with_facet_query("space")
711+ /// .build(); // you can also execute() instead of build()
712+ /// ```
713+
714+ #[ derive( Debug , Serialize , Clone ) ]
715+ #[ serde( rename_all = "camelCase" ) ]
716+ pub struct FacetSearchQuery < ' a > {
717+ #[ serde( skip_serializing) ]
718+ index : & ' a Index ,
719+ /// The facet name to search values on.
720+ pub facet_name : & ' a str ,
721+ /// The search query for the facet values.
722+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
723+ pub facet_query : Option < & ' a str > ,
724+ /// The text that will be searched for among the documents.
725+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
726+ #[ serde( rename = "q" ) ]
727+ pub search_query : Option < & ' a str > ,
728+ /// Filter applied to documents.
729+ ///
730+ /// Read the [dedicated guide](https://www.meilisearch.com/docs/learn/advanced/filtering) to learn the syntax.
731+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
732+ pub filter : Option < Filter < ' a > > ,
733+ /// Defines the strategy on how to handle search queries containing multiple words.
734+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
735+ pub matching_strategy : Option < MatchingStrategies > ,
736+ }
737+
738+ #[ allow( missing_docs) ]
739+ impl < ' a > FacetSearchQuery < ' a > {
740+ pub fn new ( index : & ' a Index , facet_name : & ' a str ) -> FacetSearchQuery < ' a > {
741+ FacetSearchQuery {
742+ index,
743+ facet_name,
744+ facet_query : None ,
745+ search_query : None ,
746+ filter : None ,
747+ matching_strategy : None ,
748+ }
749+ }
750+
751+ pub fn with_facet_query < ' b > (
752+ & ' b mut self ,
753+ facet_query : & ' a str ,
754+ ) -> & ' b mut FacetSearchQuery < ' a > {
755+ self . facet_query = Some ( facet_query) ;
756+ self
757+ }
758+
759+ pub fn with_search_query < ' b > (
760+ & ' b mut self ,
761+ search_query : & ' a str ,
762+ ) -> & ' b mut FacetSearchQuery < ' a > {
763+ self . search_query = Some ( search_query) ;
764+ self
765+ }
766+
767+ pub fn with_filter < ' b > ( & ' b mut self , filter : & ' a str ) -> & ' b mut FacetSearchQuery < ' a > {
768+ self . filter = Some ( Filter :: new ( Either :: Left ( filter) ) ) ;
769+ self
770+ }
771+
772+ pub fn with_array_filter < ' b > (
773+ & ' b mut self ,
774+ filter : Vec < & ' a str > ,
775+ ) -> & ' b mut FacetSearchQuery < ' a > {
776+ self . filter = Some ( Filter :: new ( Either :: Right ( filter) ) ) ;
777+ self
778+ }
779+
780+ pub fn with_matching_strategy < ' b > (
781+ & ' b mut self ,
782+ matching_strategy : MatchingStrategies ,
783+ ) -> & ' b mut FacetSearchQuery < ' a > {
784+ self . matching_strategy = Some ( matching_strategy) ;
785+ self
786+ }
787+
788+ pub fn build ( & mut self ) -> FacetSearchQuery < ' a > {
789+ self . clone ( )
790+ }
791+
792+ pub async fn execute ( & ' a self ) -> Result < FacetSearchResponse , Error > {
793+ self . index . execute_facet_query ( self ) . await
794+ }
795+ }
796+
797+ #[ derive( Debug , Deserialize ) ]
798+ #[ serde( rename_all = "camelCase" ) ]
799+ pub struct FacetHit {
800+ pub value : String ,
801+ pub count : usize ,
802+ }
803+
804+ #[ derive( Debug , Deserialize ) ]
805+ #[ serde( rename_all = "camelCase" ) ]
806+ pub struct FacetSearchResponse {
807+ pub facet_hits : Vec < FacetHit > ,
808+ pub facet_query : Option < String > ,
809+ pub processing_time_ms : usize ,
810+ }
811+
665812#[ cfg( test) ]
666813mod tests {
667814 use crate :: {
@@ -1307,4 +1454,118 @@ mod tests {
13071454
13081455 Ok ( ( ) )
13091456 }
1457+
1458+ #[ meilisearch_test]
1459+ async fn test_facet_search_base ( client : Client , index : Index ) -> Result < ( ) , Error > {
1460+ setup_test_index ( & client, & index) . await ?;
1461+ let res = index. facet_search ( "kind" ) . execute ( ) . await ?;
1462+ assert_eq ! ( res. facet_hits. len( ) , 2 ) ;
1463+ Ok ( ( ) )
1464+ }
1465+
1466+ #[ meilisearch_test]
1467+ async fn test_facet_search_with_facet_query ( client : Client , index : Index ) -> Result < ( ) , Error > {
1468+ setup_test_index ( & client, & index) . await ?;
1469+ let res = index
1470+ . facet_search ( "kind" )
1471+ . with_facet_query ( "title" )
1472+ . execute ( )
1473+ . await ?;
1474+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1475+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1476+ assert_eq ! ( res. facet_hits[ 0 ] . count, 8 ) ;
1477+ Ok ( ( ) )
1478+ }
1479+
1480+ #[ meilisearch_test]
1481+ async fn test_facet_search_with_search_query (
1482+ client : Client ,
1483+ index : Index ,
1484+ ) -> Result < ( ) , Error > {
1485+ setup_test_index ( & client, & index) . await ?;
1486+ let res = index
1487+ . facet_search ( "kind" )
1488+ . with_search_query ( "Harry Potter" )
1489+ . execute ( )
1490+ . await ?;
1491+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1492+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1493+ assert_eq ! ( res. facet_hits[ 0 ] . count, 7 ) ;
1494+ Ok ( ( ) )
1495+ }
1496+
1497+ #[ meilisearch_test]
1498+ async fn test_facet_search_with_filter ( client : Client , index : Index ) -> Result < ( ) , Error > {
1499+ setup_test_index ( & client, & index) . await ?;
1500+ let res = index
1501+ . facet_search ( "kind" )
1502+ . with_filter ( "value = \" The Social Network\" " )
1503+ . execute ( )
1504+ . await ?;
1505+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1506+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1507+ assert_eq ! ( res. facet_hits[ 0 ] . count, 1 ) ;
1508+
1509+ let res = index
1510+ . facet_search ( "kind" )
1511+ . with_filter ( "NOT value = \" The Social Network\" " )
1512+ . execute ( )
1513+ . await ?;
1514+ assert_eq ! ( res. facet_hits. len( ) , 2 ) ;
1515+ Ok ( ( ) )
1516+ }
1517+
1518+ #[ meilisearch_test]
1519+ async fn test_facet_search_with_array_filter (
1520+ client : Client ,
1521+ index : Index ,
1522+ ) -> Result < ( ) , Error > {
1523+ setup_test_index ( & client, & index) . await ?;
1524+ let res = index
1525+ . facet_search ( "kind" )
1526+ . with_array_filter ( vec ! [
1527+ "value = \" The Social Network\" " ,
1528+ "value = \" The Social Network\" " ,
1529+ ] )
1530+ . execute ( )
1531+ . await ?;
1532+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1533+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1534+ assert_eq ! ( res. facet_hits[ 0 ] . count, 1 ) ;
1535+ Ok ( ( ) )
1536+ }
1537+
1538+ #[ meilisearch_test]
1539+ async fn test_facet_search_with_matching_strategy_all (
1540+ client : Client ,
1541+ index : Index ,
1542+ ) -> Result < ( ) , Error > {
1543+ setup_test_index ( & client, & index) . await ?;
1544+ let res = index
1545+ . facet_search ( "kind" )
1546+ . with_search_query ( "Harry Styles" )
1547+ . with_matching_strategy ( MatchingStrategies :: ALL )
1548+ . execute ( )
1549+ . await ?;
1550+ assert_eq ! ( res. facet_hits. len( ) , 0 ) ;
1551+ Ok ( ( ) )
1552+ }
1553+
1554+ #[ meilisearch_test]
1555+ async fn test_facet_search_with_matching_strategy_last (
1556+ client : Client ,
1557+ index : Index ,
1558+ ) -> Result < ( ) , Error > {
1559+ setup_test_index ( & client, & index) . await ?;
1560+ let res = index
1561+ . facet_search ( "kind" )
1562+ . with_search_query ( "Harry Styles" )
1563+ . with_matching_strategy ( MatchingStrategies :: LAST )
1564+ . execute ( )
1565+ . await ?;
1566+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1567+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1568+ assert_eq ! ( res. facet_hits[ 0 ] . count, 7 ) ;
1569+ Ok ( ( ) )
1570+ }
13101571}
0 commit comments