From 29eed5c04a5ebe6af29caa9fb15cf027e9a22f73 Mon Sep 17 00:00:00 2001 From: Noah-Mack-01 Date: Sat, 19 Apr 2025 18:59:08 -0400 Subject: [PATCH 1/9] Add backwards-compatible serialization for filtration ### New Additions * **`FilterableAttribute`** is a flattened POJO implementation of the new [granular filtration feature](https://github.com/meilisearch/meilisearch/issues/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. --- .code-samples.meilisearch.yaml | 5 +- src/main/java/com/meilisearch/sdk/Index.java | 53 +++++- .../com/meilisearch/sdk/SettingsHandler.java | 20 +-- .../GsonFilterableAttributeSerializer.java | 168 ++++++++++++++++++ .../meilisearch/sdk/json/GsonJsonHandler.java | 3 + .../sdk/model/FilterableAttribute.java | 72 ++++++++ .../com/meilisearch/sdk/model/Settings.java | 10 +- .../integration/FilterableAttributeTest.java | 97 ++++++++++ .../meilisearch/integration/SettingsTest.java | 107 ++++++++--- .../meilisearch/integration/TasksTest.java | 4 +- ...GsonFilterableAttributeSerializerTest.java | 166 +++++++++++++++++ 11 files changed, 659 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java create mode 100644 src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java create mode 100644 src/test/java/com/meilisearch/integration/FilterableAttributeTest.java create mode 100644 src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index ccdb9189..756c8d01 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -227,7 +227,10 @@ get_filterable_attributes_1: |- client.index("movies").getFilterableAttributesSettings(); update_filterable_attributes_1: |- Settings settings = new Settings(); - settings.setFilterableAttributes(new String[] {"genres", "director"}); + Map filtersTypes = new HashMap<>(); + filtersTypes.put("comparison",true); + filtersTypes.put("equality",true); + settings.setFilterableAttributes(new FilterableAttributes[] {new FilterableAttributes("genres"), new FilterableAttributes(new String[]{"director"}, true, filters)}); client.index("movies").updateSettings(settings); reset_filterable_attributes_1: |- client.index("movies").resetFilterableAttributesSettings(); diff --git a/src/main/java/com/meilisearch/sdk/Index.java b/src/main/java/com/meilisearch/sdk/Index.java index ee0431a4..fa908f30 100644 --- a/src/main/java/com/meilisearch/sdk/Index.java +++ b/src/main/java/com/meilisearch/sdk/Index.java @@ -5,6 +5,7 @@ import com.meilisearch.sdk.model.*; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import lombok.Getter; @@ -443,8 +444,8 @@ public Searchable search(SearchRequest searchRequest) throws MeilisearchExceptio * @see API * specification - * @see Index#getFilterableAttributesSettings() getFilterableAttributesSettings - * @see Index#updateFilterableAttributesSettings(String[]) updateFilterableAttributesSettings + * @see Index#legacyGetFilterableAttributesSettings() getFilterableAttributesSettings + * @see Index#updateFilterableAttributesSettings(Object[]) updateFilterableAttributesSettings * @since 1.3 */ public FacetSearchable facetSearch(FacetSearchRequest facetSearchRequest) @@ -746,25 +747,59 @@ public TaskInfo resetLocalizedAttributesSettings() throws MeilisearchException { * href="https://www.meilisearch.com/docs/reference/api/settings#get-filterable-attributes">API * specification */ - public String[] getFilterableAttributesSettings() throws MeilisearchException { + public String[] legacyGetFilterableAttributesSettings() throws MeilisearchException { + FilterableAttribute[] attributes = + this.settingsHandler.getFilterableAttributesSettings(this.uid); + return Arrays.stream(this.settingsHandler.getFilterableAttributesSettings(this.uid)) + .reduce( + new ArrayList(), + (list, next) -> { + list.addAll(Arrays.asList(next.getPatterns())); + return list; + }, + (a, b) -> { + a.addAll(b); + return a; + }) + .toArray(new String[0]); + } + + public FilterableAttribute[] getFilterableAttributesSettings() throws MeilisearchException { return this.settingsHandler.getFilterableAttributesSettings(this.uid); } /** - * Updates the filterable attributes of the index. This will re-index all documents in the index + * Generic getFilterableAttributesSettings. Updates the filterable attributes of the index. This + * will re-index all documents in the index * - * @param filterableAttributes An array of strings containing the attributes that can be used as - * filters at query time. + * @param filterableAttributes An array of FilterableAttributes or Strings containing the + * attributes that can be used as filters at query time. * @return TaskInfo instance - * @throws MeilisearchException if an error occurs + * @throws MeilisearchException if an error occurs in the que * @see API * specification */ - public TaskInfo updateFilterableAttributesSettings(String[] filterableAttributes) + public 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, filterableAttributes); + this.uid, + Arrays.stream(filterableAttributes) + .map(FilterableAttribute::new) + .toArray(FilterableAttribute[]::new)); } /** diff --git a/src/main/java/com/meilisearch/sdk/SettingsHandler.java b/src/main/java/com/meilisearch/sdk/SettingsHandler.java index c70f9f80..e7181a53 100644 --- a/src/main/java/com/meilisearch/sdk/SettingsHandler.java +++ b/src/main/java/com/meilisearch/sdk/SettingsHandler.java @@ -2,12 +2,7 @@ import com.meilisearch.sdk.exceptions.MeilisearchException; import com.meilisearch.sdk.http.URLBuilder; -import com.meilisearch.sdk.model.Faceting; -import com.meilisearch.sdk.model.LocalizedAttribute; -import com.meilisearch.sdk.model.Pagination; -import com.meilisearch.sdk.model.Settings; -import com.meilisearch.sdk.model.TaskInfo; -import com.meilisearch.sdk.model.TypoTolerance; +import com.meilisearch.sdk.model.*; import java.util.Map; /** @@ -318,9 +313,10 @@ TaskInfo resetLocalizedAttributesSettings(String uid) throws MeilisearchExceptio * @return an array of strings that contains the filterable attributes settings * @throws MeilisearchException if an error occurs */ - String[] getFilterableAttributesSettings(String uid) throws MeilisearchException { + FilterableAttribute[] getFilterableAttributesSettings(String uid) throws MeilisearchException { return httpClient.get( - settingsPath(uid).addSubroute("filterable-attributes").getURL(), String[].class); + settingsPath(uid).addSubroute("filterable-attributes").getURL(), + FilterableAttribute[].class); } /** @@ -332,13 +328,11 @@ String[] getFilterableAttributesSettings(String uid) throws MeilisearchException * @return TaskInfo instance * @throws MeilisearchException if an error occurs */ - TaskInfo updateFilterableAttributesSettings(String uid, String[] filterableAttributes) - throws MeilisearchException { + 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); } diff --git a/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java b/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java new file mode 100644 index 00000000..7b496510 --- /dev/null +++ b/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java @@ -0,0 +1,168 @@ +package com.meilisearch.sdk.json; + +import com.google.gson.*; +import com.meilisearch.sdk.model.FilterableAttribute; +import java.lang.reflect.Type; +import java.util.*; + +/** JSON serializer/deserializer for {@link FilterableAttribute} objects. */ +public class GsonFilterableAttributeSerializer + implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize( + FilterableAttribute attributes, + Type type, + JsonSerializationContext jsonSerializationContext) { + // when possible, limit size of data sent by using legacy string format + return (canBeString(attributes)) + ? new JsonPrimitive(attributes.getPatterns()[0]) + : serializeAttribute(attributes); + } + + private boolean canBeString(FilterableAttribute attribute) { + if (attribute == null) return false; + Map 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 JsonElement serializeAttribute(FilterableAttribute attribute) { + if (attribute == null) return null; + List exceptions = new ArrayList<>(); + JsonArray patternArray = new JsonArray(); + if (attribute.getPatterns() != null && attribute.getPatterns().length > 0) + try { + // Collect values from POJO + patternArray = + Arrays.stream(attribute.getPatterns()) + .map(JsonPrimitive::new) + .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); + } catch (Exception e) { + exceptions.add(e); + } + else exceptions.add(new JsonParseException("Patterns to filter for were not specified!")); + + JsonObject filters = new JsonObject(); + if (attribute.getFilter() != null) { + try { + filters = + attribute.getFilter().entrySet().stream() + .collect( + JsonObject::new, + (jsonObject, kv) -> + jsonObject.addProperty(kv.getKey(), kv.getValue()), + this::combineJsonObjects); + } catch (Exception e) { + exceptions.add(e); + } + } else { + filters.addProperty("comparison", false); + filters.addProperty("equality", true); + } + + if (!exceptions.isEmpty()) { + throw new JsonParseException(String.join("\n", Arrays.toString(exceptions.toArray()))); + } + + // Create JSON object + JsonObject jsonObject = new JsonObject(); + JsonObject features = new JsonObject(); + if (attribute.getFacetSearch() != null) + features.addProperty("facetSearch", attribute.getFacetSearch()); + else features.addProperty("facetSearch", false); + features.add("filter", filters); + jsonObject.add("attributePatterns", patternArray); + jsonObject.add("features", features); + return jsonObject; + } + + private void combineJsonObjects(JsonObject a, JsonObject b) { + for (Map.Entry kv : b.entrySet()) a.add(kv.getKey(), kv.getValue()); + } + + @Override + public FilterableAttribute deserialize( + JsonElement jsonElement, + Type type, + JsonDeserializationContext jsonDeserializationContext) + throws JsonParseException { + try { + // legacy check + if (jsonElement.isJsonPrimitive() && jsonElement.getAsJsonPrimitive().isString()) + return new FilterableAttribute(jsonElement.getAsString()); + + JsonObject object = jsonElement.getAsJsonObject(); + JsonObject features = + object.has("features") ? object.getAsJsonObject("features") : null; + // default values for instance lacking `features` + boolean facetSearch = false; + Map filters = new HashMap<>(); + filters.put("equality", true); + filters.put("comparison", false); + + List exceptions = new ArrayList<>(); + // pull values from features. + if (features != null && features.has("facetSearch")) { + try { + JsonPrimitive facetSearchPrimitive = features.getAsJsonPrimitive("facetSearch"); + facetSearch = + facetSearchPrimitive != null && facetSearchPrimitive.getAsBoolean(); + } catch (ClassCastException | IllegalStateException e) { + exceptions.add(e); + } + } + if (features != null && features.has("filter")) + try { + filters = + features.getAsJsonObject("filter").entrySet().stream() + .collect( + HashMap::new, + (m, kv) -> + m.put( + kv.getKey(), + kv.getValue().getAsBoolean()), + HashMap::putAll); + } catch (ClassCastException | IllegalStateException e) { + exceptions.add(e); + } + String[] patterns = new String[0]; + try { + patterns = + object.has("attributePatterns") + ? object.getAsJsonArray("attributePatterns").asList().stream() + .map(JsonElement::getAsString) + .toArray(String[]::new) + : new String[0]; + } catch (ClassCastException | IllegalStateException e) { + exceptions.add(e); + } + + if (!exceptions.isEmpty()) + throw new JsonParseException( + String.join("\n", Arrays.toString(exceptions.toArray()))); + + if (filters.entrySet().stream().noneMatch(Map.Entry::getValue)) + exceptions.add( + new JsonParseException( + "No filtration methods were allowed! Must have at least one type allowed.\n" + + Arrays.toString(filters.entrySet().toArray()))); + if (patterns.length == 0) + exceptions.add( + new JsonParseException( + "Patterns to filter for were not specified! Invalid Attribute.")); + + if (!exceptions.isEmpty()) + throw new JsonParseException( + String.join("\n", Arrays.toString(exceptions.toArray()))); + + return new FilterableAttribute(patterns, facetSearch, filters); + } catch (Exception e) { + throw new JsonParseException("Failed to deserialize FilterableAttribute", e); + } + } +} diff --git a/src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java b/src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java index 05bd66ed..32083d58 100644 --- a/src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java +++ b/src/main/java/com/meilisearch/sdk/json/GsonJsonHandler.java @@ -7,6 +7,7 @@ import com.meilisearch.sdk.exceptions.JsonDecodingException; import com.meilisearch.sdk.exceptions.JsonEncodingException; import com.meilisearch.sdk.exceptions.MeilisearchException; +import com.meilisearch.sdk.model.FilterableAttribute; import com.meilisearch.sdk.model.Key; public class GsonJsonHandler implements JsonHandler { @@ -16,6 +17,8 @@ public class GsonJsonHandler implements JsonHandler { public GsonJsonHandler() { GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(Key.class, new GsonKeyTypeAdapter()); + builder.registerTypeAdapter( + FilterableAttribute.class, new GsonFilterableAttributeSerializer()); this.gson = builder.create(); } diff --git a/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java b/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java new file mode 100644 index 00000000..1c917047 --- /dev/null +++ b/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java @@ -0,0 +1,72 @@ +package com.meilisearch.sdk.model; + +import java.util.*; +import lombok.Getter; + +/** + * Flattened filtration attributes for the MeiliSearch API See @link + * API + */ +@Getter +public class FilterableAttribute { + + String[] patterns; + Boolean facetSearch; + Map filter; + + public static final String _GEO = "_geo"; + + public FilterableAttribute(String pattern) { + boolean patternIsGeo = _GEO.equals(pattern); + this.patterns = new String[] {pattern}; + this.facetSearch = patternIsGeo; + this.filter = new HashMap<>(); + this.filter.put("equality", true); + this.filter.put("comparison", patternIsGeo); + } + + public FilterableAttribute(String[] patterns) { + // Special case of '_geo' pattern will apply special conditions to default attributes + boolean patternHasGeo = false; + for (String s : patterns) + if (_GEO.equals(s)) { + patternHasGeo = true; + break; + } + this.facetSearch = patternHasGeo; + this.filter = new HashMap<>(); + this.filter.put("equality", true); + this.filter.put("comparison", patternHasGeo); + this.patterns = patterns; + } + + public FilterableAttribute( + String[] patterns, boolean facetSearch, Map 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; + } + + private static void checkGeoValidation(boolean facetSearch, Map filters) { + String[] errors = new String[3]; + if (!filters.containsKey("comparison") || filters.get("comparison") == false) + errors[0] = "Comparison filter cannot be null for '_geo' pattern"; + // rewrite the below lines to be JDK 8 compatible. + if (!filters.containsKey("equality") || filters.get("equality") == false) + errors[1] = "Equality filter cannot be null for '_geo' pattern"; + if (!facetSearch) errors[2] = "Facet search cannot be null for '_geo' pattern"; + for (String error : errors) + if (error != null) + throw new RuntimeException( + "Invalid filter for geo pattern: " + Arrays.toString(errors)); + } +} diff --git a/src/main/java/com/meilisearch/sdk/model/Settings.java b/src/main/java/com/meilisearch/sdk/model/Settings.java index 2a4233b6..ef1ec44c 100644 --- a/src/main/java/com/meilisearch/sdk/model/Settings.java +++ b/src/main/java/com/meilisearch/sdk/model/Settings.java @@ -14,11 +14,10 @@ @Setter @Accessors(chain = true) public class Settings { - protected HashMap synonyms; protected String[] stopWords; protected String[] rankingRules; - protected String[] filterableAttributes; + protected FilterableAttribute[] filterableAttributes; protected String distinctAttribute; protected String[] searchableAttributes; protected String[] displayedAttributes; @@ -35,4 +34,11 @@ public class Settings { protected LocalizedAttribute[] localizedAttributes; public Settings() {} + + public final void setFilterableAttributes(String[] settings) { + this.filterableAttributes = new FilterableAttribute[settings.length]; + for (int i = 0; i < settings.length; i++) { + this.filterableAttributes[i] = new FilterableAttribute(settings[i]); + } + } } diff --git a/src/test/java/com/meilisearch/integration/FilterableAttributeTest.java b/src/test/java/com/meilisearch/integration/FilterableAttributeTest.java new file mode 100644 index 00000000..3622984a --- /dev/null +++ b/src/test/java/com/meilisearch/integration/FilterableAttributeTest.java @@ -0,0 +1,97 @@ +package com.meilisearch.integration; + +import static org.junit.jupiter.api.Assertions.*; + +import com.meilisearch.sdk.model.FilterableAttribute; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class FilterableAttributeTest { + + @Test + public void testSinglePatternConstructor() { + FilterableAttribute attribute = new FilterableAttribute("attribute1"); + assertArrayEquals(new String[] {"attribute1"}, attribute.getPatterns()); + assertFalse(attribute.getFacetSearch()); + assertTrue(attribute.getFilter().get("equality")); + assertFalse(attribute.getFilter().get("comparison")); + } + + @Test + public void testSinglePatternConstructorWithGeo() { + FilterableAttribute attribute = new FilterableAttribute("_geo"); + assertArrayEquals(new String[] {"_geo"}, attribute.getPatterns()); + assertTrue(attribute.getFacetSearch()); + assertTrue(attribute.getFilter().get("equality")); + assertTrue(attribute.getFilter().get("comparison")); + } + + @Test + public void testArrayPatternConstructor() { + FilterableAttribute attribute = + new FilterableAttribute(new String[] {"attribute1", "attribute2"}); + assertArrayEquals(new String[] {"attribute1", "attribute2"}, attribute.getPatterns()); + assertFalse(attribute.getFacetSearch()); + assertTrue(attribute.getFilter().get("equality")); + assertFalse(attribute.getFilter().get("comparison")); + } + + @Test + public void testArrayPatternConstructorWithGeo() { + FilterableAttribute attribute = + new FilterableAttribute(new String[] {"attribute1", "_geo"}); + assertArrayEquals(new String[] {"attribute1", "_geo"}, attribute.getPatterns()); + assertTrue(attribute.getFacetSearch()); + assertTrue(attribute.getFilter().get("equality")); + assertTrue(attribute.getFilter().get("comparison")); + } + + @Test + public void testFullConstructorValidInput() { + Map filters = new HashMap<>(); + filters.put("equality", true); + filters.put("comparison", true); + FilterableAttribute attribute = + new FilterableAttribute(new String[] {"attribute1"}, true, filters); + assertArrayEquals(new String[] {"attribute1"}, attribute.getPatterns()); + assertTrue(attribute.getFacetSearch()); + assertEquals(filters, attribute.getFilter()); + } + + @Test + public void testFullConstructorWithGeoValidation() { + Map filters = new HashMap<>(); + filters.put("equality", true); + filters.put("comparison", true); + FilterableAttribute attribute = + new FilterableAttribute(new String[] {"_geo"}, true, filters); + assertArrayEquals(new String[] {"_geo"}, attribute.getPatterns()); + assertTrue(attribute.getFacetSearch()); + assertEquals(filters, attribute.getFilter()); + } + + @Test + public void testFullConstructorWithInvalidGeoValidation() { + Map filters = new HashMap<>(); + filters.put("equality", false); + filters.put("comparison", false); + Exception exception = + assertThrows( + RuntimeException.class, + () -> new FilterableAttribute(new String[] {"_geo"}, false, filters)); + assertTrue(exception.getMessage().contains("Invalid filter for geo pattern")); + } + + @Test + public void testFullConstructorWithNullPatterns() { + Map filters = new HashMap<>(); + filters.put("equality", true); + filters.put("comparison", true); + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> new FilterableAttribute(null, true, filters)); + assertEquals("Patterns cannot be null", exception.getMessage()); + } +} diff --git a/src/test/java/com/meilisearch/integration/SettingsTest.java b/src/test/java/com/meilisearch/integration/SettingsTest.java index b345bdac..8e04891c 100644 --- a/src/test/java/com/meilisearch/integration/SettingsTest.java +++ b/src/test/java/com/meilisearch/integration/SettingsTest.java @@ -16,17 +16,9 @@ import com.meilisearch.integration.classes.AbstractIT; import com.meilisearch.integration.classes.TestData; import com.meilisearch.sdk.Index; -import com.meilisearch.sdk.model.FacetSortValue; -import com.meilisearch.sdk.model.Faceting; -import com.meilisearch.sdk.model.LocalizedAttribute; -import com.meilisearch.sdk.model.Pagination; -import com.meilisearch.sdk.model.Settings; -import com.meilisearch.sdk.model.TaskInfo; -import com.meilisearch.sdk.model.TypoTolerance; +import com.meilisearch.sdk.model.*; import com.meilisearch.sdk.utils.Movie; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -592,7 +584,7 @@ public void testResetLocalizedAttributesSettings() throws Exception { public void testGetFilterableAttributesSettings() throws Exception { Index index = createIndex("testGetDisplayedAttributesSettings"); Settings initialSettings = index.getSettings(); - String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); assertThat( initialFilterableAttributes, @@ -606,12 +598,12 @@ public void testGetFilterableAttributesSettings() throws Exception { @DisplayName("Test update filterable attributes settings") public void testUpdateFilterableAttributesSettings() throws Exception { Index index = createIndex("testUpdateDisplayedAttributesSettings"); - String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); String[] newFilterableAttributes = {"title", "description", "genre", "release_date"}; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - String[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); + String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); assertThat( @@ -626,17 +618,17 @@ public void testUpdateFilterableAttributesSettings() throws Exception { @DisplayName("Test reset filterable attributes settings") public void testResetFilterableAttributesSettings() throws Exception { Index index = createIndex("testUpdateDisplayedAttributesSettings"); - String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); String[] newFilterableAttributes = { "title", "description", "genres", "director", "release_date" }; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - String[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); + String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); - String[] filterableAttributesAfterReset = index.getFilterableAttributesSettings(); + String[] filterableAttributesAfterReset = index.legacyGetFilterableAttributesSettings(); assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); assertThat( @@ -653,6 +645,81 @@ public void testResetFilterableAttributesSettings() throws Exception { is(not(arrayWithSize(initialFilterableAttributes.length)))); } + @Test + @DisplayName("Test get filterable attributes settings by uid (new method)") + public void testGetFilterableAttributesSettingsNew() throws Exception { + Index index = createIndex("testGetFilterableAttributesSettingsNew"); + Settings initialSettings = index.getSettings(); + FilterableAttribute[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + + assertThat( + initialFilterableAttributes, + is(arrayWithSize(initialSettings.getFilterableAttributes().length))); + assertThat( + initialFilterableAttributes, + is(equalTo(initialSettings.getFilterableAttributes()))); + } + + @Test + @DisplayName("Test update filterable attributes settings (new method)") + public void testUpdateFilterableAttributesSettingsNew() throws Exception { + Index index = createIndex("testUpdateFilterableAttributesSettingsNew"); + FilterableAttribute[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + String[] newFilterableAttributes = {"title", "description", "genre", "release_date"}; + + index.waitForTask( + index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); + String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + FilterableAttribute[] attributes = index.getFilterableAttributesSettings(); + assertThat(attributes, is(arrayWithSize(newFilterableAttributes.length))); + assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); + List searchablePatterns = Arrays.asList(newFilterableAttributes); + for (FilterableAttribute attribute : attributes) + for (String patternInArray : attribute.getPatterns()) + assertThat(searchablePatterns.contains(patternInArray), is(true)); + + assertThat( + Arrays.asList(newFilterableAttributes), + containsInAnyOrder(updatedFilterableAttributes)); + assertThat( + updatedFilterableAttributes, + is(not(arrayWithSize(initialFilterableAttributes.length)))); + } + + @Test + @DisplayName("Test reset filterable attributes settings (new method)") + public void testResetFilterableAttributesSettingsNew() throws Exception { + Index index = createIndex("testResetFilterableAttributesSettingsNew"); + FilterableAttribute[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + String[] newFilterableAttributes = { + "title", "description", "genres", "director", "release_date" + }; + + index.waitForTask( + index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); + FilterableAttribute[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); + + index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); + FilterableAttribute[] filterableAttributesAfterReset = + index.getFilterableAttributesSettings(); + + assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); + List searchablePatterns = Arrays.asList(newFilterableAttributes); + for (FilterableAttribute attribute : filterableAttributesAfterReset) + for (String patternInArray : attribute.getPatterns()) + assertThat(searchablePatterns.contains(patternInArray), is(true)); + + assertThat( + updatedFilterableAttributes, + is(not(arrayWithSize(initialFilterableAttributes.length)))); + assertThat( + filterableAttributesAfterReset, + is(not(arrayWithSize(updatedFilterableAttributes.length)))); + assertThat( + updatedFilterableAttributes, + is(not(arrayWithSize(initialFilterableAttributes.length)))); + } + /** Tests of the sortable attributes setting methods* */ @Test @DisplayName("Test get sortable attributes settings by uid") @@ -701,7 +768,7 @@ public void testResetSortableAttributesSettings() throws Exception { String[] updatedSortableAttributes = index.getSortableAttributesSettings(); index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); - String[] filterableAttributesAfterReset = index.getFilterableAttributesSettings(); + String[] filterableAttributesAfterReset = index.legacyGetFilterableAttributesSettings(); assertThat(updatedSortableAttributes, is(arrayWithSize(newSortableAttributes.length))); assertThat( @@ -1018,15 +1085,15 @@ public void testUpdateDisplayedAttributesSettingsUsingNull() throws Exception { @DisplayName("Test update filterable attributes settings when null is passed") public void testUpdateFilterableAttributesSettingsUsingNull() throws Exception { Index index = createIndex("testUpdateFilterableAttributesSettingsUsingNull"); - String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); String[] newFilterableAttributes = {"title", "genres", "cast", "release_date"}; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - String[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); + String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); index.waitForTask(index.updateFilterableAttributesSettings(null).getTaskUid()); - String[] resetFilterableAttributes = index.getFilterableAttributesSettings(); + String[] resetFilterableAttributes = index.legacyGetFilterableAttributesSettings(); assertThat( resetFilterableAttributes, diff --git a/src/test/java/com/meilisearch/integration/TasksTest.java b/src/test/java/com/meilisearch/integration/TasksTest.java index 0eec58d1..7dafa462 100644 --- a/src/test/java/com/meilisearch/integration/TasksTest.java +++ b/src/test/java/com/meilisearch/integration/TasksTest.java @@ -142,7 +142,9 @@ public void testClientGetTasksLimitAndFrom() throws Exception { int from = 2; TasksQuery query = new TasksQuery().setLimit(limit).setFrom(from); TasksResults result = client.getTasks(query); - + System.out.println("Expected from: " + from); + System.out.println("Actual from: " + result.getFrom()); + System.out.println("Tasks returned: " + Arrays.toString(result.getResults())); assertThat(result.getLimit(), is(equalTo(limit))); assertThat(result.getFrom(), is(equalTo(from))); assertThat(result.getFrom(), is(notNullValue())); diff --git a/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java new file mode 100644 index 00000000..c4709e52 --- /dev/null +++ b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java @@ -0,0 +1,166 @@ +package com.meilisearch.sdk.json; + +import static org.junit.jupiter.api.Assertions.*; + +import com.google.gson.JsonParseException; +import com.meilisearch.sdk.model.FilterableAttribute; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class GsonFilterableAttributeSerializerTest { + + GsonFilterableAttributeSerializer serializer; + GsonJsonHandler handler; + + @BeforeEach + public void preTestSetup() { + handler = new GsonJsonHandler(); + } + + @AfterEach + public void postTestCleanup() { + // Cleanup after each test, if needed + } + + @Test + public void testLegacySerialization() { + String input = "attribute1"; + String expectedOutput = "\"attribute1\""; + String result = handler.encode(new FilterableAttribute(input)); + assertEquals(expectedOutput, result); + } + + @Test + public void testLegacyDeserialization() { + String input = "\"attribute1\""; + FilterableAttribute expectedOutput = new FilterableAttribute("attribute1"); + assertDeserializedOutputsEquals( + handler.decode(input, FilterableAttribute.class), expectedOutput); + } + + @Test + public void testNewDeserializationOutputArray() { + String input = "{attributePatterns:[\"attribute1\"]}"; + FilterableAttribute expectedOutput = new FilterableAttribute("attribute1"); + assertDeserializedOutputsEquals( + handler.decode(input, FilterableAttribute.class), expectedOutput); + } + + @Test + public void testNewSerializationOutputArray() { + FilterableAttribute input = new FilterableAttribute(new String[] {"attribute1"}); + String expectedOutput = "\"attribute1\""; + assertEquals(expectedOutput, handler.encode(input)); + } + + @Test + public void testMixedDeserializationOutputWithNoFeatures() { + String input = "[{attributePatterns:[\"attribute1\", \"attribute2\"]},\"attribute3\"]"; + FilterableAttribute expectedOutputOne = + new FilterableAttribute(new String[] {"attribute1", "attribute2"}); + FilterableAttribute expectedOutputTwo = new FilterableAttribute("attribute3"); + FilterableAttribute[] expectedOutput = + new FilterableAttribute[] {expectedOutputOne, expectedOutputTwo}; + FilterableAttribute[] result = handler.decode(input, FilterableAttribute[].class); + assertEquals(expectedOutput.length, result.length); + for (int i = 0; i < expectedOutput.length; i++) { + assertDeserializedOutputsEquals(expectedOutput[i], result[i]); + } + } + + @Test + public void testDeserializationWithFeatures() { + String input = + "{attributePatterns:[\"attribute1\", \"attribute2\"],features: {facetSearch:true, filter:{equality:true, comparison:true}}}"; + Map filters = new HashMap<>(); + filters.put("equality", true); + filters.put("comparison", true); + FilterableAttribute expectedOutput = + new FilterableAttribute(new String[] {"attribute1", "attribute2"}, true, filters); + FilterableAttribute result = handler.decode(input, FilterableAttribute.class); + assertDeserializedOutputsEquals(expectedOutput, result); + } + + @Test + public void testMixedSerializationWithFeatures() { + HashMap 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)); + } + + @Test + public void testDeserializationWithNullElement() { + assertNull(handler.decode("null", FilterableAttribute.class)); + } + + @Test + public void testDeserializationWithInvalidFilter() { + String input = + "{attributePatterns:[\"attribute1\"],features: {facetSearch:true, filter:{equality:false, comparison:false}}}"; + Exception exception = + assertThrows( + JsonParseException.class, + () -> handler.decode(input, FilterableAttribute.class)); + assertTrue(exception.getMessage().contains("Failed to deserialize")); + assertTrue( + exception.getCause().getMessage().contains("No filtration methods were allowed")); + } + + @Test + public void testDeserializationWithMissingPatterns() { + String input = "{features: {facetSearch:true, filter:{equality:true, comparison:true}}}"; + Exception exception = + assertThrows( + JsonParseException.class, + () -> handler.decode(input, FilterableAttribute.class)); + assertTrue(exception.getMessage().contains("Failed to deserialize")); + assertTrue( + exception + .getCause() + .getMessage() + .contains("Patterns to filter for were not specified")); + } + + @Test + public void testSerializationWithNullAttribute() { + FilterableAttribute input = null; + String result = handler.encode(input); + assertEquals("null", result); + } + + @Test + public void testSerializationWithEmptyPatterns() { + FilterableAttribute input = new FilterableAttribute(new String[0], false, new HashMap<>()); + Exception exception = assertThrows(Exception.class, () -> handler.encode(input)); + assertTrue( + exception + .getCause() + .getMessage() + .contains("Patterns to filter for were not specified")); + } + + private static void assertDeserializedOutputsEquals( + FilterableAttribute a, FilterableAttribute b) { + if (a == null) assertNull(b); + else { + assertEquals(a.getPatterns().length, b.getPatterns().length); + for (int i = 0; i < a.getPatterns().length; i++) { + assertEquals(a.getPatterns()[i], b.getPatterns()[i]); + } + assertEquals(a.getFacetSearch(), b.getFacetSearch()); + for (String key : a.getFilter().keySet()) { + assertEquals(a.getFilter().get(key), b.getFilter().get(key)); + } + } + } +} From 06d8fe865baa1162af052f0adab7edbdcbd1ed1e Mon Sep 17 00:00:00 2001 From: Noah Newman Mack <96362615+Noah-Mack-01@users.noreply.github.com> Date: Tue, 22 Apr 2025 07:51:36 -0400 Subject: [PATCH 2/9] Update .code-samples.meilisearch.yaml Co-authored-by: Laurent Cazanove --- .code-samples.meilisearch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index 756c8d01..91f1271c 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -230,7 +230,7 @@ update_filterable_attributes_1: |- Map filtersTypes = new HashMap<>(); filtersTypes.put("comparison",true); filtersTypes.put("equality",true); - settings.setFilterableAttributes(new FilterableAttributes[] {new FilterableAttributes("genres"), new FilterableAttributes(new String[]{"director"}, true, filters)}); + settings.setFilterableAttributes(new FilterableAttribute[] {new FilterableAttribute("genres"), new FilterableAttribute(new String[]{"director"}, true, filters)}); client.index("movies").updateSettings(settings); reset_filterable_attributes_1: |- client.index("movies").resetFilterableAttributesSettings(); From d2ebd253b5a952402aaa4fe1b95bdbb371f4f53d Mon Sep 17 00:00:00 2001 From: Noah-Mack-01 Date: Sun, 27 Apr 2025 09:23:22 -0400 Subject: [PATCH 3/9] Rename getter method in `Index` 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[]`. --- src/main/java/com/meilisearch/sdk/Index.java | 15 ++++++-- .../meilisearch/integration/SettingsTest.java | 38 ++++++++++--------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/meilisearch/sdk/Index.java b/src/main/java/com/meilisearch/sdk/Index.java index fa908f30..6f7634ce 100644 --- a/src/main/java/com/meilisearch/sdk/Index.java +++ b/src/main/java/com/meilisearch/sdk/Index.java @@ -444,7 +444,7 @@ public Searchable search(SearchRequest searchRequest) throws MeilisearchExceptio * @see API * specification - * @see Index#legacyGetFilterableAttributesSettings() getFilterableAttributesSettings + * @see Index#getFilterableAttributesSettings() getFilterableAttributesSettings * @see Index#updateFilterableAttributesSettings(Object[]) updateFilterableAttributesSettings * @since 1.3 */ @@ -747,7 +747,7 @@ public TaskInfo resetLocalizedAttributesSettings() throws MeilisearchException { * href="https://www.meilisearch.com/docs/reference/api/settings#get-filterable-attributes">API * specification */ - public String[] legacyGetFilterableAttributesSettings() throws MeilisearchException { + public String[] getFilterableAttributesSettings() throws MeilisearchException { FilterableAttribute[] attributes = this.settingsHandler.getFilterableAttributesSettings(this.uid); return Arrays.stream(this.settingsHandler.getFilterableAttributesSettings(this.uid)) @@ -764,7 +764,16 @@ public String[] legacyGetFilterableAttributesSettings() throws MeilisearchExcept .toArray(new String[0]); } - public FilterableAttribute[] getFilterableAttributesSettings() throws MeilisearchException { + /** + * Gets the filterable attributes of the index, along with its filtration metadata. + * + * @return filterable attributes of a given uid as FilterableAttribute + * @throws MeilisearchException if an error occurs + * @see API + * Specification + */ + public FilterableAttribute[] getFullFilterableAttributesSettings() throws MeilisearchException { return this.settingsHandler.getFilterableAttributesSettings(this.uid); } diff --git a/src/test/java/com/meilisearch/integration/SettingsTest.java b/src/test/java/com/meilisearch/integration/SettingsTest.java index 8e04891c..4173f9ab 100644 --- a/src/test/java/com/meilisearch/integration/SettingsTest.java +++ b/src/test/java/com/meilisearch/integration/SettingsTest.java @@ -584,7 +584,7 @@ public void testResetLocalizedAttributesSettings() throws Exception { public void testGetFilterableAttributesSettings() throws Exception { Index index = createIndex("testGetDisplayedAttributesSettings"); Settings initialSettings = index.getSettings(); - String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); assertThat( initialFilterableAttributes, @@ -598,12 +598,12 @@ public void testGetFilterableAttributesSettings() throws Exception { @DisplayName("Test update filterable attributes settings") public void testUpdateFilterableAttributesSettings() throws Exception { Index index = createIndex("testUpdateDisplayedAttributesSettings"); - String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); String[] newFilterableAttributes = {"title", "description", "genre", "release_date"}; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); assertThat( @@ -618,17 +618,17 @@ public void testUpdateFilterableAttributesSettings() throws Exception { @DisplayName("Test reset filterable attributes settings") public void testResetFilterableAttributesSettings() throws Exception { Index index = createIndex("testUpdateDisplayedAttributesSettings"); - String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); String[] newFilterableAttributes = { "title", "description", "genres", "director", "release_date" }; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); - String[] filterableAttributesAfterReset = index.legacyGetFilterableAttributesSettings(); + String[] filterableAttributesAfterReset = index.getFilterableAttributesSettings(); assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); assertThat( @@ -650,7 +650,8 @@ public void testResetFilterableAttributesSettings() throws Exception { public void testGetFilterableAttributesSettingsNew() throws Exception { Index index = createIndex("testGetFilterableAttributesSettingsNew"); Settings initialSettings = index.getSettings(); - FilterableAttribute[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + FilterableAttribute[] initialFilterableAttributes = + index.getFullFilterableAttributesSettings(); assertThat( initialFilterableAttributes, @@ -664,13 +665,14 @@ public void testGetFilterableAttributesSettingsNew() throws Exception { @DisplayName("Test update filterable attributes settings (new method)") public void testUpdateFilterableAttributesSettingsNew() throws Exception { Index index = createIndex("testUpdateFilterableAttributesSettingsNew"); - FilterableAttribute[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + FilterableAttribute[] initialFilterableAttributes = + index.getFullFilterableAttributesSettings(); String[] newFilterableAttributes = {"title", "description", "genre", "release_date"}; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); - FilterableAttribute[] attributes = index.getFilterableAttributesSettings(); + String[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); + FilterableAttribute[] attributes = index.getFullFilterableAttributesSettings(); assertThat(attributes, is(arrayWithSize(newFilterableAttributes.length))); assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); List searchablePatterns = Arrays.asList(newFilterableAttributes); @@ -690,18 +692,20 @@ public void testUpdateFilterableAttributesSettingsNew() throws Exception { @DisplayName("Test reset filterable attributes settings (new method)") public void testResetFilterableAttributesSettingsNew() throws Exception { Index index = createIndex("testResetFilterableAttributesSettingsNew"); - FilterableAttribute[] initialFilterableAttributes = index.getFilterableAttributesSettings(); + FilterableAttribute[] initialFilterableAttributes = + index.getFullFilterableAttributesSettings(); String[] newFilterableAttributes = { "title", "description", "genres", "director", "release_date" }; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - FilterableAttribute[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); + FilterableAttribute[] updatedFilterableAttributes = + index.getFullFilterableAttributesSettings(); index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); FilterableAttribute[] filterableAttributesAfterReset = - index.getFilterableAttributesSettings(); + index.getFullFilterableAttributesSettings(); assertThat(updatedFilterableAttributes, is(arrayWithSize(newFilterableAttributes.length))); List searchablePatterns = Arrays.asList(newFilterableAttributes); @@ -768,7 +772,7 @@ public void testResetSortableAttributesSettings() throws Exception { String[] updatedSortableAttributes = index.getSortableAttributesSettings(); index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); - String[] filterableAttributesAfterReset = index.legacyGetFilterableAttributesSettings(); + String[] filterableAttributesAfterReset = index.getFilterableAttributesSettings(); assertThat(updatedSortableAttributes, is(arrayWithSize(newSortableAttributes.length))); assertThat( @@ -1085,15 +1089,15 @@ public void testUpdateDisplayedAttributesSettingsUsingNull() throws Exception { @DisplayName("Test update filterable attributes settings when null is passed") public void testUpdateFilterableAttributesSettingsUsingNull() throws Exception { Index index = createIndex("testUpdateFilterableAttributesSettingsUsingNull"); - String[] initialFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] initialFilterableAttributes = index.getFilterableAttributesSettings(); String[] newFilterableAttributes = {"title", "genres", "cast", "release_date"}; index.waitForTask( index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); - String[] updatedFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] updatedFilterableAttributes = index.getFilterableAttributesSettings(); index.waitForTask(index.updateFilterableAttributesSettings(null).getTaskUid()); - String[] resetFilterableAttributes = index.legacyGetFilterableAttributesSettings(); + String[] resetFilterableAttributes = index.getFilterableAttributesSettings(); assertThat( resetFilterableAttributes, From ed6e3fc4efec044704180796a5128ff1a569fe58 Mon Sep 17 00:00:00 2001 From: Noah Newman Mack <96362615+Noah-Mack-01@users.noreply.github.com> Date: Fri, 16 May 2025 13:05:03 -0400 Subject: [PATCH 4/9] Update src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../sdk/json/GsonFilterableAttributeSerializer.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java b/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java index 7b496510..18673431 100644 --- a/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java +++ b/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java @@ -24,11 +24,15 @@ private boolean canBeString(FilterableAttribute attribute) { if (attribute == null) return false; Map filters = attribute.getFilter(); if (filters == null) filters = new HashMap<>(); - return (attribute.getPatterns() != null + + 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()) - && (filters.containsKey("equality") && filters.get("equality")) - && (!filters.containsKey("comparison") || !filters.get("comparison"))); + && equalityAllowed + && !comparisonAllowed; } private JsonElement serializeAttribute(FilterableAttribute attribute) { From b48c4493b683da5fc78c7f9418eaad760805558c Mon Sep 17 00:00:00 2001 From: Noah Newman Mack <96362615+Noah-Mack-01@users.noreply.github.com> Date: Fri, 16 May 2025 13:08:23 -0400 Subject: [PATCH 5/9] Update src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../sdk/json/GsonFilterableAttributeSerializerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java index c4709e52..80b1d3c2 100644 --- a/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java +++ b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java @@ -43,7 +43,7 @@ public void testLegacyDeserialization() { @Test public void testNewDeserializationOutputArray() { - String input = "{attributePatterns:[\"attribute1\"]}"; + String input = "{\"attributePatterns\":[\"attribute1\"]}"; FilterableAttribute expectedOutput = new FilterableAttribute("attribute1"); assertDeserializedOutputsEquals( handler.decode(input, FilterableAttribute.class), expectedOutput); From ce8e59267d8c0fd01354f13c80e29e8753aa5b4a Mon Sep 17 00:00:00 2001 From: Noah Newman Mack <96362615+Noah-Mack-01@users.noreply.github.com> Date: Fri, 16 May 2025 13:12:53 -0400 Subject: [PATCH 6/9] Update src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../sdk/model/FilterableAttribute.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java b/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java index 1c917047..057df493 100644 --- a/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java +++ b/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java @@ -42,19 +42,20 @@ public FilterableAttribute(String[] patterns) { } public FilterableAttribute( - String[] patterns, boolean facetSearch, Map 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 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 +} private static void checkGeoValidation(boolean facetSearch, Map filters) { String[] errors = new String[3]; From 29c4badc4b9bb8350ba31d7f7aa2e221b4df2ea7 Mon Sep 17 00:00:00 2001 From: Noah-Mack-01 Date: Fri, 16 May 2025 15:08:00 -0400 Subject: [PATCH 7/9] Fix test cases --- src/main/java/com/meilisearch/sdk/Index.java | 2 +- .../sdk/json/GsonFilterableAttributeSerializer.java | 4 ++-- src/test/java/com/meilisearch/integration/TasksTest.java | 3 --- .../sdk/json/GsonFilterableAttributeSerializerTest.java | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/meilisearch/sdk/Index.java b/src/main/java/com/meilisearch/sdk/Index.java index 6f7634ce..aca8dda2 100644 --- a/src/main/java/com/meilisearch/sdk/Index.java +++ b/src/main/java/com/meilisearch/sdk/Index.java @@ -812,7 +812,7 @@ private TaskInfo updateFilterableAttributeSettingsLegacy(String[] filterableAttr } /** - * Resets the filterable attributes of the index + * Resets the filterable attributes of te index * * @return TaskInfo instance * @throws MeilisearchException if an error occurs diff --git a/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java b/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java index 18673431..7f2aa044 100644 --- a/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java +++ b/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java @@ -25,8 +25,8 @@ private boolean canBeString(FilterableAttribute attribute) { Map filters = attribute.getFilter(); if (filters == null) filters = new HashMap<>(); - boolean equalityAllowed = !filters.containsKey("equality") || filters.get("equality"); - boolean comparisonAllowed = filters.getOrDefault("comparison", false); + boolean equalityAllowed = !filters.containsKey("equality") || filters.get("equality"); + boolean comparisonAllowed = filters.getOrDefault("comparison", false); return attribute.getPatterns() != null && attribute.getPatterns().length == 1 diff --git a/src/test/java/com/meilisearch/integration/TasksTest.java b/src/test/java/com/meilisearch/integration/TasksTest.java index 7dafa462..ad4901be 100644 --- a/src/test/java/com/meilisearch/integration/TasksTest.java +++ b/src/test/java/com/meilisearch/integration/TasksTest.java @@ -142,9 +142,6 @@ public void testClientGetTasksLimitAndFrom() throws Exception { int from = 2; TasksQuery query = new TasksQuery().setLimit(limit).setFrom(from); TasksResults result = client.getTasks(query); - System.out.println("Expected from: " + from); - System.out.println("Actual from: " + result.getFrom()); - System.out.println("Tasks returned: " + Arrays.toString(result.getResults())); assertThat(result.getLimit(), is(equalTo(limit))); assertThat(result.getFrom(), is(equalTo(from))); assertThat(result.getFrom(), is(notNullValue())); diff --git a/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java index 80b1d3c2..9cf6726c 100644 --- a/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java +++ b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java @@ -95,7 +95,7 @@ public void testMixedSerializationWithFeatures() { 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)); + assertEquals(expectedOutput, array); } @Test From 20f563d1a6e148485c3961400c0aa21a2f8a7177 Mon Sep 17 00:00:00 2001 From: Noah-Mack-01 Date: Sun, 8 Jun 2025 09:47:24 -0400 Subject: [PATCH 8/9] Fix test cases --- .editorconfig | 2 +- .../sdk/model/FilterableAttribute.java | 28 +++++++++---------- .../meilisearch/integration/ClientTest.java | 4 ++- ...GsonFilterableAttributeSerializerTest.java | 9 +++++- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/.editorconfig b/.editorconfig index 44824155..143f2b34 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -root = true +# root = true [*] charset = utf-8 diff --git a/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java b/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java index 057df493..621ff9ca 100644 --- a/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java +++ b/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java @@ -42,20 +42,20 @@ public FilterableAttribute(String[] patterns) { } public FilterableAttribute( -public FilterableAttribute(String[] patterns, boolean facetSearch, Map 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 -} + String[] patterns, boolean facetSearch, Map 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 + } private static void checkGeoValidation(boolean facetSearch, Map filters) { String[] errors = new String[3]; diff --git a/src/test/java/com/meilisearch/integration/ClientTest.java b/src/test/java/com/meilisearch/integration/ClientTest.java index 82cb4656..864a44f5 100644 --- a/src/test/java/com/meilisearch/integration/ClientTest.java +++ b/src/test/java/com/meilisearch/integration/ClientTest.java @@ -321,7 +321,9 @@ public void testTransientFieldExclusion() throws MeilisearchException { new GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC).create(); // TODO: Throws StackOverflowError on JDK 1.8, but InaccessibleObjectException on JDK9+ - Assertions.assertThrows(StackOverflowError.class, () -> gsonWithTransient.toJson(test)); + Assertions.assertThrows( + Exception.class, + () -> gsonWithTransient.toJson(test)); // accommodating upgrade to jdk 11 Assertions.assertDoesNotThrow(() -> gson.toJson(test)); } } diff --git a/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java index 9cf6726c..b9af1dd2 100644 --- a/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java +++ b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java @@ -2,7 +2,9 @@ import static org.junit.jupiter.api.Assertions.*; +import com.google.gson.JsonElement; import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; import com.meilisearch.sdk.model.FilterableAttribute; import java.util.HashMap; import java.util.Map; @@ -95,7 +97,12 @@ public void testMixedSerializationWithFeatures() { String expectedOutput = "[{\"attributePatterns\":[\"attribute1\",\"attribute2\"],\"features\":{\"facetSearch\":true,\"filter\":{\"comparison\":true,\"equality\":false}}},\"attribute3\"]"; String array = handler.encode(new FilterableAttribute[] {input, input2}); - assertEquals(expectedOutput, array); + + // Parse JSON strings for comparison + JsonElement expectedJson = JsonParser.parseString(expectedOutput); + JsonElement actualJson = JsonParser.parseString(array); + + assertEquals(expectedJson, actualJson); } @Test From 912d5e09233a382d9218a8a4d62ea16bed43560d Mon Sep 17 00:00:00 2001 From: Noah Newman Mack Date: Mon, 9 Jun 2025 07:05:01 -0400 Subject: [PATCH 9/9] Revert ClientTest.java --- src/test/java/com/meilisearch/integration/ClientTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/com/meilisearch/integration/ClientTest.java b/src/test/java/com/meilisearch/integration/ClientTest.java index 864a44f5..82cb4656 100644 --- a/src/test/java/com/meilisearch/integration/ClientTest.java +++ b/src/test/java/com/meilisearch/integration/ClientTest.java @@ -321,9 +321,7 @@ public void testTransientFieldExclusion() throws MeilisearchException { new GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC).create(); // TODO: Throws StackOverflowError on JDK 1.8, but InaccessibleObjectException on JDK9+ - Assertions.assertThrows( - Exception.class, - () -> gsonWithTransient.toJson(test)); // accommodating upgrade to jdk 11 + Assertions.assertThrows(StackOverflowError.class, () -> gsonWithTransient.toJson(test)); Assertions.assertDoesNotThrow(() -> gson.toJson(test)); } }