Skip to content

Allow users to customize facet value sort behaviour #676

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 10, 2025
Merged
20 changes: 20 additions & 0 deletions .code-samples.meilisearch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Every example written here will be automatically fetched by
# the documentation on build
# You can read more on https://github.com/meilisearch/documentation/tree/main/learn
# See the original at https://github.com/meilisearch/documentation/blob/main/.code-samples.meilisearch.yaml
---
synonyms_guide_1: |-
let mut synonyms = std::collections::HashMap::new();
Expand Down Expand Up @@ -581,8 +582,12 @@ get_faceting_settings_1: |-
.await
.unwrap();
update_faceting_settings_1: |-
let mut facet_sort_setting = BTreeMap::new();
facet_sort_setting.insert(String::from("*"), FacetSortValue::Alpha);
facet_sort_setting.insert(String::from("genres"), FacetSortValue::Count);
let mut faceting = FacetingSettings {
max_values_per_facet: 2,
sort_facet_values_by: Some(facet_sort_setting),
};

let task: TaskInfo = client
Expand Down Expand Up @@ -1265,8 +1270,11 @@ getting_started_sorting: |-
.await
.unwrap();
getting_started_faceting: |-
let mut facet_sort_setting = BTreeMap::new();
facet_sort_setting.insert("*".to_string(), FacetSortValue::Count);
let mut faceting = FacetingSettings {
max_values_per_facet: 2,
sort_facet_values_by: Some(facet_sort_setting),
};

let task: TaskInfo = client
Expand Down Expand Up @@ -1671,6 +1679,18 @@ facet_search_1: |-
.execute()
.await
.unwrap();
facet_search_2: |-
let mut facet_sort_setting = BTreeMap::new();
facet_sort_setting.insert("genres".to_string(), FacetSortValue::Count);
let faceting = FacetingSettings {
max_values_per_facet: 100,
sort_facet_values_by: Some(facet_sort_setting),
};

let res = client.index("books")
.set_faceting(&faceting)
.await
.unwrap();
facet_search_3: |-
let res = client.index("books")
.facet_search("genres")
Expand Down
141 changes: 127 additions & 14 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
task_info::TaskInfo,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Copy)]
#[serde(rename_all = "camelCase")]
Expand All @@ -30,10 +30,21 @@ pub struct TypoToleranceSettings {
pub min_word_size_for_typos: Option<MinWordSizeForTypos>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq, Copy)]
#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum FacetSortValue {
Alpha,
Count,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FacetingSettings {
/// Maximum number of facet values returned for each facet. Values are sorted in ascending lexicographical order
pub max_values_per_facet: usize,
/// Customize facet order to sort by descending value count (count) or ascending alphanumeric order (alpha)
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_facet_values_by: Option<BTreeMap<String, FacetSortValue>>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)]
Expand Down Expand Up @@ -278,9 +289,32 @@ impl Settings {
}

