Skip to content

Add backwards-compatible serialization for filtration #840

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

Noah-Mack-01
Copy link

@Noah-Mack-01 Noah-Mack-01 commented Apr 19, 2025

Pull Request - Add new FiltrationAttributes POJO and update implementation of Settings, Index.

Related issue

Fixes #838

What does this PR do?

New Additions

  • FilterableAttribute is a flattened POJO implementation of the new granular filtration feature. It contains backwards compatibility with legacy data.
  • GsonFilterableAttributeSerializer allows for the serialization/deserialization of these POJOs from legacy primitive string filter attributes, as well as the persistence of new single-string attributes with default settings in legacy format.

Changes

  • GsonJsonHandler registers our new serializer.
  • Index: getFilterableAttributesSettings is updated to accommodate the new POJO. Backwards compatible legacyGetFilterableAttributesSettings which returns the traditional String[] attribute configuration has been added as well. updateFilterableAttributes has been updated to accept Object[], as polymorphic backwards compatibility which can accept null values, requires no ambiguity; as we cannot do a polymorphic method, this seemed the best fit.
  • Settings: filterableAttributes was updated to now be of type FilterableAttribute[].
  • SettingsHandler: updateFilterableAttributesSettings now accepts a FilterableAttribute[] object. getFilterableAttributesSettings now returns one likewise.

PR checklist

Please check if your PR fulfills the following requirements:

  • Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)?
  • Have you read the contributing guidelines?
  • Have you made sure that the title is accurate and descriptive of the changes?

Thank you so much for contributing to Meilisearch!

Summary by CodeRabbit

  • New Features

    • Introduced enhanced support for filterable attributes, allowing for advanced filtering options with additional metadata and nested filters.
    • Added new methods to retrieve and update filterable attributes with detailed settings.
  • Bug Fixes

    • Improved validation and error handling for filterable attribute configurations.
  • Tests

    • Added comprehensive unit and integration tests for filterable attribute handling and JSON serialization.
  • Documentation

    • Updated code examples to reflect the new structured approach for defining filterable attributes.

Sorry, something went wrong.

