From 3c3eb6b4b174a4c95d57f62c175b7b694ada73be Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 8 Apr 2014 13:27:00 +0200 Subject: [PATCH] Implemented _suggest endpoint as .Suggest() fix #397 and fix #518 --- src/Nest/DSL/SearchDescriptor.cs | 1 - src/Nest/DSL/SuggestDescriptor.cs | 90 +++++++++++++++++++ src/Nest/DSL/_Descriptors.generated.cs | 14 +-- src/Nest/Domain/Responses/SuggestResponse.cs | 26 ++++++ src/Nest/ElasticClient-Suggest.cs | 31 +++++++ src/Nest/IElasticClient.cs | 18 ++++ src/Nest/Nest.csproj | 4 + .../Converters/SuggestResponseConverter.cs | 40 +++++++++ src/Nest/Resolvers/ElasticContractResolver.cs | 3 + .../Core/Suggest/SuggestTests.cs | 44 +++++++++ .../Nest.Tests.Integration.csproj | 1 + 11 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 src/Nest/DSL/SuggestDescriptor.cs create mode 100644 src/Nest/Domain/Responses/SuggestResponse.cs create mode 100644 src/Nest/ElasticClient-Suggest.cs create mode 100644 src/Nest/Resolvers/Converters/SuggestResponseConverter.cs create mode 100644 src/Tests/Nest.Tests.Integration/Core/Suggest/SuggestTests.cs diff --git a/src/Nest/DSL/SearchDescriptor.cs b/src/Nest/DSL/SearchDescriptor.cs index a1c921ca911..46fe216ef0a 100644 --- a/src/Nest/DSL/SearchDescriptor.cs +++ b/src/Nest/DSL/SearchDescriptor.cs @@ -870,7 +870,6 @@ public SearchDescriptor SuggestGlobalText(string globalSuggestText) return this; } - /// /// The term suggester suggests terms based on edit distance. The provided suggest text is analyzed before terms are suggested. /// The suggested terms are provided per analyzed suggest text token. The term suggester doesn’t take the query into account that is part of request. diff --git a/src/Nest/DSL/SuggestDescriptor.cs b/src/Nest/DSL/SuggestDescriptor.cs new file mode 100644 index 00000000000..72616bcfa29 --- /dev/null +++ b/src/Nest/DSL/SuggestDescriptor.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using Elasticsearch.Net; +using Newtonsoft.Json; +using System.Linq.Expressions; +using Nest.Resolvers; +using Nest.Domain; + +namespace Nest +{ + [DescriptorFor("Suggest")] + public partial class SuggestDescriptor : + IndicesOptionalPathDescriptor, SuggestRequestParameters> + , IPathInfo + where T : class + { + internal IDictionary _Suggest = new Dictionary(); + + /// + /// To avoid repetition of the suggest text, it is possible to define a global text. + /// + public SuggestDescriptor GlobalText(string globalSuggestText) + { + this._Suggest.Add("text", globalSuggestText); + return this; + } + + /// + /// The term suggester suggests terms based on edit distance. The provided suggest text is analyzed before terms are suggested. + /// The suggested terms are provided per analyzed suggest text token. The term suggester doesn’t take the query into account that is part of request. + /// + public SuggestDescriptor Term(string name, Func, TermSuggestDescriptor> suggest) + { + name.ThrowIfNullOrEmpty("name"); + suggest.ThrowIfNull("suggest"); + var desc = new TermSuggestDescriptor(); + var item = suggest(desc); + var bucket = new SuggestDescriptorBucket { _Text = item._Text, TermSuggest = item }; + this._Suggest.Add(name, bucket); + return this; + } + + /// + /// The phrase suggester adds additional logic on top of the term suggester to select entire corrected phrases + /// instead of individual tokens weighted based on ngram-langugage models. + /// + public SuggestDescriptor Phrase(string name, Func, PhraseSuggestDescriptor> suggest) + { + name.ThrowIfNullOrEmpty("name"); + suggest.ThrowIfNull("suggest"); + if (this._Suggest == null) + this._Suggest = new Dictionary(); + + var desc = new PhraseSuggestDescriptor(); + var item = suggest(desc); + var bucket = new SuggestDescriptorBucket { _Text = item._Text, PhraseSuggest = item }; + this._Suggest.Add(name, bucket); + return this; + } + + /// + /// The completion suggester is a so-called prefix suggester. + /// It does not do spell correction like the term or phrase suggesters but allows basic auto-complete functionality. + /// + public SuggestDescriptor Completion(string name, Func, CompletionSuggestDescriptor> suggest) + { + name.ThrowIfNullOrEmpty("name"); + suggest.ThrowIfNull("suggest"); + if (this._Suggest == null) + this._Suggest = new Dictionary(); + + var desc = new CompletionSuggestDescriptor(); + var item = suggest(desc); + var bucket = new SuggestDescriptorBucket { _Text = item._Text, CompletionSuggest = item }; + this._Suggest.Add(name, bucket); + return this; + } + + ElasticsearchPathInfo IPathInfo.ToPathInfo(IConnectionSettingsValues settings) + { + var pathInfo = base.ToPathInfo(settings, this._QueryString); + pathInfo.HttpMethod = PathInfoHttpMethod.POST; + + return pathInfo; + } + } +} diff --git a/src/Nest/DSL/_Descriptors.generated.cs b/src/Nest/DSL/_Descriptors.generated.cs index 7ef8573adc5..23c811dfe36 100644 --- a/src/Nest/DSL/_Descriptors.generated.cs +++ b/src/Nest/DSL/_Descriptors.generated.cs @@ -4788,13 +4788,13 @@ public SnapshotRestoreDescriptor WaitForCompletion(bool wait_for_completion = tr ///http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/search-search.html /// /// - public partial class SuggestDescriptor + public partial class SuggestDescriptor { internal SuggestRequestParameters _QueryString = new SuggestRequestParameters(); ///Whether specified concrete indices should be ignored when unavailable (missing or closed) - public SuggestDescriptor IgnoreUnavailable(bool ignore_unavailable = true) + public SuggestDescriptor IgnoreUnavailable(bool ignore_unavailable = true) { this._QueryString.IgnoreUnavailable(ignore_unavailable); return this; @@ -4802,7 +4802,7 @@ public SuggestDescriptor IgnoreUnavailable(bool ignore_unavailable = true) ///Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) - public SuggestDescriptor AllowNoIndices(bool allow_no_indices = true) + public SuggestDescriptor AllowNoIndices(bool allow_no_indices = true) { this._QueryString.AllowNoIndices(allow_no_indices); return this; @@ -4810,7 +4810,7 @@ public SuggestDescriptor AllowNoIndices(bool allow_no_indices = true) ///Whether to expand wildcard expression to concrete indices that are open, closed or both. - public SuggestDescriptor ExpandWildcards(ExpandWildcardsOptions expand_wildcards) + public SuggestDescriptor ExpandWildcards(ExpandWildcardsOptions expand_wildcards) { this._QueryString.ExpandWildcards(expand_wildcards); return this; @@ -4818,7 +4818,7 @@ public SuggestDescriptor ExpandWildcards(ExpandWildcardsOptions expand_wildcards ///Specify the node or shard the operation should be performed on (default: random) - public SuggestDescriptor Preference(string preference) + public SuggestDescriptor Preference(string preference) { this._QueryString.Preference(preference); return this; @@ -4826,7 +4826,7 @@ public SuggestDescriptor Preference(string preference) ///Specific routing value - public SuggestDescriptor Routing(string routing) + public SuggestDescriptor Routing(string routing) { this._QueryString.Routing(routing); return this; @@ -4834,7 +4834,7 @@ public SuggestDescriptor Routing(string routing) ///The URL-encoded request definition (instead of using request body) - public SuggestDescriptor Source(string source) + public SuggestDescriptor Source(string source) { this._QueryString.Source(source); return this; diff --git a/src/Nest/Domain/Responses/SuggestResponse.cs b/src/Nest/Domain/Responses/SuggestResponse.cs new file mode 100644 index 00000000000..a3f58ecdc37 --- /dev/null +++ b/src/Nest/Domain/Responses/SuggestResponse.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace Nest +{ + public interface ISuggestResponse : IResponse + { + ShardsMetaData Shards { get; } + IDictionary Suggestions { get; set; } + } + + [JsonObject] + public class SuggestResponse : BaseResponse, ISuggestResponse + { + public SuggestResponse() + { + this.IsValid = true; + this.Suggestions = new Dictionary(); + } + + public ShardsMetaData Shards { get; internal set; } + + public IDictionary Suggestions { get; set;} + } +} \ No newline at end of file diff --git a/src/Nest/ElasticClient-Suggest.cs b/src/Nest/ElasticClient-Suggest.cs new file mode 100644 index 00000000000..7a4baf8d05e --- /dev/null +++ b/src/Nest/ElasticClient-Suggest.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Elasticsearch.Net; + +namespace Nest +{ + public partial class ElasticClient + { + /// + public ISuggestResponse Suggest(Func, SuggestDescriptor> selector) + where T : class + { + return this.Dispatch, SuggestRequestParameters, SuggestResponse>( + selector, + (p, d) => this.RawDispatch.SuggestDispatch(p, d._Suggest) + ); + } + + /// + public Task SuggestAsync(Func, SuggestDescriptor> selector) + where T : class + { + return this.DispatchAsync, SuggestRequestParameters, SuggestResponse, ISuggestResponse>( + selector, + (p, d) => this.RawDispatch.SuggestDispatchAsync(p, d._Suggest) + ); + } + } +} \ No newline at end of file diff --git a/src/Nest/IElasticClient.cs b/src/Nest/IElasticClient.cs index 256a6b6b0f8..bbf40f80d0a 100644 --- a/src/Nest/IElasticClient.cs +++ b/src/Nest/IElasticClient.cs @@ -990,5 +990,23 @@ Task IndexManyAsync(IEnumerable objects, string index = nul /// /// An optional descriptor that further describes the status operation, i.e limiting it to certain indices Task StatusAsync(Func selector = null); + + /// + /// The suggest feature suggests similar looking terms based on a provided text by using a suggester. + /// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-status.html + /// + /// The type used to strongly type parts of the suggest operation + /// The suggesters to use this operation (can be multiple) + ISuggestResponse Suggest(Func, SuggestDescriptor> selector) + where T : class; + + /// + /// The suggest feature suggests similar looking terms based on a provided text by using a suggester. + /// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-status.html + /// + /// The type used to strongly type parts of the suggest operation + /// The suggesters to use this operation (can be multiple) + Task SuggestAsync(Func, SuggestDescriptor> selector) + where T : class; } } \ No newline at end of file diff --git a/src/Nest/Nest.csproj b/src/Nest/Nest.csproj index e9497cdd5d5..a5cb80852bb 100644 --- a/src/Nest/Nest.csproj +++ b/src/Nest/Nest.csproj @@ -138,6 +138,7 @@ + @@ -164,6 +165,7 @@ + @@ -220,6 +222,7 @@ + @@ -668,6 +671,7 @@ + diff --git a/src/Nest/Resolvers/Converters/SuggestResponseConverter.cs b/src/Nest/Resolvers/Converters/SuggestResponseConverter.cs new file mode 100644 index 00000000000..11ccb19c188 --- /dev/null +++ b/src/Nest/Resolvers/Converters/SuggestResponseConverter.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Elasticsearch.Net; +using Elasticsearch.Net.Connection; +using Nest.Domain; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Reflection; + +namespace Nest.Resolvers.Converters +{ + + public class SuggestResponseConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotSupportedException(); + } + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var response = new SuggestResponse(); + var jsonObject = JObject.Load(reader); + foreach (var prop in jsonObject.Properties()) + { + if (prop.Name == "_shards") + response.Shards = prop.Value.ToObject(); + else + response.Suggestions.Add(prop.Name, prop.Value.ToObject()); + } + + return response; + } + + public override bool CanConvert(Type objectType) + { + return true; + } + } +} diff --git a/src/Nest/Resolvers/ElasticContractResolver.cs b/src/Nest/Resolvers/ElasticContractResolver.cs index 91f34140cb9..75369ccef11 100644 --- a/src/Nest/Resolvers/ElasticContractResolver.cs +++ b/src/Nest/Resolvers/ElasticContractResolver.cs @@ -72,6 +72,9 @@ protected override JsonContract CreateContract(Type objectType) if (objectType == typeof(PropertyPathMarker)) contract.Converter = new PropertyPathMarkerConverter(this.ConnectionSettings); + if (objectType == typeof(SuggestResponse)) + contract.Converter = new SuggestResponseConverter(); + if (objectType == typeof(MultiSearchResponse)) contract.Converter = new MultiSearchConverter(); diff --git a/src/Tests/Nest.Tests.Integration/Core/Suggest/SuggestTests.cs b/src/Tests/Nest.Tests.Integration/Core/Suggest/SuggestTests.cs new file mode 100644 index 00000000000..49223d81b46 --- /dev/null +++ b/src/Tests/Nest.Tests.Integration/Core/Suggest/SuggestTests.cs @@ -0,0 +1,44 @@ +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using Nest.Tests.MockData.Domain; +using Nest.Resolvers; +using Elasticsearch.Net; + +namespace Nest.Tests.Integration.Core.Suggest +{ + [TestFixture] + public class SuggestTests : IntegrationTests + { + [Test] + public void TestSuggest() + { + var country = this._client.Search(s => s.Size(1)).Documents.First().Country; + var wrongCountry = country + "x"; + + var suggestResults = ((ElasticClient)_client).Suggest(s => s + .Term("mySuggest", m => m + .SuggestMode(SuggestMode.Always) + .Text(wrongCountry) + .Size(1) + .OnField("country") + ) + ); + + suggestResults.IsValid.Should().BeTrue(); + + suggestResults.Shards.Should().NotBeNull(); + suggestResults.Suggestions.Should().NotBeNull().And.HaveCount(1); + var suggestions = suggestResults.Suggestions["mySuggest"]; + suggestions.Should().NotBeNull().And.NotBeEmpty(); + + var suggestion = suggestions.First(); + suggestion.Text.Should().Be(wrongCountry); + var option = suggestion.Options.First(); + option.Text.Should().Be(country); + + } + + + } +} diff --git a/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj b/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj index 866b6968958..ced2e43588d 100644 --- a/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj +++ b/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj @@ -99,6 +99,7 @@ +