diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index d46b4f9f..8a6fe098 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 FilterableAttribute[] {new FilterableAttribute("genres"), new FilterableAttribute(new String[]{"director"}, true, filters)}); client.index("movies").updateSettings(settings); reset_filterable_attributes_1: |- client.index("movies").resetFilterableAttributesSettings(); 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/Index.java b/src/main/java/com/meilisearch/sdk/Index.java index 1eb01bec..0b2fc186 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; @@ -444,7 +445,7 @@ public Searchable search(SearchRequest searchRequest) throws MeilisearchExceptio * href="https://www.meilisearch.com/docs/reference/api/facet_search#perform-a-facet-search">API * specification * @see Index#getFilterableAttributesSettings() getFilterableAttributesSettings - * @see Index#updateFilterableAttributesSettings(String[]) updateFilterableAttributesSettings + * @see Index#updateFilterableAttributesSettings(Object[]) updateFilterableAttributesSettings * @since 1.3 */ public FacetSearchable facetSearch(FacetSearchRequest facetSearchRequest) @@ -747,28 +748,71 @@ public TaskInfo resetLocalizedAttributesSettings() throws MeilisearchException { * specification */ public String[] getFilterableAttributesSettings() 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]); + } + + /** + * 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); } /** - * 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)); } /** - * 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/SettingsHandler.java b/src/main/java/com/meilisearch/sdk/SettingsHandler.java index 38fd4034..f2fb8b68 100644 --- a/src/main/java/com/meilisearch/sdk/SettingsHandler.java +++ b/src/main/java/com/meilisearch/sdk/SettingsHandler.java @@ -2,13 +2,7 @@ import com.meilisearch.sdk.exceptions.MeilisearchException; import com.meilisearch.sdk.http.URLBuilder; -import com.meilisearch.sdk.model.Embedder; -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; /** @@ -319,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); } /** @@ -333,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..7f2aa044 --- /dev/null +++ b/src/main/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializer.java @@ -0,0 +1,172 @@ +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<>(); + + 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; + } + + 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..621ff9ca --- /dev/null +++ b/src/main/java/com/meilisearch/sdk/model/FilterableAttribute.java @@ -0,0 +1,73 @@ +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"); + 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]; + 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 419bd09f..5023927f 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 a8dbbbe0..644830cd 100644 --- a/src/test/java/com/meilisearch/integration/SettingsTest.java +++ b/src/test/java/com/meilisearch/integration/SettingsTest.java @@ -16,20 +16,9 @@ import com.meilisearch.integration.classes.AbstractIT; import com.meilisearch.integration.classes.TestData; import com.meilisearch.sdk.Index; -import com.meilisearch.sdk.model.Embedder; -import com.meilisearch.sdk.model.EmbedderDistribution; -import com.meilisearch.sdk.model.EmbedderSource; -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; @@ -656,6 +645,85 @@ 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.getFullFilterableAttributesSettings(); + + 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.getFullFilterableAttributesSettings(); + String[] newFilterableAttributes = {"title", "description", "genre", "release_date"}; + + index.waitForTask( + index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); + 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); + 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.getFullFilterableAttributesSettings(); + String[] newFilterableAttributes = { + "title", "description", "genres", "director", "release_date" + }; + + index.waitForTask( + index.updateFilterableAttributesSettings(newFilterableAttributes).getTaskUid()); + FilterableAttribute[] updatedFilterableAttributes = + index.getFullFilterableAttributesSettings(); + + index.waitForTask(index.resetFilterableAttributesSettings().getTaskUid()); + FilterableAttribute[] filterableAttributesAfterReset = + index.getFullFilterableAttributesSettings(); + + 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") diff --git a/src/test/java/com/meilisearch/integration/TasksTest.java b/src/test/java/com/meilisearch/integration/TasksTest.java index 391b57ca..6f5c1d5e 100644 --- a/src/test/java/com/meilisearch/integration/TasksTest.java +++ b/src/test/java/com/meilisearch/integration/TasksTest.java @@ -149,7 +149,6 @@ public void testClientGetTasksLimitAndFrom() throws Exception { int from = 2; TasksQuery query = new TasksQuery().setLimit(limit).setFrom(from); TasksResults result = client.getTasks(query); - 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..b9af1dd2 --- /dev/null +++ b/src/test/java/com/meilisearch/sdk/json/GsonFilterableAttributeSerializerTest.java @@ -0,0 +1,173 @@ +package com.meilisearch.sdk.json; + +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; +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}); + + // Parse JSON strings for comparison + JsonElement expectedJson = JsonParser.parseString(expectedOutput); + JsonElement actualJson = JsonParser.parseString(array); + + assertEquals(expectedJson, actualJson); + } + + @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)); + } + } + } +}