### New Additions
* **`FilterableAttribute`** is a flattened POJO implementation
   of the new [granular filtration feature](meilisearch/meilisearch#5163).
   It contains backwards compatibility with legacy data.
* **`GsonFilterableAttributeSerializer`** allows for the
   serialization/deserialization of these POJOs from legacy
   primitive string filter attributes, as well as the persistence
   of new single-string attributes with default settings in
   legacy format.
### Changes
* **`GsonJsonHandler`** registers our new serializer.
* **`Index`**: `getFilterableAttributesSettings` is updated
   to accommodate the new POJO. Backwards compatible
   `legacyGetFilterableAttributesSettings` which returns
   the traditional `String[]` attribute configuration has been
   added as well. `updateFilterableAttributes` has been
   updated to accept `Object[]`, as polymorphic backwards
   compatibility which can accept `null` values, requires no
   ambiguity; as we cannot do a polymorphic method, this
   seemed the best fit.
* **`Settings`**: `filterableAttributes` was updated to now
  be of type `FilterableAttribute[]`.
* **`SettingsHandler`**: `updateFilterableAttributesSettings`
  now accepts a `FilterableAttribute[]` object.
  `getFilterableAttributesSettings` now returns one likewise.
@Strift Strift requested a review from Copilot April 21, 2025 06:10
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new FilterableAttribute POJO along with a backwards-compatible Gson serializer/deserializer and updates to Settings and Index to support the new filtration format while preserving legacy functionality. Key changes include:

  • Adding new serialization/deserialization logic for FilterableAttribute in GsonFilterableAttributeSerializer and its tests.
  • Updating Settings and Index classes to handle FilterableAttribute arrays and provide legacy support.
  • Modifying integration tests and code samples to work with the new filtration attributes.

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java Unit tests for verifying legacy and new serialization behaviors.
src/test/java/com/meilisearch/integration/TasksTest.java Integration tests now print debug information for task queries.
src/test/java/com/meilisearch/integration/SettingsTest.java Tests updated to use legacy methods and new methods for filterable attributes settings.
src/test/java/com/meilisearch/integration/FilterableAttributeTest.java Unit tests for the FilterableAttribute POJO constructors and validations.
src/main/java/com/meilisearch/sdk/model/Settings.java Updated settings to use FilterableAttribute[] and added a helper to convert legacy strings.
src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java New POJO implementation for filtration settings with validation for geo patterns.
src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java Registers the new serializer for FilterableAttribute.
src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java Implements Gson serialization and deserialization for FilterableAttribute.
src/main/java/com/meilisearch/sdk/SettingsHandler.java Modified to support FilterableAttribute in get/update/reset settings.
src/main/java/com/meilisearch/sdk/Index.java Updated to provide both legacy and new methods for filterable attributes settings.
.code-samples.meilisearch.yaml Updated samples to demonstrate the new filterable attributes setup.
Comments suppressed due to low confidence (3)

.code-samples.meilisearch.yaml:233

  • There is an inconsistency with class and variable names – use 'FilterableAttribute' instead of 'FilterableAttributes' and ensure that the correct variable (e.g., 'filtersTypes') is passed in the constructor.
settings.setFilterableAttributes(new FilterableAttributes[] {new FilterableAttributes("genres"), new FilterableAttributes(new String[]{"director"}, true, filters)});

src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java:98

  • The testDoubleEncoding: 'array' already holds the encoded string value, so re-encoding it may lead to incorrect output. Compare 'array' directly with expectedOutput to ensure correct behavior.
assertEquals(expectedOutput, handler.encode(array));

src/test/java/com/meilisearch/integration/TasksTest.java:145

  • [nitpick] Remove the debugging print statements; they are likely leftover from development and are not needed in committed integration tests.
System.out.println("Expected from: " + from);

@Noah-Mack-01
Copy link
Author

@Strift PR in relation to #838

LMK if you have any questions or concerns!

@Strift Strift requested review from curquiza and brunoocasali April 22, 2025 01:58
@curquiza curquiza added the enhancement New feature or request label Apr 22, 2025
Noah-Mack-01 and others added 2 commits April 22, 2025 07:51
…butes-Methods
@curquiza
Copy link
Member

Hello!

Thank you for your PR @Noah-Mack-01! ❤️

If I understand correctly, this PR introduces breaking changes, because people need to use the legacy methods to have the same behavior than before.
It's ok to make breaking changes, we are not stable yet in this library, but we need to know which ones to do the appropriate changelogs

Also, is there a way to avoid breaking change? Make the new methods to accept both of format (array of string and array of object)?

I would love having the review of @brunoocasali before merging that

@Noah-Mack-01
Copy link
Author

Noah-Mack-01 commented Apr 23, 2025

Hi @curquiza !!

Thinking through this now. Its unfortunate we're working off such an old JDK, since Java 15's sealed interface would be useful in this situation. There's only one method that's been migrated to a legacy tag, and thats a getter method to retrieve the older String[] format of filters.

All update methods are generic, such as the following:

public <O> TaskInfo updateFilterableAttributesSettings(O[] filterableAttributes)
            throws MeilisearchException {
        if (filterableAttributes == null)
            return this.settingsHandler.updateFilterableAttributesSettings(this.uid, null);
        else if (filterableAttributes.getClass().getComponentType() == FilterableAttribute.class)
            return this.settingsHandler.updateFilterableAttributesSettings(
                    this.uid, (FilterableAttribute[]) filterableAttributes);
        else if (filterableAttributes.getClass().getComponentType() == String.class)
            return this.updateFilterableAttributeSettingsLegacy((String[]) filterableAttributes);
        else
            throw new MeilisearchException(
                    "filterableAttributes must be of type String or FilterableAttribute");
    }
    private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) {
        return this.settingsHandler.updateFilterableAttributesSettings(
                this.uid,
                Arrays.stream(filterableAttributes)
                        .map(FilterableAttribute::new)
                        .toArray(FilterableAttribute[]::new));
    }

However I can't think of a way to guarantee that the getFilterableAttributesSettings() method will return EITHER a String[] or FilterableAttribute[], and we can't utilize polymorphism here since there aren't parameters to the method.

I suppose the answer would be to maintain the legacy implementation's original nomenclature getFilterableAttributeSettings and refer to the new method's implementation as something else. Let me know what you think!!!

Priority was placed on full backwards compatibility.
Since two methods in the same java class
cannot have the same name and input parameters
(the compiler does not consider output class)
the legacy feature will retain its same
`getFilterableAttributesSettings` name and
the new, full config getter will instead
be called `getFullFilterableAttributesSettings[]`.
…erable-Attributes-Methods' into enhancement/meilisearch#838-Filterable-Attributes-Methods
@Noah-Mack-01
Copy link
Author

