diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index db106fe8..fe9b325e 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -288,5 +288,11 @@ security_guide_delete_key_1: |- await client.deleteKey('ac5cd97d-5a4b-4226-a868-2d0eb6d197ab'); search_parameter_guide_crop_marker_1: "await client.index('movies').search(\n 'shifu',\n SearchQuery(\n attributesToCrop: ['overview'],\n cropMarker: '[…]',\n ),\n );" search_parameter_guide_highlight_tag_1: "await client.index('movies').search(\n 'winter feast',\n SearchQuery(\n attributesToHighlight: ['overview'],\n highlightPreTag: '',\n highlightPostTag: '<\/span>',\n ),\n );" -geosearch_guide_filter_usage_3: "await client.index('restaurants').search(\n '',\n SearchQuery(\n filter:\n '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])'));\n});" +geosearch_guide_filter_usage_3: "await client.index('restaurants').search(\n '',\n SearchQuery(\n filter:\n '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])',\n ),\n );" search_get_1: await client.index('movies').search('American ninja'); +get_separator_tokens_1: await client.index('articles').getSeparatorTokens(); +update_separator_tokens_1: "await client.index('articles').updateSeparatorTokens([\"|\", \"…\"]);" +reset_separator_tokens_1: await client.index('articles').resetSeparatorTokens(); +get_non_separator_tokens_1: await client.index('articles').getNonSeparatorTokens(); +update_non_separator_tokens_1: "await client.index('articles').updateNonSeparatorTokens([\"@\", \"#\"]);" +reset_non_separator_tokens_1: await client.index('articles').resetNonSeparatorTokens(); diff --git a/lib/src/index.dart b/lib/src/index.dart index ace479aa..47aadca1 100644 --- a/lib/src/index.dart +++ b/lib/src/index.dart @@ -608,6 +608,56 @@ class MeiliSearchIndex { ); } + /// Get separator tokens of the index. + Future> getSeparatorTokens() async { + final response = await http + .getMethod>('/indexes/$uid/settings/separator-tokens'); + + return response.data!.cast(); + } + + /// Reset separator tokens of the index. + Future resetSeparatorTokens() async { + return await _getTask( + http.deleteMethod('/indexes/$uid/settings/separator-tokens'), + ); + } + + /// Update separator tokens of the index. + Future updateSeparatorTokens(List separatorTokens) async { + return await _getTask( + http.putMethod( + '/indexes/$uid/settings/separator-tokens', + data: separatorTokens, + ), + ); + } + + /// Get non separator tokens of the index. + Future> getNonSeparatorTokens() async { + final response = await http.getMethod>( + '/indexes/$uid/settings/non-separator-tokens'); + + return response.data!.cast(); + } + + /// Reset separator tokens of the index. + Future resetNonSeparatorTokens() async { + return await _getTask( + http.deleteMethod('/indexes/$uid/settings/non-separator-tokens'), + ); + } + + /// Update separator tokens of the index. + Future updateNonSeparatorTokens(List nonSeparatorTokens) async { + return await _getTask( + http.putMethod( + '/indexes/$uid/settings/non-separator-tokens', + data: nonSeparatorTokens, + ), + ); + } + /// Get searchable attributes of the index. Future> getSearchableAttributes() async { final response = await http.getMethod>( diff --git a/lib/src/settings/index_settings.dart b/lib/src/settings/index_settings.dart index 71ea53a4..b40fc451 100644 --- a/lib/src/settings/index_settings.dart +++ b/lib/src/settings/index_settings.dart @@ -15,6 +15,8 @@ class IndexSettings { this.typoTolerance, this.pagination, this.faceting, + this.separatorTokens, + this.nonSeparatorTokens, }); static const allAttributes = ['*']; @@ -28,6 +30,12 @@ class IndexSettings { /// List of ranking rules sorted by order of importance List? rankingRules; + /// List of tokens that will be considered as word separators by Meilisearch. + List? separatorTokens; + + /// List of tokens that will not be considered as word separators by Meilisearch. + List? nonSeparatorTokens; + /// Attributes to use in [filters](https://www.meilisearch.com/docs/reference/api/search#filter) List? filterableAttributes; @@ -64,6 +72,8 @@ class IndexSettings { 'typoTolerance': typoTolerance?.toMap(), 'pagination': pagination?.toMap(), 'faceting': faceting?.toMap(), + 'separatorTokens': separatorTokens, + 'nonSeparatorTokens': nonSeparatorTokens }; factory IndexSettings.fromMap(Map map) { @@ -77,6 +87,8 @@ class IndexSettings { final searchableAttributes = map['searchableAttributes']; final displayedAttributes = map['displayedAttributes']; final sortableAttributes = map['sortableAttributes']; + final separatorTokens = map['separatorTokens']; + final nonSeparatorTokens = map['nonSeparatorTokens']; return IndexSettings( synonyms: synonyms is Map @@ -109,6 +121,12 @@ class IndexSettings { : null, faceting: faceting is Map ? Faceting.fromMap(faceting) : null, + nonSeparatorTokens: nonSeparatorTokens is List + ? nonSeparatorTokens.cast() + : null, + separatorTokens: separatorTokens is List + ? separatorTokens.cast() + : null, ); } } diff --git a/test/code_samples.dart b/test/code_samples.dart index 3de892be..7c2ddfbf 100644 --- a/test/code_samples.dart +++ b/test/code_samples.dart @@ -846,12 +846,33 @@ void main() { // #docregion geosearch_guide_filter_usage_3 await client.index('restaurants').search( - '', - SearchQuery( + '', + SearchQuery( filter: - '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])')); + '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])', + ), + ); + // #enddocregion + // #docregion get_separator_tokens_1 + await client.index('articles').getSeparatorTokens(); + // #enddocregion + // #docregion update_separator_tokens_1 + await client.index('articles').updateSeparatorTokens(["|", "…"]); + // #enddocregion + // #docregion reset_separator_tokens_1 + await client.index('articles').resetSeparatorTokens(); + // #enddocregion + // #docregion get_non_separator_tokens_1 + await client.index('articles').getNonSeparatorTokens(); + // #enddocregion + // #docregion update_non_separator_tokens_1 + await client.index('articles').updateNonSeparatorTokens(["@", "#"]); + // #enddocregion + // #docregion reset_non_separator_tokens_1 + await client.index('articles').resetNonSeparatorTokens(); + // #enddocregion }); - // #enddocregion + // skip this test, since it's only used for generating code samples }, skip: true); diff --git a/test/settings_test.dart b/test/settings_test.dart index c6d98621..3952c5f1 100644 --- a/test/settings_test.dart +++ b/test/settings_test.dart @@ -149,6 +149,122 @@ void main() { expect(resetRules, defaultRankingRules); }); + group('separator tokens', () { + Future> doUpdate() async { + final toUpdate = ['zz', 'ff']; + var response = + await index.updateSeparatorTokens(toUpdate).waitFor(client: client); + + expect(response.status, "succeeded"); + return toUpdate; + } + + test("Get", () async { + final initial = await index.getSeparatorTokens(); + final initialFromSettings = + await index.getSettings().then((value) => value.separatorTokens); + + expect( + initial, + equals(initialFromSettings), + ); + }); + + test("Update", () async { + final toUpdate = await doUpdate(); + + final afterUpdate = await index.getSeparatorTokens(); + final afterUpdateFromSettings = + await index.getSettings().then((value) => value.separatorTokens); + expect( + afterUpdateFromSettings, + unorderedEquals(toUpdate), + ); + expect( + afterUpdate, + unorderedEquals(toUpdate), + ); + }); + + test("Reset", () async { + //first update, then reset + await doUpdate(); + final response = + await index.resetSeparatorTokens().waitFor(client: client); + + expect(response.status, 'succeeded'); + final afterReset = await index.getSeparatorTokens(); + final afterResetFromSettings = + await index.getSettings().then((value) => value.separatorTokens); + expect( + afterReset, + equals([]), + ); + expect( + afterResetFromSettings, + equals([]), + ); + }); + }); + group('Non separator tokens', () { + Future> doUpdate() async { + final toUpdate = ['/']; + var response = await index + .updateNonSeparatorTokens(toUpdate) + .waitFor(client: client); + + expect(response.status, "succeeded"); + return toUpdate; + } + + test("Get", () async { + final initial = await index.getNonSeparatorTokens(); + final initialFromSettings = + await index.getSettings().then((value) => value.nonSeparatorTokens); + + expect( + initial, + equals(initialFromSettings), + ); + }); + + test("Update", () async { + final toUpdate = await doUpdate(); + + final afterUpdate = await index.getNonSeparatorTokens(); + final afterUpdateFromSettings = + await index.getSettings().then((value) => value.nonSeparatorTokens); + expect( + afterUpdateFromSettings, + unorderedEquals(toUpdate), + ); + expect( + afterUpdate, + unorderedEquals(toUpdate), + ); + }); + + test("Reset", () async { + //first update, then reset + await doUpdate(); + final response = + await index.resetNonSeparatorTokens().waitFor(client: client); + + expect(response.status, 'succeeded'); + final afterReset = await index.getNonSeparatorTokens(); + final afterResetFromSettings = + await index.getSettings().then((value) => value.nonSeparatorTokens); + expect( + afterReset, + equals([]), + ); + expect( + afterResetFromSettings, + equals([]), + ); + }); + }); + test('Getting, setting, and deleting searchable attributes', () async { final updatedSearchableAttributes = ['title', 'id']; await index