@@ -601,6 +601,153 @@ pub struct MultiSearchResponse<T> {
601
601
pub results : Vec < SearchResults < T > > ,
602
602
}
603
603
604
+ /// A struct representing a facet-search query.
605
+ ///
606
+ /// You can add search parameters using the builder syntax.
607
+ ///
608
+ /// See [this page](https://www.meilisearch.com/docs/reference/api/facet_search) for the official list and description of all parameters.
609
+ ///
610
+ /// # Examples
611
+ ///
612
+ /// ```
613
+ /// # use serde::{Serialize, Deserialize};
614
+ /// # use meilisearch_sdk::{client::*, indexes::*, search::*};
615
+ /// #
616
+ /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
617
+ /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
618
+ /// #
619
+ /// #[derive(Serialize)]
620
+ /// struct Movie {
621
+ /// name: String,
622
+ /// genre: String,
623
+ /// }
624
+ /// # futures::executor::block_on(async move {
625
+ /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY));
626
+ /// let movies = client.index("execute_query");
627
+ ///
628
+ /// // add some documents
629
+ /// # 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();
630
+ /// # movies.set_filterable_attributes(["genre"]).await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
631
+ ///
632
+ /// let query = FacetSearchQuery::new(&movies, "genre").with_facet_query("scifi").build();
633
+ /// let res = movies.execute_facet_query(&query).await.unwrap();
634
+ ///
635
+ /// assert!(res.facet_hits.len() > 0);
636
+ /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
637
+ /// # });
638
+ /// ```
639
+ ///
640
+ /// ```
641
+ /// # use meilisearch_sdk::{Client, SearchQuery, Index};
642
+ /// #
643
+ /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
644
+ /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
645
+ /// #
646
+ /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY));
647
+ /// # let index = client.index("facet_search_query_builder_build");
648
+ /// let query = index.facet_search("kind")
649
+ /// .with_facet_query("space")
650
+ /// .build(); // you can also execute() instead of build()
651
+ /// ```
652
+
653
+ #[ derive( Debug , Serialize , Clone ) ]
654
+ #[ serde( rename_all = "camelCase" ) ]
655
+ pub struct FacetSearchQuery < ' a > {
656
+ #[ serde( skip_serializing) ]
657
+ index : & ' a Index ,
658
+ /// The facet name to search values on.
659
+ pub facet_name : & ' a str ,
660
+ /// The search query for the facet values.
661
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
662
+ pub facet_query : Option < & ' a str > ,
663
+ /// The text that will be searched for among the documents.
664
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
665
+ #[ serde( rename = "q" ) ]
666
+ pub search_query : Option < & ' a str > ,
667
+ /// Filter applied to documents.
668
+ ///
669
+ /// Read the [dedicated guide](https://www.meilisearch.com/docs/learn/advanced/filtering) to learn the syntax.
670
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
671
+ pub filter : Option < Filter < ' a > > ,
672
+ /// Defines the strategy on how to handle search queries containing multiple words.
673
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
674
+ pub matching_strategy : Option < MatchingStrategies > ,
675
+ }
676
+
677
+ #[ allow( missing_docs) ]
678
+ impl < ' a > FacetSearchQuery < ' a > {
679
+ pub fn new ( index : & ' a Index , facet_name : & ' a str ) -> FacetSearchQuery < ' a > {
680
+ FacetSearchQuery {
681
+ index,
682
+ facet_name,
683
+ facet_query : None ,
684
+ search_query : None ,
685
+ filter : None ,
686
+ matching_strategy : None ,
687
+ }
688
+ }
689
+
690
+ pub fn with_facet_query < ' b > (
691
+ & ' b mut self ,
692
+ facet_query : & ' a str ,
693
+ ) -> & ' b mut FacetSearchQuery < ' a > {
694
+ self . facet_query = Some ( facet_query) ;
695
+ self
696
+ }
697
+
698
+ pub fn with_search_query < ' b > (
699
+ & ' b mut self ,
700
+ search_query : & ' a str ,
701
+ ) -> & ' b mut FacetSearchQuery < ' a > {
702
+ self . search_query = Some ( search_query) ;
703
+ self
704
+ }
705
+
706
+ pub fn with_filter < ' b > ( & ' b mut self , filter : & ' a str ) -> & ' b mut FacetSearchQuery < ' a > {
707
+ self . filter = Some ( Filter :: new ( Either :: Left ( filter) ) ) ;
708
+ self
709
+ }
710
+
711
+ pub fn with_array_filter < ' b > (
712
+ & ' b mut self ,
713
+ filter : Vec < & ' a str > ,
714
+ ) -> & ' b mut FacetSearchQuery < ' a > {
715
+ self . filter = Some ( Filter :: new ( Either :: Right ( filter) ) ) ;
716
+ self
717
+ }
718
+
719
+ pub fn with_matching_strategy < ' b > (
720
+ & ' b mut self ,
721
+ matching_strategy : MatchingStrategies ,
722
+ ) -> & ' b mut FacetSearchQuery < ' a > {
723
+ self . matching_strategy = Some ( matching_strategy) ;
724
+ self
725
+ }
726
+
727
+ pub fn build ( & mut self ) -> FacetSearchQuery < ' a > {
728
+ self . clone ( )
729
+ }
730
+
731
+ pub async fn execute ( & ' a self ) -> Result < FacetSearchResponse , Error > {
732
+ self . index . execute_facet_query ( self ) . await
733
+ }
734
+ }
735
+
736
+ #[ derive( Debug , Deserialize ) ]
737
+ #[ serde( rename_all = "camelCase" ) ]
738
+ pub struct FacetHit {
739
+ pub value : String ,
740
+ pub count : usize ,
741
+ }
742
+
743
+ #[ derive( Debug , Deserialize ) ]
744
+ #[ serde( rename_all = "camelCase" ) ]
745
+ pub struct FacetSearchResponse {
746
+ pub facet_hits : Vec < FacetHit > ,
747
+ pub facet_query : Option < String > ,
748
+ pub processing_time_ms : usize ,
749
+ }
750
+
604
751
#[ cfg( test) ]
605
752
mod tests {
606
753
use crate :: {
@@ -1174,4 +1321,118 @@ mod tests {
1174
1321
1175
1322
Ok ( ( ) )
1176
1323
}
1324
+
1325
+ #[ meilisearch_test]
1326
+ async fn test_facet_search_base ( client : Client , index : Index ) -> Result < ( ) , Error > {
1327
+ setup_test_index ( & client, & index) . await ?;
1328
+ let res = index. facet_search ( "kind" ) . execute ( ) . await ?;
1329
+ assert_eq ! ( res. facet_hits. len( ) , 2 ) ;
1330
+ Ok ( ( ) )
1331
+ }
1332
+
1333
+ #[ meilisearch_test]
1334
+ async fn test_facet_search_with_facet_query ( client : Client , index : Index ) -> Result < ( ) , Error > {
1335
+ setup_test_index ( & client, & index) . await ?;
1336
+ let res = index
1337
+ . facet_search ( "kind" )
1338
+ . with_facet_query ( "title" )
1339
+ . execute ( )
1340
+ . await ?;
1341
+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1342
+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1343
+ assert_eq ! ( res. facet_hits[ 0 ] . count, 8 ) ;
1344
+ Ok ( ( ) )
1345
+ }
1346
+
1347
+ #[ meilisearch_test]
1348
+ async fn test_facet_search_with_search_query (
1349
+ client : Client ,
1350
+ index : Index ,
1351
+ ) -> Result < ( ) , Error > {
1352
+ setup_test_index ( & client, & index) . await ?;
1353
+ let res = index
1354
+ . facet_search ( "kind" )
1355
+ . with_search_query ( "Harry Potter" )
1356
+ . execute ( )
1357
+ . await ?;
1358
+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1359
+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1360
+ assert_eq ! ( res. facet_hits[ 0 ] . count, 7 ) ;
1361
+ Ok ( ( ) )
1362
+ }
1363
+
1364
+ #[ meilisearch_test]
1365
+ async fn test_facet_search_with_filter ( client : Client , index : Index ) -> Result < ( ) , Error > {
1366
+ setup_test_index ( & client, & index) . await ?;
1367
+ let res = index
1368
+ . facet_search ( "kind" )
1369
+ . with_filter ( "value = \" The Social Network\" " )
1370
+ . execute ( )
1371
+ . await ?;
1372
+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1373
+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1374
+ assert_eq ! ( res. facet_hits[ 0 ] . count, 1 ) ;
1375
+
1376
+ let res = index
1377
+ . facet_search ( "kind" )
1378
+ . with_filter ( "NOT value = \" The Social Network\" " )
1379
+ . execute ( )
1380
+ . await ?;
1381
+ assert_eq ! ( res. facet_hits. len( ) , 2 ) ;
1382
+ Ok ( ( ) )
1383
+ }
1384
+
1385
+ #[ meilisearch_test]
1386
+ async fn test_facet_search_with_array_filter (
1387
+ client : Client ,
1388
+ index : Index ,
1389
+ ) -> Result < ( ) , Error > {
1390
+ setup_test_index ( & client, & index) . await ?;
1391
+ let res = index
1392
+ . facet_search ( "kind" )
1393
+ . with_array_filter ( vec ! [
1394
+ "value = \" The Social Network\" " ,
1395
+ "value = \" The Social Network\" " ,
1396
+ ] )
1397
+ . execute ( )
1398
+ . await ?;
1399
+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1400
+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1401
+ assert_eq ! ( res. facet_hits[ 0 ] . count, 1 ) ;
1402
+ Ok ( ( ) )
1403
+ }
1404
+
1405
+ #[ meilisearch_test]
1406
+ async fn test_facet_search_with_matching_strategy_all (
1407
+ client : Client ,
1408
+ index : Index ,
1409
+ ) -> Result < ( ) , Error > {
1410
+ setup_test_index ( & client, & index) . await ?;
1411
+ let res = index
1412
+ . facet_search ( "kind" )
1413
+ . with_search_query ( "Harry Styles" )
1414
+ . with_matching_strategy ( MatchingStrategies :: ALL )
1415
+ . execute ( )
1416
+ . await ?;
1417
+ assert_eq ! ( res. facet_hits. len( ) , 0 ) ;
1418
+ Ok ( ( ) )
1419
+ }
1420
+
1421
+ #[ meilisearch_test]
1422
+ async fn test_facet_search_with_matching_strategy_last (
1423
+ client : Client ,
1424
+ index : Index ,
1425
+ ) -> Result < ( ) , Error > {
1426
+ setup_test_index ( & client, & index) . await ?;
1427
+ let res = index
1428
+ . facet_search ( "kind" )
1429
+ . with_search_query ( "Harry Styles" )
1430
+ . with_matching_strategy ( MatchingStrategies :: LAST )
1431
+ . execute ( )
1432
+ . await ?;
1433
+ assert_eq ! ( res. facet_hits. len( ) , 1 ) ;
1434
+ assert_eq ! ( res. facet_hits[ 0 ] . value, "title" ) ;
1435
+ assert_eq ! ( res. facet_hits[ 0 ] . count, 7 ) ;
1436
+ Ok ( ( ) )
1437
+ }
1177
1438
}
0 commit comments