Hi @curquiza @Strift

Made changes I described above. I think the compiler's inability to discern method by result class will be a hard limitation for the current implementation of Index. Please LMK if there's anything else you think would be prudent to change!

@curquiza
Copy link
Member

@coderabbitai review

Copy link

coderabbitai bot commented May 13, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

coderabbitai bot commented May 13, 2025

Walkthrough

The changes introduce support for the new, structured filterableAttributes syntax in the Meilisearch Java SDK. This includes a new FilterableAttribute model, updated settings APIs to handle both legacy and new formats, custom JSON (de)serialization, and comprehensive tests. Backward compatibility and code samples are also updated.

Changes

File(s) Change Summary
.code-samples.meilisearch.yaml Updated code sample to demonstrate usage of the new filterableAttributes syntax, including structured filter types and nested attributes.
src/main/java/com/meilisearch/sdk/Index.java Refactored filterable attributes methods to support both legacy and new structured formats. Added method to retrieve full structured settings. Updated method signatures and internal logic for compatibility.
src/main/java/com/meilisearch/sdk/SettingsHandler.java Changed methods to accept and return FilterableAttribute[] instead of String[]. Simplified encoding and decoding logic for new structure.
src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java Introduced new model class representing structured filterable attributes with patterns, facet search, and filter types. Includes validation logic and multiple constructors.
src/main/java/com/meilisearch/sdk/model/Settings.java Changed filterableAttributes field to FilterableAttribute[]. Added setter for backward compatibility with string arrays.
src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java Added custom Gson serializer/deserializer to handle both legacy string and new object formats for FilterableAttribute, including validation and error handling.
src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java Registered the custom serializer for FilterableAttribute to ensure correct JSON handling throughout the SDK.
src/test/java/com/meilisearch/integration/FilterableAttributeTest.java New test class covering all constructors and validation logic of FilterableAttribute, including geo-specific and error scenarios.
src/test/java/com/meilisearch/integration/SettingsTest.java Added integration tests for new filterable attributes API: retrieval, update, and reset using the new structured format.
src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java New test class for serialization and deserialization of FilterableAttribute, covering legacy, new, mixed, and error cases.
src/test/java/com/meilisearch/integration/TasksTest.java Added print statements for debugging; no functional or structural changes.

Sequence Diagram(s)

Loading
sequenceDiagram
    participant User
    participant Index
    participant SettingsHandler
    participant GsonJsonHandler

    User->>Index: updateFilterableAttributesSettings(attributes)
    Index->>Index: Determine attribute type (String[] or FilterableAttribute[])
    Index->>SettingsHandler: updateFilterableAttributesSettings(uid, FilterableAttribute[])
    SettingsHandler->>GsonJsonHandler: Serialize FilterableAttribute[]
    GsonJsonHandler-->>SettingsHandler: JSON payload
    SettingsHandler->>Meilisearch API: PUT /settings/filterable-attributes
    Meilisearch API-->>SettingsHandler: TaskInfo
    SettingsHandler-->>Index: TaskInfo
    Index-->>User: TaskInfo

    User->>Index: getFullFilterableAttributesSettings()
    Index->>SettingsHandler: getFilterableAttributesSettings(uid)
    SettingsHandler->>Meilisearch API: GET /settings/filterable-attributes
    Meilisearch API-->>SettingsHandler: JSON response
    SettingsHandler->>GsonJsonHandler: Deserialize JSON to FilterableAttribute[]
    GsonJsonHandler-->>SettingsHandler: FilterableAttribute[]
    SettingsHandler-->>Index: FilterableAttribute[]
    Index-->>User: FilterableAttribute[]

Assessment against linked issues

