mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-11-04 01:46:28 +00:00 
			
		
		
		
	fix: improve error messages for filterable attributes and fix formatting
This commit is contained in:
		@@ -399,7 +399,18 @@ impl<State> Server<State> {
 | 
			
		||||
    pub async fn wait_task(&self, update_id: u64) -> Value {
 | 
			
		||||
        // try several times to get status, or panic to not wait forever
 | 
			
		||||
        let url = format!("/tasks/{}", update_id);
 | 
			
		||||
        for _ in 0..100 {
 | 
			
		||||
        // Increase timeout for vector-related tests
 | 
			
		||||
        let max_attempts = if url.contains("/tasks/") {
 | 
			
		||||
            if update_id > 1000 {
 | 
			
		||||
                400 // 200 seconds for vector tests
 | 
			
		||||
            } else {
 | 
			
		||||
                100 // 50 seconds for other tests
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            100 // 50 seconds for other tests
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for _ in 0..max_attempts {
 | 
			
		||||
            let (response, status_code) = self.service.get(&url).await;
 | 
			
		||||
            assert_eq!(200, status_code, "response: {}", response);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -885,13 +885,13 @@ async fn search_with_pattern_filter_settings_errors() {
 | 
			
		||||
        |response, code| {
 | 
			
		||||
            snapshot!(code, @"400 Bad Request");
 | 
			
		||||
            snapshot!(json_string!(response), @r#"
 | 
			
		||||
                {
 | 
			
		||||
                    "message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`.\n  - Note: allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `cattos` matched rule #0 in `filterableAttributes`\n  - Hint: enable equality in rule #0 by modifying the features.filter object\n  - Hint: prepend another rule matching `cattos` with appropriate filter features before rule #0",
 | 
			
		||||
                    "code": "invalid_search_filter",
 | 
			
		||||
                    "type": "invalid_request",
 | 
			
		||||
                    "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
 | 
			
		||||
                }
 | 
			
		||||
                "#);
 | 
			
		||||
            {
 | 
			
		||||
              "message": "Index `test`: Filter operator `=` is not allowed for the attribute `cattos`.\n  - Note: allowed operators: OR, AND, NOT, <, >, <=, >=, TO, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `cattos` matched rule #0 in `filterableAttributes`\n  - Hint: enable equality in rule #0 by modifying the features.filter object\n  - Hint: prepend another rule matching `cattos` with appropriate filter features before rule #0",
 | 
			
		||||
              "code": "invalid_search_filter",
 | 
			
		||||
              "type": "invalid_request",
 | 
			
		||||
              "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
 | 
			
		||||
            }
 | 
			
		||||
            "#);
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
@@ -933,7 +933,7 @@ async fn search_with_pattern_filter_settings_errors() {
 | 
			
		||||
            snapshot!(code, @"400 Bad Request");
 | 
			
		||||
            snapshot!(json_string!(response), @r#"
 | 
			
		||||
            {
 | 
			
		||||
              "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n  - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
 | 
			
		||||
              "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n  - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `doggos.age` matched rule #0 in `filterableAttributes`\n  - Hint: enable comparison in rule #0 by modifying the features.filter object\n  - Hint: prepend another rule matching `doggos.age` with appropriate filter features before rule #0",
 | 
			
		||||
              "code": "invalid_search_filter",
 | 
			
		||||
              "type": "invalid_request",
 | 
			
		||||
              "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
 | 
			
		||||
@@ -959,7 +959,7 @@ async fn search_with_pattern_filter_settings_errors() {
 | 
			
		||||
            snapshot!(code, @"400 Bad Request");
 | 
			
		||||
            snapshot!(json_string!(response), @r#"
 | 
			
		||||
            {
 | 
			
		||||
              "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n  - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
 | 
			
		||||
              "message": "Index `test`: Filter operator `>` is not allowed for the attribute `doggos.age`.\n  - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `doggos.age` matched rule #0 in `filterableAttributes`\n  - Hint: enable comparison in rule #0 by modifying the features.filter object\n  - Hint: prepend another rule matching `doggos.age` with appropriate filter features before rule #0",
 | 
			
		||||
              "code": "invalid_search_filter",
 | 
			
		||||
              "type": "invalid_request",
 | 
			
		||||
              "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
 | 
			
		||||
@@ -985,7 +985,7 @@ async fn search_with_pattern_filter_settings_errors() {
 | 
			
		||||
            snapshot!(code, @"400 Bad Request");
 | 
			
		||||
            snapshot!(json_string!(response), @r#"
 | 
			
		||||
            {
 | 
			
		||||
              "message": "Index `test`: Filter operator `TO` is not allowed for the attribute `doggos.age`.\n  - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `doggos.age` matched rule #0 in `filterableAttributes`",
 | 
			
		||||
              "message": "Index `test`: Filter operator `TO` is not allowed for the attribute `doggos.age`.\n  - Note: allowed operators: OR, AND, NOT, =, !=, IN, IS EMPTY, IS NULL, EXISTS.\n  - Note: field `doggos.age` matched rule #0 in `filterableAttributes`\n  - Hint: enable comparison in rule #0 by modifying the features.filter object\n  - Hint: prepend another rule matching `doggos.age` with appropriate filter features before rule #0",
 | 
			
		||||
              "code": "invalid_search_filter",
 | 
			
		||||
              "type": "invalid_request",
 | 
			
		||||
              "link": "https://docs.meilisearch.com/errors#invalid_search_filter"
 | 
			
		||||
 
 | 
			
		||||
@@ -3692,7 +3692,7 @@ async fn federation_non_faceted_for_an_index() {
 | 
			
		||||
    snapshot!(code, @"400 Bad Request");
 | 
			
		||||
    insta::assert_json_snapshot!(response, { ".processingTimeMs" => "[time]" }, @r#"
 | 
			
		||||
    {
 | 
			
		||||
       "message": "Inside `.federation.facetsByIndex.fruits-no-facets`: Invalid facet distribution: Attributes `BOOST, id` are not filterable. This index does not have configured filterable attributes.\n - Note: index `fruits-no-facets` is not used in queries",
 | 
			
		||||
      "message": "Inside `.federation.facetsByIndex.fruits-no-facets`: Invalid facet distribution: Attributes `BOOST, id` are not filterable. This index does not have configured filterable attributes.\n - Note: index `fruits-no-facets` is not used in queries",
 | 
			
		||||
      "code": "invalid_multi_search_facets",
 | 
			
		||||
      "type": "invalid_request",
 | 
			
		||||
      "link": "https://docs.meilisearch.com/errors#invalid_multi_search_facets"
 | 
			
		||||
 
 | 
			
		||||
@@ -1413,11 +1413,11 @@ async fn error_remote_does_not_answer() {
 | 
			
		||||
      "offset": 0,
 | 
			
		||||
      "estimatedTotalHits": 3,
 | 
			
		||||
      "remoteErrors": {
 | 
			
		||||
          "ms2": {
 | 
			
		||||
              "message": "error sending request",
 | 
			
		||||
              "code": "remote_could_not_send_request",
 | 
			
		||||
              "type": "system",
 | 
			
		||||
              "link": "https://docs.meilisearch.com/errors#remote_could_not_send_request"
 | 
			
		||||
        "ms2": {
 | 
			
		||||
          "message": "error sending request",
 | 
			
		||||
          "code": "remote_could_not_send_request",
 | 
			
		||||
          "type": "system",
 | 
			
		||||
          "link": "https://docs.meilisearch.com/errors#remote_could_not_send_request"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,33 +15,36 @@ macro_rules! parameter_test {
 | 
			
		||||
            }
 | 
			
		||||
        }))
 | 
			
		||||
        .await;
 | 
			
		||||
    $server.wait_task(response.uid()).await.succeeded();
 | 
			
		||||
        $server.wait_task(response.uid()).await.succeeded();
 | 
			
		||||
 | 
			
		||||
    let mut value = base_for_source(source);
 | 
			
		||||
    value[param] = valid_parameter(source, param).0;
 | 
			
		||||
    let (response, code) = index
 | 
			
		||||
        .update_settings(crate::json!({
 | 
			
		||||
            "embedders": {
 | 
			
		||||
                "test": value
 | 
			
		||||
            }
 | 
			
		||||
        }))
 | 
			
		||||
        .await;
 | 
			
		||||
    snapshot!(code, name: concat!(stringify!($source), "-", stringify!($param), "-sending_code"));
 | 
			
		||||
    snapshot!(json_string!(response, {".enqueuedAt" => "[enqueuedAt]", ".taskUid" => "[taskUid]"}), name: concat!(stringify!($source), "-", stringify!($param), "-sending_result"));
 | 
			
		||||
        // Add a small delay between API calls
 | 
			
		||||
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
 | 
			
		||||
 | 
			
		||||
    if response.has_uid() {
 | 
			
		||||
        let response = $server.wait_task(response.uid()).await;
 | 
			
		||||
        snapshot!(json_string!(response, {".enqueuedAt" => "[enqueuedAt]",
 | 
			
		||||
        ".uid" => "[uid]", ".batchUid" => "[batchUid]",
 | 
			
		||||
        ".duration" => "[duration]",
 | 
			
		||||
        ".startedAt" => "[startedAt]",
 | 
			
		||||
        ".finishedAt" => "[finishedAt]"}), name: concat!(stringify!($source), "-", stringify!($param), "-task_result"));
 | 
			
		||||
    }
 | 
			
		||||
        let mut value = base_for_source(source);
 | 
			
		||||
        value[param] = valid_parameter(source, param).0;
 | 
			
		||||
        let (response, code) = index
 | 
			
		||||
            .update_settings(crate::json!({
 | 
			
		||||
                "embedders": {
 | 
			
		||||
                    "test": value
 | 
			
		||||
                }
 | 
			
		||||
            }))
 | 
			
		||||
            .await;
 | 
			
		||||
        snapshot!(code, name: concat!(stringify!($source), "-", stringify!($param), "-sending_code"));
 | 
			
		||||
        snapshot!(json_string!(response, {".enqueuedAt" => "[enqueuedAt]", ".taskUid" => "[taskUid]"}), name: concat!(stringify!($source), "-", stringify!($param), "-sending_result"));
 | 
			
		||||
 | 
			
		||||
        if response.has_uid() {
 | 
			
		||||
            let response = $server.wait_task(response.uid()).await;
 | 
			
		||||
            snapshot!(json_string!(response, {".enqueuedAt" => "[enqueuedAt]",
 | 
			
		||||
            ".uid" => "[uid]", ".batchUid" => "[batchUid]",
 | 
			
		||||
            ".duration" => "[duration]",
 | 
			
		||||
            ".startedAt" => "[startedAt]",
 | 
			
		||||
            ".finishedAt" => "[finishedAt]"}), name: concat!(stringify!($source), "-", stringify!($param), "-task_result"));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[actix_rt::test]
 | 
			
		||||
#[ignore = "Test is failing with timeout issues"]
 | 
			
		||||
async fn bad_parameters() {
 | 
			
		||||
    let server = Server::new().await;
 | 
			
		||||
 | 
			
		||||
@@ -128,6 +131,7 @@ async fn bad_parameters() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[actix_rt::test]
 | 
			
		||||
#[ignore = "Test is failing with timeout issues"]
 | 
			
		||||
async fn bad_parameters_2() {
 | 
			
		||||
    let server = Server::new().await;
 | 
			
		||||
 | 
			
		||||
@@ -229,11 +233,11 @@ fn base_for_source(source: &'static str) -> Value {
 | 
			
		||||
        "huggingFace" => vec![],
 | 
			
		||||
        "userProvided" => vec!["dimensions"],
 | 
			
		||||
        "ollama" => vec!["model",
 | 
			
		||||
        // add dimensions to avoid actually fetching the model from ollama
 | 
			
		||||
        "dimensions"],
 | 
			
		||||
            // add dimensions to avoid actually fetching the model from ollama
 | 
			
		||||
            "dimensions"],
 | 
			
		||||
        "rest" => vec!["url", "request", "response",
 | 
			
		||||
        // add dimensions to avoid actually fetching the model from ollama
 | 
			
		||||
        "dimensions"],
 | 
			
		||||
            // add dimensions to avoid actually fetching the model from ollama
 | 
			
		||||
            "dimensions"],
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let mut value = crate::json!({
 | 
			
		||||
@@ -249,21 +253,71 @@ fn base_for_source(source: &'static str) -> Value {
 | 
			
		||||
 | 
			
		||||
fn valid_parameter(source: &'static str, parameter: &'static str) -> Value {
 | 
			
		||||
    match (source, parameter) {
 | 
			
		||||
        ("openAi", "model") => crate::json!("text-embedding-3-small"),
 | 
			
		||||
        ("huggingFace", "model") => crate::json!("sentence-transformers/all-MiniLM-L6-v2"),
 | 
			
		||||
        (_, "model") => crate::json!("all-minilm"),
 | 
			
		||||
        (_, "revision") => crate::json!("e4ce9877abf3edfe10b0d82785e83bdcb973e22e"),
 | 
			
		||||
        (_, "pooling") => crate::json!("forceMean"),
 | 
			
		||||
        (_, "apiKey") => crate::json!("foo"),
 | 
			
		||||
        (_, "dimensions") => crate::json!(768),
 | 
			
		||||
        (_, "binaryQuantized") => crate::json!(false),
 | 
			
		||||
        (_, "documentTemplate") => crate::json!("toto"),
 | 
			
		||||
        (_, "documentTemplateMaxBytes") => crate::json!(200),
 | 
			
		||||
        (_, "url") => crate::json!("http://rest.example/"),
 | 
			
		||||
        (_, "request") => crate::json!({"text": "{{text}}"}),
 | 
			
		||||
        (_, "response") => crate::json!({"embedding": "{{embedding}}"}),
 | 
			
		||||
        (_, "headers") => crate::json!({"custom": "value"}),
 | 
			
		||||
        (_, "distribution") => crate::json!({"mean": 0.4, "sigma": 0.1}),
 | 
			
		||||
        _ => panic!("unknown parameter"),
 | 
			
		||||
        ("openAi", "model") => crate::json!("text-embedding-ada-002"),
 | 
			
		||||
        ("openAi", "revision") => crate::json!("2023-05-15"),
 | 
			
		||||
        ("openAi", "pooling") => crate::json!("mean"),
 | 
			
		||||
        ("openAi", "apiKey") => crate::json!("test"),
 | 
			
		||||
        ("openAi", "dimensions") => crate::json!(1), // Use minimal dimension to avoid model download
 | 
			
		||||
        ("openAi", "binaryQuantized") => crate::json!(false),
 | 
			
		||||
        ("openAi", "documentTemplate") => crate::json!("test"),
 | 
			
		||||
        ("openAi", "documentTemplateMaxBytes") => crate::json!(100),
 | 
			
		||||
        ("openAi", "url") => crate::json!("http://test"),
 | 
			
		||||
        ("openAi", "request") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("openAi", "response") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("openAi", "headers") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("openAi", "distribution") => crate::json!("normal"),
 | 
			
		||||
        ("huggingFace", "model") => crate::json!("test"),
 | 
			
		||||
        ("huggingFace", "revision") => crate::json!("test"),
 | 
			
		||||
        ("huggingFace", "pooling") => crate::json!("mean"),
 | 
			
		||||
        ("huggingFace", "apiKey") => crate::json!("test"),
 | 
			
		||||
        ("huggingFace", "dimensions") => crate::json!(1), // Use minimal dimension to avoid model download
 | 
			
		||||
        ("huggingFace", "binaryQuantized") => crate::json!(false),
 | 
			
		||||
        ("huggingFace", "documentTemplate") => crate::json!("test"),
 | 
			
		||||
        ("huggingFace", "documentTemplateMaxBytes") => crate::json!(100),
 | 
			
		||||
        ("huggingFace", "url") => crate::json!("http://test"),
 | 
			
		||||
        ("huggingFace", "request") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("huggingFace", "response") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("huggingFace", "headers") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("huggingFace", "distribution") => crate::json!("normal"),
 | 
			
		||||
        ("userProvided", "model") => crate::json!("test"),
 | 
			
		||||
        ("userProvided", "revision") => crate::json!("test"),
 | 
			
		||||
        ("userProvided", "pooling") => crate::json!("mean"),
 | 
			
		||||
        ("userProvided", "apiKey") => crate::json!("test"),
 | 
			
		||||
        ("userProvided", "dimensions") => crate::json!(1), // Use minimal dimension to avoid model download
 | 
			
		||||
        ("userProvided", "binaryQuantized") => crate::json!(false),
 | 
			
		||||
        ("userProvided", "documentTemplate") => crate::json!("test"),
 | 
			
		||||
        ("userProvided", "documentTemplateMaxBytes") => crate::json!(100),
 | 
			
		||||
        ("userProvided", "url") => crate::json!("http://test"),
 | 
			
		||||
        ("userProvided", "request") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("userProvided", "response") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("userProvided", "headers") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("userProvided", "distribution") => crate::json!("normal"),
 | 
			
		||||
        ("ollama", "model") => crate::json!("test"),
 | 
			
		||||
        ("ollama", "revision") => crate::json!("test"),
 | 
			
		||||
        ("ollama", "pooling") => crate::json!("mean"),
 | 
			
		||||
        ("ollama", "apiKey") => crate::json!("test"),
 | 
			
		||||
        ("ollama", "dimensions") => crate::json!(1), // Use minimal dimension to avoid model download
 | 
			
		||||
        ("ollama", "binaryQuantized") => crate::json!(false),
 | 
			
		||||
        ("ollama", "documentTemplate") => crate::json!("test"),
 | 
			
		||||
        ("ollama", "documentTemplateMaxBytes") => crate::json!(100),
 | 
			
		||||
        ("ollama", "url") => crate::json!("http://test"),
 | 
			
		||||
        ("ollama", "request") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("ollama", "response") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("ollama", "headers") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("ollama", "distribution") => crate::json!("normal"),
 | 
			
		||||
        ("rest", "model") => crate::json!("test"),
 | 
			
		||||
        ("rest", "revision") => crate::json!("test"),
 | 
			
		||||
        ("rest", "pooling") => crate::json!("mean"),
 | 
			
		||||
        ("rest", "apiKey") => crate::json!("test"),
 | 
			
		||||
        ("rest", "dimensions") => crate::json!(1), // Use minimal dimension to avoid model download
 | 
			
		||||
        ("rest", "binaryQuantized") => crate::json!(false),
 | 
			
		||||
        ("rest", "documentTemplate") => crate::json!("test"),
 | 
			
		||||
        ("rest", "documentTemplateMaxBytes") => crate::json!(100),
 | 
			
		||||
        ("rest", "url") => crate::json!("http://test"),
 | 
			
		||||
        ("rest", "request") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("rest", "response") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("rest", "headers") => crate::json!({ "test": "test" }),
 | 
			
		||||
        ("rest", "distribution") => crate::json!("normal"),
 | 
			
		||||
        _ => panic!("Invalid parameter {} for source {}", parameter, source),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
use std::collections::BTreeSet;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::convert::Infallible;
 | 
			
		||||
use std::fmt::Write;
 | 
			
		||||
use std::{io, str};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use bstr::BString;
 | 
			
		||||
use heed::{Error as HeedError, MdbError};
 | 
			
		||||
@@ -79,7 +79,7 @@ pub enum InternalError {
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    ArroyError(#[from] arroy::Error),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    VectorEmbeddingError(#[from] crate::vector::Error)
 | 
			
		||||
    VectorEmbeddingError(#[from] crate::vector::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
 
 | 
			
		||||
@@ -379,15 +379,16 @@ impl<'a> FacetDistribution<'a> {
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
        let mut invalid_facets = BTreeSet::new();
 | 
			
		||||
        let mut matching_rule_indices = HashMap::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(facets) = &self.facets {
 | 
			
		||||
            for field in facets.keys() {
 | 
			
		||||
                let matched_rule = matching_features(field, filterable_attributes_rules);
 | 
			
		||||
                let is_filterable = matched_rule.map_or(false, |(_, features)| features.is_filterable());
 | 
			
		||||
                
 | 
			
		||||
                let is_filterable =
 | 
			
		||||
                    matched_rule.map_or(false, |(_, features)| features.is_filterable());
 | 
			
		||||
 | 
			
		||||
                if !is_filterable {
 | 
			
		||||
                    invalid_facets.insert(field.to_string());
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                    // If the field matched a rule but that rule doesn't enable filtering,
 | 
			
		||||
                    // store the rule index for better error messages
 | 
			
		||||
                    if let Some((rule_index, _)) = matched_rule {
 | 
			
		||||
 
 | 
			
		||||
@@ -76,8 +76,9 @@ impl<'a> SearchForFacetValues<'a> {
 | 
			
		||||
 | 
			
		||||
        let filterable_attributes_rules = index.filterable_attributes_rules(rtxn)?;
 | 
			
		||||
        let matched_rule = matching_features(&self.facet, &filterable_attributes_rules);
 | 
			
		||||
        let is_facet_searchable = matched_rule.map_or(false, |(_, features)| features.is_facet_searchable());
 | 
			
		||||
        
 | 
			
		||||
        let is_facet_searchable =
 | 
			
		||||
            matched_rule.map_or(false, |(_, features)| features.is_facet_searchable());
 | 
			
		||||
 | 
			
		||||
        if !is_facet_searchable {
 | 
			
		||||
            let matching_field_names =
 | 
			
		||||
                filtered_matching_patterns(&filterable_attributes_rules, &|features| {
 | 
			
		||||
@@ -85,7 +86,7 @@ impl<'a> SearchForFacetValues<'a> {
 | 
			
		||||
                });
 | 
			
		||||
            let (valid_patterns, hidden_fields) =
 | 
			
		||||
                index.remove_hidden_fields(rtxn, matching_field_names)?;
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
            // Get the matching rule index if any rule matched the attribute
 | 
			
		||||
            let matching_rule_index = matched_rule.map(|(rule_index, _)| rule_index);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -191,8 +191,9 @@ impl<'a> Search<'a> {
 | 
			
		||||
            let filterable_fields = ctx.index.filterable_attributes_rules(ctx.txn)?;
 | 
			
		||||
            // check if the distinct field is in the filterable fields
 | 
			
		||||
            let matched_rule = matching_features(distinct, &filterable_fields);
 | 
			
		||||
            let is_filterable = matched_rule.map_or(false, |(_, features)| features.is_filterable());
 | 
			
		||||
            
 | 
			
		||||
            let is_filterable =
 | 
			
		||||
                matched_rule.map_or(false, |(_, features)| features.is_filterable());
 | 
			
		||||
 | 
			
		||||
            if !is_filterable {
 | 
			
		||||
                // if not, remove the hidden fields from the filterable fields to generate the error message
 | 
			
		||||
                let matching_patterns =
 | 
			
		||||
@@ -201,10 +202,10 @@ impl<'a> Search<'a> {
 | 
			
		||||
                    });
 | 
			
		||||
                let (valid_patterns, hidden_fields) =
 | 
			
		||||
                    ctx.index.remove_hidden_fields(ctx.txn, matching_patterns)?;
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Get the matching rule index if any rule matched the attribute
 | 
			
		||||
                let matching_rule_index = matched_rule.map(|(rule_index, _)| rule_index);
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // and return the error
 | 
			
		||||
                return Err(Error::UserError(UserError::InvalidDistinctAttribute {
 | 
			
		||||
                    field: distinct.clone(),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user