#[must_use]
pub fn with_faceting(self, faceting: &FacetingSettings) -> Settings {
pub fn with_faceting(self, faceting: FacetingSettings) -> Settings {
Settings {
faceting: Some(*faceting),
faceting: Some(faceting),
..self
}
}

#[must_use]
pub fn with_max_values_per_facet(mut self, max_values_per_facet: usize) -> Settings {
let mut faceting = self.faceting.take().unwrap_or_default();
faceting.max_values_per_facet = max_values_per_facet;
Settings {
faceting: Some(faceting),
..self
}
}

#[must_use]
pub fn with_sort_facet_values_by(
mut self,
sort_facet_values_by: BTreeMap<String, FacetSortValue>,
) -> Settings {
let mut faceting = self.faceting.take().unwrap_or_default();
faceting.sort_facet_values_by = Some(sort_facet_values_by);
Settings {
faceting: Some(faceting),
..self
}
}
Expand Down Expand Up @@ -1397,6 +1431,7 @@ impl<Http: HttpClient> Index<Http> {
///
/// let mut faceting = FacetingSettings {
/// max_values_per_facet: 12,
/// sort_facet_values_by: None,
/// };
///
/// let task = index.set_faceting(&faceting).await.unwrap();
Expand Down Expand Up @@ -2314,54 +2349,132 @@ mod tests {

#[meilisearch_test]
async fn test_set_faceting_settings(client: Client, index: Index) {
let faceting = FacetingSettings {
let settings = Settings::new().with_max_values_per_facet(5);

let task_info = index.set_settings(&settings).await.unwrap();
client.wait_for_task(task_info, None, None).await.unwrap();

let res = index.get_faceting().await.unwrap();

let mut expected_facet_sort_setting = BTreeMap::new();
expected_facet_sort_setting.insert("*".to_string(), FacetSortValue::Alpha);
let expected_faceting = FacetingSettings {
max_values_per_facet: 5,
sort_facet_values_by: Some(expected_facet_sort_setting),
};
let settings = Settings::new().with_faceting(&faceting);

assert_eq!(expected_faceting, res);
}

#[meilisearch_test]
async fn test_set_faceting_settings_with_sort_values(client: Client, index: Index) {
let mut req_facet_sort_setting = BTreeMap::new();
req_facet_sort_setting.insert("genres".to_string(), FacetSortValue::Count);
let req_faceting = FacetingSettings {
max_values_per_facet: 5,
sort_facet_values_by: Some(req_facet_sort_setting),
};
let settings = Settings::new().with_faceting(req_faceting.clone());

let task_info = index.set_settings(&settings).await.unwrap();
client.wait_for_task(task_info, None, None).await.unwrap();

let res = index.get_faceting().await.unwrap();

assert_eq!(faceting, res);
let mut expected_facet_sort_setting = BTreeMap::new();
expected_facet_sort_setting.insert("*".to_string(), FacetSortValue::Alpha);
expected_facet_sort_setting.insert("genres".to_string(), FacetSortValue::Count);
let expected_faceting = FacetingSettings {
max_values_per_facet: req_faceting.max_values_per_facet,
sort_facet_values_by: Some(expected_facet_sort_setting),
};

assert_eq!(expected_faceting, res);
}

#[meilisearch_test]
async fn test_get_faceting(index: Index) {
let faceting = FacetingSettings {
let req_faceting = FacetingSettings {
max_values_per_facet: 100,
sort_facet_values_by: None,
};

let res = index.get_faceting().await.unwrap();

assert_eq!(faceting, res);
let mut expected_facet_sort_setting = BTreeMap::new();
expected_facet_sort_setting.insert("*".to_string(), FacetSortValue::Alpha);
let expected_faceting = FacetingSettings {
max_values_per_facet: req_faceting.max_values_per_facet,
sort_facet_values_by: Some(expected_facet_sort_setting),
};

assert_eq!(expected_faceting, res);
}

#[meilisearch_test]
async fn test_set_faceting(client: Client, index: Index) {
let faceting = FacetingSettings {
let req_faceting = FacetingSettings {
max_values_per_facet: 5,
sort_facet_values_by: None,
};
let task_info = index.set_faceting(&req_faceting).await.unwrap();
client.wait_for_task(task_info, None, None).await.unwrap();

let res = index.get_faceting().await.unwrap();

let mut expected_facet_sort_setting = BTreeMap::new();
expected_facet_sort_setting.insert("*".to_string(), FacetSortValue::Alpha);
let expected_faceting = FacetingSettings {
max_values_per_facet: req_faceting.max_values_per_facet,
sort_facet_values_by: Some(expected_facet_sort_setting),
};

assert_eq!(expected_faceting, res);
}

#[meilisearch_test]
async fn test_set_faceting_with_sort_values(client: Client, index: Index) {
let mut req_facet_sort_setting = BTreeMap::new();
req_facet_sort_setting.insert("genres".to_string(), FacetSortValue::Count);
let req_faceting = FacetingSettings {
max_values_per_facet: 5,
sort_facet_values_by: Some(req_facet_sort_setting),
};
let task_info = index.set_faceting(&faceting).await.unwrap();
let task_info = index.set_faceting(&req_faceting).await.unwrap();
client.wait_for_task(task_info, None, None).await.unwrap();

let res = index.get_faceting().await.unwrap();

assert_eq!(faceting, res);
let mut expected_facet_sort_setting = BTreeMap::new();
expected_facet_sort_setting.insert("*".to_string(), FacetSortValue::Alpha);
expected_facet_sort_setting.insert("genres".to_string(), FacetSortValue::Count);
let expected_faceting = FacetingSettings {
max_values_per_facet: req_faceting.max_values_per_facet,
sort_facet_values_by: Some(expected_facet_sort_setting),
};

assert_eq!(expected_faceting, res);
}

#[meilisearch_test]
async fn test_reset_faceting(client: Client, index: Index) {
let task_info = index.reset_faceting().await.unwrap();
client.wait_for_task(task_info, None, None).await.unwrap();
let faceting = FacetingSettings {
let req_faceting = FacetingSettings {
max_values_per_facet: 100,
sort_facet_values_by: None,
};

let res = index.get_faceting().await.unwrap();

assert_eq!(faceting, res);
let mut expected_facet_sort_setting = BTreeMap::new();
expected_facet_sort_setting.insert("*".to_string(), FacetSortValue::Alpha);
let expected_faceting = FacetingSettings {
max_values_per_facet: req_faceting.max_values_per_facet,
sort_facet_values_by: Some(expected_facet_sort_setting),
};

assert_eq!(expected_faceting, res);
}

#[meilisearch_test]
Expand Down