Objective Addressed Explanation
Update methods to accept new filterableAttributes syntax (#838)
Update methods to return new filterableAttributes syntax when returned by Meilisearch (#838)
Update code sample in .code-samples.meilisearch.yaml to show new syntax (#838)

Poem

A hop and a skip, new filters in tow,
Now attributes flex with patterns that grow.
Legacy strings or objects—no fuss!
Gson and tests all join in the plus.
From the warren of code, we leap with delight—
Filterable dreams, now structured and bright!
🐇✨

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Sorry, something went wrong.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🔭 Outside diff range comments (1)
src/test/java/com/meilisearch/integration/SettingsTest.java (1)

774-776: ⚠️ Potential issue

Wrong reset method called – test is exercising the wrong API

In the sortable attributes test you reset filterable attributes
instead of sortable ones, so the assertions are not validating what
the test name describes.

-        index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid());
-        String[] filterableAttributesAfterReset = index.getFilterableAttributesSettings();
+        index.waitForTask(index.resetSortableAttributesSettings().getTaskUid());
+        String[] sortableAttributesAfterReset = index.getSortableAttributesSettings();

Update the variable names accordingly.

🧹 Nitpick comments (7)
src/test/java/com/meilisearch/integration/TasksTest.java (1)

145-147: Consider removing debugging print statements.

These print statements appear to be added for debugging purposes during development and don't provide value in automated test execution. It's generally best practice to remove debug outputs before submitting a PR, as they can create noise in test logs.

-        System.out.println("Expected from: " + from);
-        System.out.println("Actual from: " + result.getFrom());
-        System.out.println("Tasks returned: " + Arrays.toString(result.getResults()));
.code-samples.meilisearch.yaml (1)

230-233: Updates example to demonstrate structured filterable attributes

The example now shows how to use the new FilterableAttribute class with filter types.

However, there's a small issue in the code:

- settings.setFilterableAttributes(new FilterableAttribute[] {new FilterableAttribute("genres"), new FilterableAttribute(new String[]{"director"}, true, filters)});
+ settings.setFilterableAttributes(new FilterableAttribute[] {new FilterableAttribute("genres"), new FilterableAttribute(new String[]{"director"}, true, filtersTypes)});

The variable is defined as filtersTypes on line 231 but used as filters on line 233.

src/main/java/com/meilisearch/sdk/Index.java (1)

750-765: Inefficient & duplicate handler call – simplify stream processing

getFilterableAttributesSettings() invokes settingsHandler.getFilterableAttributesSettings twice and uses an over-complicated reduce to flatten patterns. The extra call wastes an HTTP round-trip, and the reduce allocates a new list at every accumulation step.

A leaner, single-pass approach is both clearer and faster:

-FilterableAttribute[] attributes =
-        this.settingsHandler.getFilterableAttributesSettings(this.uid);
-return Arrays.stream(this.settingsHandler.getFilterableAttributesSettings(this.uid))
-        .reduce(
-                new ArrayList<String>(),
-                (list, next) -> {
-                    list.addAll(Arrays.asList(next.getPatterns()));
-                    return list;
-                },
-                (a, b) -> {
-                    a.addAll(b);
-                    return a;
-                })
-        .toArray(new String[0]);
+FilterableAttribute[] attributes =
+        this.settingsHandler.getFilterableAttributesSettings(this.uid);
+return Arrays.stream(attributes)
+        .flatMap(attr -> Arrays.stream(attr.getPatterns()))
+        .toArray(String[]::new);
src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java (1)

59-70: Error aggregation prints “null” entries – improve message building

Arrays.toString(errors) will include the string "null" for slots
without an error, resulting in confusing messages such as
[null, Equality filter cannot be null..., null].

A cleaner JDK-8-compatible build:

-String[] errors = new String[3];
-...
-for (String error : errors)
-    if (error != null)
-        throw new RuntimeException(
-                "Invalid filter for geo pattern: " + Arrays.toString(errors));
+List<String> errors = new ArrayList<>();
+...
+if (!errors.isEmpty()) {
+    throw new RuntimeException(
+            "Invalid filter for geo pattern: " + String.join("; ", errors));
+}
src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java (2)

15-17: Remove unused field to avoid confusion

The serializer instance is declared but never assigned/used.
Keeping dead fields dilutes readability and can mislead maintainers about the test’s intent.

-    GsonFilterableAttributeSerializer serializer;
-    GsonJsonHandler handler;
+    GsonJsonHandler handler;

152-164: Potential NPE and asymmetric filter comparison

assertDeserializedOutputsEquals iterates only over a.getFilter().keySet().
If either side contains additional keys, or getFilter() returns null, the method can throw or silently miss mismatches.

-            for (String key : a.getFilter().keySet()) {
-                assertEquals(a.getFilter().get(key), b.getFilter().get(key));
-            }
+            Map<String, Boolean> filtersA = Optional.ofNullable(a.getFilter()).orElse(Map.of());
+            Map<String, Boolean> filtersB = Optional.ofNullable(b.getFilter()).orElse(Map.of());
+
+            assertEquals(filtersA.size(), filtersB.size());
+            for (Map.Entry<String, Boolean> entry : filtersA.entrySet()) {
+                assertEquals(entry.getValue(), filtersB.get(entry.getKey()));
+            }
src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java (1)

68-70: Exception message building hides root causes

Using String.join("\n", Arrays.toString(exceptions.toArray())) collapses the list into a single string (the delimiter is ignored) and prints the Exception.toString() rather than the message.

-            throw new JsonParseException(String.join("\n", Arrays.toString(exceptions.toArray())));
+            String message = exceptions.stream()
+                    .map(Throwable::getMessage)
+                    .collect(Collectors.joining("\n"));
+            throw new JsonParseException(message);

Remember to import java.util.stream.Collectors;.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 431cc80 and b836b8a.

📒 Files selected for processing (11)
  • .code-samples.meilisearch.yaml (1 hunks)
  • src/main/java/com/meilisearch/sdk/Index.java (3 hunks)
  • src/main/java/com/meilisearch/sdk/SettingsHandler.java (3 hunks)
  • src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java (1 hunks)
  • src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java (2 hunks)
  • src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java (1 hunks)
  • src/main/java/com/meilisearch/sdk/model/Settings.java (2 hunks)
  • src/test/java/com/meilisearch/integration/FilterableAttributeTest.java (1 hunks)
  • src/test/java/com/meilisearch/integration/SettingsTest.java (2 hunks)
  • src/test/java/com/meilisearch/integration/TasksTest.java (1 hunks)
  • src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java (1)
src/main/java/com/meilisearch/sdk/model/Settings.java (1)
  • Getter (13-44)
🔇 Additional comments (9)
src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java (2)

10-10: Adds required import for new class

This import is necessary for the registration of the FilterableAttribute class with Gson.


20-21: Adds custom type adapter for the new FilterableAttribute class

The registration of GsonFilterableAttributeSerializer enables the JSON handler to support both legacy string format and the new object format for filterable attributes.

src/main/java/com/meilisearch/sdk/model/Settings.java (2)

20-20: Updates field type to support new structured filterable attributes

Changing from String[] to FilterableAttribute[] enables storing richer metadata associated with filterable attributes.


38-43: Adds backward-compatible setter method

This method provides backward compatibility by converting simple strings to FilterableAttribute objects, allowing existing code to continue working without changes.

src/main/java/com/meilisearch/sdk/SettingsHandler.java (2)

5-5: Optimizes imports using wildcard

Replaced individual model class imports with a wildcard import, which is cleaner and easier to maintain as new model classes are added.


316-320:

✅ Verification successful

Updates method return type for filterable attributes

Changed the return type from String[] to FilterableAttribute[] to support the new structured format. This is a breaking change at this level, but according to the PR description, backward compatibility is maintained at the Index class level with legacy methods.

Let's verify the backward compatibility handling in the Index class:


🏁 Script executed:

#!/bin/bash
# Find the backward-compatible methods in Index class
rg "legacyGetFilterableAttributesSettings" --type java

Length of output: 54


To confirm how backward compatibility is exposed, let’s locate the Index class and search for any methods handling “filterable” attributes:


🏁 Script executed:

#!/bin/bash
# 1. Find the Index class file
fd Index.java

# 2. Search for any filterable-related methods across Java sources
rg -n "filterable" --type java

Length of output: 7009


Let’s inspect how getFilterableAttributesSettings is implemented in Index.java to confirm backward compatibility:


🏁 Script executed:

#!/bin/bash
# Show the getFilterableAttributesSettings methods with context
rg -n "getFilterableAttributesSettings" -C3 src/main/java/com/meilisearch/sdk/Index.java

Length of output: 1602


Backward compatibility for filterable attributes methods confirmed

  • SettingsHandler.getFilterableAttributesSettings now returns FilterableAttribute[]
  • Index#getFullFilterableAttributesSettings() exposes the new FilterableAttribute[] return type
  • Index#getFilterableAttributesSettings() remains as String[] by converting the structured response

No further changes needed.

src/test/java/com/meilisearch/integration/SettingsTest.java (1)

710-715: Logical mismatch: assertions after reset still use new patterns

After a successful reset we expect the attributes to match the initial
state, not the interim new list. Re-checking against
newFilterableAttributes may lead to false positives (loops simply
don’t execute when the array is empty).

Please verify that the post-reset values equal
initialFilterableAttributes instead.

src/test/java/com/meilisearch/integration/FilterableAttributeTest.java (1)

12-19: Good coverage of default constructor behaviour

The test accurately checks patterns, facetSearch and default filters for
a single-pattern attribute. Nice work!

src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java (1)

137-140: JsonArray#asList() is available only from Gson 2.10

Projects pinned to older Gson versions (quite common in Android stacks) will not compile.
Consider a version-agnostic fallback:

JsonArray array = object.getAsJsonArray("attributePatterns");
patterns = StreamSupport.stream(array.spliterator(), false)
        .map(JsonElement::getAsString)
        .toArray(String[]::new);

Comment on lines +331 to 336
TaskInfo updateFilterableAttributesSettings(
String uid, FilterableAttribute[] filterableAttributes) throws MeilisearchException {
return httpClient.put(
settingsPath(uid).addSubroute("filterable-attributes").getURL(),
filterableAttributes == null
? httpClient.jsonHandler.encode(filterableAttributes)
: filterableAttributes,
httpClient.jsonHandler.encode(filterableAttributes),
TaskInfo.class);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Updates update method parameter type and simplifies encoding

Changed the parameter type from String[] to FilterableAttribute[] and simplified the JSON encoding logic. The removal of the null check is safe because encode() already handles null values.

Let's verify how the Update method is called:


🏁 Script executed:

#!/bin/bash
# Find calls to updateFilterableAttributesSettings
rg "updateFilterableAttributesSettings" --type java

Length of output: 2055


Breaking Change: UpdateFilterableAttributesSettings Signature Now Requires FilterableAttribute[]

The parameter type was changed from String[] to FilterableAttribute[] and the null‐check was removed (since encode() already handles null). This is a breaking change: all existing call sites passing String[] or relying on the generic overload will fail to compile.

Please update the following locations:

  • src/main/java/com/meilisearch/sdk/Index.java
    The generic method

    public <O> TaskInfo updateFilterableAttributesSettings(O[] filterableAttributes)

    now passes O[] directly to SettingsHandler#updateFilterableAttributesSettings(String, FilterableAttribute[]). You must either:

    • Add an overload taking String... filterableAttributes that converts each String to a FilterableAttribute; or
    • Change all call sites to use FilterableAttribute enums directly.
  • src/test/java/com/meilisearch/integration/SettingsTest.java

  • src/test/java/com/meilisearch/integration/DocumentsTest.java
    Update calls like

    index.updateFilterableAttributesSettings(new String[] {"genres"});
    index.updateFilterableAttributesSettings(null);

    to use FilterableAttribute[] (or your new String... overload).

No change needed in SettingsHandler.java beyond the already applied signature update.

Comment on lines +806 to 812
private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) {
return this.settingsHandler.updateFilterableAttributesSettings(
this.uid, filterableAttributes);
this.uid,
Arrays.stream(filterableAttributes)
.map(FilterableAttribute::new)
.toArray(FilterableAttribute[]::new));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Checked exception is swallowed – compilation will fail

updateFilterableAttributeSettingsLegacy calls
settingsHandler.updateFilterableAttributesSettings, which declares
throws MeilisearchException.
Because this private method does not declare or catch the checked
exception, the file will not compile.

-private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) {
+private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes)
+        throws MeilisearchException {

Also adjust the single call-site (line 799/800) – the surrounding method
already declares throws MeilisearchException, so no further change is
needed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes) {
return this.settingsHandler.updateFilterableAttributesSettings(
this.uid, filterableAttributes);
this.uid,
Arrays.stream(filterableAttributes)
.map(FilterableAttribute::new)
.toArray(FilterableAttribute[]::new));
}
private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttributes)
throws MeilisearchException {
return this.settingsHandler.updateFilterableAttributesSettings(
this.uid,
Arrays.stream(filterableAttributes)
.map(FilterableAttribute::new)
.toArray(FilterableAttribute[]::new));
}

Comment on lines +45 to +57
String[] patterns, boolean facetSearch, Map<String, Boolean> filters) {
if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null");
boolean patternHasGeo = false;
for (String s : patterns)
if (_GEO.equals(s)) {
patternHasGeo = true;
break;
}
if (patternHasGeo) checkGeoValidation(facetSearch, filters);
this.patterns = patterns;
this.facetSearch = facetSearch;
this.filter = filters;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential NPE & defensive-copy omissions in full constructor

  1. filters may be null; subsequent containsKey calls would throw a
    NullPointerException, breaking the geo-validation path.
  2. patterns is stored directly; callers can mutate the passed array
    after construction, violating encapsulation.
 if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null");
+if (filters == null) throw new IllegalArgumentException("Filters cannot be null");
 ...
-this.patterns = patterns;
+this.patterns = Arrays.copyOf(patterns, patterns.length); // defensive copy
-this.filter = filters;
+this.filter = new HashMap<>(filters);                     // defensive copy

Consider wrapping filter with Collections.unmodifiableMap to keep
the instance immutable.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String[] patterns, boolean facetSearch, Map<String, Boolean> filters) {
if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null");
boolean patternHasGeo = false;
for (String s : patterns)
if (_GEO.equals(s)) {
patternHasGeo = true;
break;
}
if (patternHasGeo) checkGeoValidation(facetSearch, filters);
this.patterns = patterns;
this.facetSearch = facetSearch;
this.filter = filters;
}
public FilterableAttribute(String[] patterns, boolean facetSearch, Map<String, Boolean> filters) {
if (patterns == null) throw new IllegalArgumentException("Patterns cannot be null");
if (filters == null) throw new IllegalArgumentException("Filters cannot be null");
boolean patternHasGeo = false;
for (String s : patterns)
if (_GEO.equals(s)) {
patternHasGeo = true;
break;
}
if (patternHasGeo) checkGeoValidation(facetSearch, filters);
this.patterns = Arrays.copyOf(patterns, patterns.length); // defensive copy
this.facetSearch = facetSearch;
this.filter = new HashMap<>(filters); // defensive copy
}

Comment on lines +96 to +98
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]";
String array = handler.encode(new FilterableAttribute[] {input, input2});
assertEquals(expectedOutput, handler.encode(array));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Deterministic JSON comparison

Even after the above fix, the test performs a string comparison.
HashMap does not guarantee key order, so the produced JSON property order can vary between JDKs, causing nondeterministic failures.

Prefer structural comparison:

JsonElement expected = JsonParser.parseString(expectedOutput);
JsonElement actual   = JsonParser.parseString(result);
assertEquals(expected, actual);

Comment on lines +45 to +50
public void testNewDeserializationOutputArray() {
String input = "{attributePatterns:[\"attribute1\"]}";
FilterableAttribute expectedOutput = new FilterableAttribute("attribute1");
assertDeserializedOutputsEquals(
handler.decode(input, FilterableAttribute.class), expectedOutput);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Test input is not valid JSON – rely on proper JSON to reduce flakiness

{attributePatterns:["attribute1"]} omits the mandatory double quotes around property names required by strict JSON.
Unless GsonJsonHandler enables lenient parsing, this will break; even with lenient mode the example obscures the real-world payload format and makes the test harder to follow.

-        String input = "{attributePatterns:[\"attribute1\"]}";
+        String input = "{\"attributePatterns\":[\"attribute1\"]}";

Please update all raw JSON literals in the file to valid JSON for clarity and forward-compatibility.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void testNewDeserializationOutputArray() {
String input = "{attributePatterns:[\"attribute1\"]}";
FilterableAttribute expectedOutput = new FilterableAttribute("attribute1");
assertDeserializedOutputsEquals(
handler.decode(input, FilterableAttribute.class), expectedOutput);
}
public void testNewDeserializationOutputArray() {
String input = "{\"attributePatterns\":[\"attribute1\"]}";
FilterableAttribute expectedOutput = new FilterableAttribute("attribute1");
assertDeserializedOutputsEquals(
handler.decode(input, FilterableAttribute.class), expectedOutput);
}

Comment on lines +88 to +99
public void testMixedSerializationWithFeatures() {
HashMap<String, Boolean> filters = new HashMap<>();
filters.put("equality", false);
filters.put("comparison", true);
FilterableAttribute input =
new FilterableAttribute(new String[] {"attribute1", "attribute2"}, true, filters);
FilterableAttribute input2 = new FilterableAttribute("attribute3");
String expectedOutput =
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]";
String array = handler.encode(new FilterableAttribute[] {input, input2});
assertEquals(expectedOutput, handler.encode(array));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Assertion encodes the JSON twice – the test will always fail

handler.encode(array) is called on a String, causing the string to be wrapped in quotes and escaped, so the comparison with the raw expectedOutput never matches.

-        String array = handler.encode(new FilterableAttribute[] {input, input2});
-        assertEquals(expectedOutput, handler.encode(array));
+        String result = handler.encode(new FilterableAttribute[] {input, input2});
+        assertEquals(expectedOutput, result);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void testMixedSerializationWithFeatures() {
HashMap<String, Boolean> filters = new HashMap<>();
filters.put("equality", false);
filters.put("comparison", true);
FilterableAttribute input =
new FilterableAttribute(new String[] {"attribute1", "attribute2"}, true, filters);
FilterableAttribute input2 = new FilterableAttribute("attribute3");
String expectedOutput =
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]";
String array = handler.encode(new FilterableAttribute[] {input, input2});
assertEquals(expectedOutput, handler.encode(array));
}
public void testMixedSerializationWithFeatures() {
HashMap<String, Boolean> filters = new HashMap<>();
filters.put("equality", false);
filters.put("comparison", true);
FilterableAttribute input =
new FilterableAttribute(new String[] {"attribute1", "attribute2"}, true, filters);
FilterableAttribute input2 = new FilterableAttribute("attribute3");
String expectedOutput =
"[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]";
- String array = handler.encode(new FilterableAttribute[] {input, input2});
- assertEquals(expectedOutput, handler.encode(array));
+ String result = handler.encode(new FilterableAttribute[] {input, input2});
+ assertEquals(expectedOutput, result);
}

Comment on lines +23 to +32
private boolean canBeString(FilterableAttribute attribute) {
if (attribute == null) return false;
Map<String, Boolean> filters = attribute.getFilter();
if (filters == null) filters = new HashMap<>();
return (attribute.getPatterns() != null
&& attribute.getPatterns().length == 1
&& (attribute.getFacetSearch() == null || !attribute.getFacetSearch())
&& (filters.containsKey("equality") && filters.get("equality"))
&& (!filters.containsKey("comparison") || !filters.get("comparison")));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

canBeString incorrectly rejects default filter settings

If attribute.getFilter() is null, the current logic treats equality as disabled (because the empty map lacks the key) and thus serialises the attribute as an object instead of the compact legacy string.

Refine the checks so that a missing key inherits the default (equality == true, comparison == false):

-        return (attribute.getPatterns() != null
-                && attribute.getPatterns().length == 1
-                && (attribute.getFacetSearch() == null || !attribute.getFacetSearch())
-                && (filters.containsKey("equality") && filters.get("equality"))
-                && (!filters.containsKey("comparison") || !filters.get("comparison")));
+        boolean equalityAllowed   = !filters.containsKey("equality") || filters.get("equality");
+        boolean comparisonAllowed =  filters.getOrDefault("comparison", false);
+
+        return attribute.getPatterns() != null
+                && attribute.getPatterns().length == 1
+                && (attribute.getFacetSearch() == null || !attribute.getFacetSearch())
+                &&  equalityAllowed
+                && !comparisonAllowed;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private boolean canBeString(FilterableAttribute attribute) {
if (attribute == null) return false;
Map<String, Boolean> filters = attribute.getFilter();
if (filters == null) filters = new HashMap<>();
return (attribute.getPatterns() != null
&& attribute.getPatterns().length == 1
&& (attribute.getFacetSearch() == null || !attribute.getFacetSearch())
&& (filters.containsKey("equality") && filters.get("equality"))
&& (!filters.containsKey("comparison") || !filters.get("comparison")));
}
private boolean canBeString(FilterableAttribute attribute) {
if (attribute == null) return false;
Map<String, Boolean> filters = attribute.getFilter();
if (filters == null) filters = new HashMap<>();
boolean equalityAllowed = !filters.containsKey("equality") || filters.get("equality");
boolean comparisonAllowed = filters.getOrDefault("comparison", false);
return attribute.getPatterns() != null
&& attribute.getPatterns().length == 1
&& (attribute.getFacetSearch() == null || !attribute.getFacetSearch())
&& equalityAllowed
&& !comparisonAllowed;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[v1.14] Filterable attributes settings opt-out
3 participants