From 498eaa8059174a46a43207bb0362d95fadccf3dc Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 2 Sep 2014 15:54:48 +0200 Subject: [PATCH 01/15] added template API syntax spike as a unit tests so @gmarz and I can itterate over it together --- .../Template/TemplateSpikeTests.cs | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/Tests/Nest.Tests.Unit/Template/TemplateSpikeTests.cs diff --git a/src/Tests/Nest.Tests.Unit/Template/TemplateSpikeTests.cs b/src/Tests/Nest.Tests.Unit/Template/TemplateSpikeTests.cs new file mode 100644 index 00000000000..5553d406db7 --- /dev/null +++ b/src/Tests/Nest.Tests.Unit/Template/TemplateSpikeTests.cs @@ -0,0 +1,132 @@ +using System; +using Elasticsearch.Net; +using Elasticsearch.Net.Connection; +using FluentAssertions; +using Newtonsoft.Json; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Nest.Tests.Unit.Template +{ + [TestFixture] + public class TemplateSpikeTests : BaseJsonTests + { + [Test] + public void Print() + { + + var search = this._client.Search(s => s + .Strict() + .Query(q => + Template.If("start", + q.Range(r => r + .OnField("fieldx") + .Greater(Template.Variable("start")) + .Lower(Template.Variable("end", "20")) + ) + ) + && Template.If("other_var", new TermQuery() + { + Field = Template.Variable("myfield"), + Value = Template.Variable("myval") + }) + && q.Terms("field", Template.Array("array")) + ) + .Sort(Template.If("sort", sort => sort.Ascending().OnField("{{sort}}"))) + ); + Assert.Fail(search.ConnectionStatus.ToString()); + } + + } + + interface IIfTemplate + { + object Instance { get; } + string Variable { get; } + } + // Define other methods and classes here + public static class Template + { + [JsonConverter(typeof(IfTemplateConverter))] + private class IfSortFieldDescriptor : SortFieldDescriptor, IIfTemplate + { + private readonly IFieldSort _queryContainer; + private readonly string _variable; + object IIfTemplate.Instance { get { return _queryContainer; } } + string IIfTemplate.Variable { get { return _variable; } } + public IfSortFieldDescriptor(string variable, IFieldSort o) + { + _variable = variable; + _queryContainer = o; + } + } + + + [JsonConverter(typeof(IfTemplateConverter))] + private class IfQueryContainer : QueryContainer, IIfTemplate + { + private readonly QueryContainer _queryContainer; + private readonly string _variable; + object IIfTemplate.Instance { get { return _queryContainer; } } + string IIfTemplate.Variable { get { return _variable; } } + public IfQueryContainer(string variable, QueryContainer o) + { + _variable = variable; + _queryContainer = o; + } + } + public static Func, IFieldSort> If(string variable, Func, IFieldSort> s) + where T : class + { + return new Func, IFieldSort>(inner => new IfSortFieldDescriptor(variable, s(inner))); + } + public static QueryContainer If(string variable, QueryContainer o) + { + return new IfQueryContainer(variable, o); + } + + public static string Variable(string name, string defaultValue = null) + { + if (!string.IsNullOrWhiteSpace(defaultValue)) + return "{{" + name + "}}{{^" + name + "}}" + defaultValue + "{{/" + name + "}}"; + return "{{" + name + "}}"; + } + public static IEnumerable Array(string name) + { + return new[] { + "{{#"+name+"}", + "{{.}}", + "{{/" + name + "}" + + }; + } + } + + public class IfTemplateConverter : JsonConverter + { + public override bool CanWrite { get { return true; } } + + public override bool CanRead { get { return false; } } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var v = value as IIfTemplate; + if (v == null) writer.WriteNull(); + + writer.WriteRaw("{{#" + v.Variable + "}}"); + serializer.Serialize(writer, v.Instance); + writer.WriteRaw("{{/" + v.Variable + "}}"); + + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return null; + } + + public override bool CanConvert(Type objectType) + { + return true; + } + } +} From 19f2145474261f91343ae130cdec2c4b8f28c78f Mon Sep 17 00:00:00 2001 From: Greg Marzouka Date: Tue, 2 Sep 2014 23:44:03 -0400 Subject: [PATCH 02/15] Refactoring of initial search template implementation * Mapped to search template endpoint * Class/method renaming and folder structuring * Unit tests to demonstrate syntax --- src/Nest/DSL/SearchTemplateDescriptor.cs | 200 ++++++++++++++++++ .../Template/TemplateSectionQueryContainer.cs | 24 +++ .../TemplateSectionSortFieldDescriptor.cs | 23 ++ .../CreateWarmerDescriptor.cs | 0 src/Nest/DSL/_Descriptors.generated.cs | 22 +- src/Nest/DSL/_Requests.generated.cs | 74 ++++++- src/Nest/Domain/Template/ITemplateSection.cs | 13 ++ src/Nest/Domain/Template/TemplateBuilder.cs | 38 ++++ src/Nest/ElasticClient-SearchTemplate.cs | 63 ++++++ src/Nest/IElasticClient.cs | 3 + src/Nest/Nest.csproj | 9 +- .../Converters/TemplateSectionConverter.cs | 35 +++ .../Nest.Tests.Unit/Nest.Tests.Unit.csproj | 4 + .../Template/TemplateSpikeTests.cs | 153 +++++--------- 14 files changed, 544 insertions(+), 117 deletions(-) create mode 100644 src/Nest/DSL/SearchTemplateDescriptor.cs create mode 100644 src/Nest/DSL/Template/TemplateSectionQueryContainer.cs create mode 100644 src/Nest/DSL/Template/TemplateSectionSortFieldDescriptor.cs rename src/Nest/DSL/{Termplates => Warmers}/CreateWarmerDescriptor.cs (100%) create mode 100644 src/Nest/Domain/Template/ITemplateSection.cs create mode 100644 src/Nest/Domain/Template/TemplateBuilder.cs create mode 100644 src/Nest/ElasticClient-SearchTemplate.cs create mode 100644 src/Nest/Resolvers/Converters/TemplateSectionConverter.cs diff --git a/src/Nest/DSL/SearchTemplateDescriptor.cs b/src/Nest/DSL/SearchTemplateDescriptor.cs new file mode 100644 index 00000000000..42567163b8d --- /dev/null +++ b/src/Nest/DSL/SearchTemplateDescriptor.cs @@ -0,0 +1,200 @@ +using Elasticsearch.Net; +using Nest.DSL.Descriptors; +using Nest.Resolvers.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace Nest +{ + internal static class SearchTemplatePathInfo + { + public static void Update(ElasticsearchPathInfo pathInfo) + { + pathInfo.HttpMethod = PathInfoHttpMethod.POST; + } + + /// + /// Based on the type information present in this descriptor create method that takes + /// the returned _source and hit and returns the ClrType it should deserialize too. + /// This is so that Documents[A] can contain actual instances of subclasses B, C as well. + /// If you specify types using .Types(typeof(B), typeof(C)) then NEST can automagically + /// create a TypeSelector based on the hits _type property. + /// + public static void CloseOverAutomagicCovariantResultSelector(ElasticInferrer infer, ISearchTemplateRequest self) + { + if (infer == null || self == null) return; + var returnType = self.ClrType; + + if (returnType == null) return; + + var types = (self.Types ?? Enumerable.Empty()).Where(t => t.Type != null).ToList(); + if (self.TypeSelector != null || !types.HasAny(t => t.Type != returnType)) + return; + + var typeDictionary = types.ToDictionary(infer.TypeName, t => t.Type); + self.TypeSelector = (o, h) => + { + Type t; + return !typeDictionary.TryGetValue(h.Type, out t) ? returnType : t; + }; + } + } + + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] + public interface ISearchTemplateRequest + : IQueryPath + { + [JsonProperty("params")] + IDictionary Params { get; set; } + + Type ClrType { get; } + + [JsonProperty(PropertyName = "sort")] + [JsonConverter(typeof(SortCollectionConverter))] + IList> Sort { get; set; } + + [JsonProperty(PropertyName = "template")] + SearchTemplate Template { get; set; } + + Func, Type> TypeSelector { get; set; } + } + + public interface ISearchTemplateRequest : ISearchTemplateRequest { } + + public partial class SearchTemplateRequest + : QueryPathBase, ISearchTemplateRequest + { + public SearchTemplate Template { get; set; } + public IDictionary Params { get; set; } + private Type _clrType { get; set; } + Type ISearchTemplateRequest.ClrType { get { return _clrType; } } + public IList> Sort { get; set; } + public Func, Type> TypeSelector { get; set; } + + protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) + { + SearchTemplatePathInfo.Update(pathInfo); + } + } + + public partial class SearchTemplateRequest + : QueryPathBase, ISearchTemplateRequest + where T : class + { + public SearchTemplate Template { get; set; } + public IDictionary Params { get; set; } + public Type ClrType { get { return typeof(T); } } + public IList> Sort { get; set; } + public Func, Type> TypeSelector { get; set; } + + protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) + { + SearchTemplatePathInfo.Update(pathInfo); + } + } + + public partial class SearchTemplateDescriptor + : QueryPathDescriptorBase, SearchTemplateRequestParameters, T>, ISearchTemplateRequest + where T : class + { + ISearchTemplateRequest Self { get { return this; } } + + Type ISearchTemplateRequest.ClrType { get { return typeof(T); } } + + /// + /// Whether conditionless queries are allowed or not + /// + internal bool _Strict { get; set; } + + SearchTemplate ISearchTemplateRequest.Template { get; set; } + + IDictionary ISearchTemplateRequest.Params { get; set; } + + IList> ISearchTemplateRequest.Sort { get; set; } + + Func, Type> ISearchTemplateRequest.TypeSelector { get; set; } + + public SearchTemplateDescriptor Template(Func templateSelector) + { + templateSelector.ThrowIfNull("templateSelector"); + var descriptor = templateSelector(new TemplateDescriptor()); + this.Self.Template = descriptor.Template; + return this; + } + + public SearchTemplateDescriptor Params(IDictionary parameters) + { + this.Self.Params = parameters; + return this; + } + + /// + /// Sort() allows you to fully describe your sort unlike the SortAscending and SortDescending aliases. + /// + /// + public SearchTemplateDescriptor Sort(Func, IFieldSort> sortSelector) + { + if (Self.Sort == null) + Self.Sort = new List>(); + + sortSelector.ThrowIfNull("sortSelector"); + var descriptor = sortSelector(new SortFieldDescriptor()); + Self.Sort.Add(new KeyValuePair(descriptor.Field, descriptor)); + return this; + } + + public SearchTemplateDescriptor ConcreteTypeSelector(Func, Type> typeSelector) + { + Self.TypeSelector = typeSelector; + return this; + } + + protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) + { + SearchTemplatePathInfo.Update(pathInfo); + } + } + + [JsonObject] + public class SearchTemplate + { + [JsonProperty("file")] + public string File { get; set; } + + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("query")] + public IQueryContainer Query { get; set; } + } + + public class TemplateDescriptor + { + public SearchTemplate Template = new SearchTemplate(); + + public TemplateDescriptor File(string file) + { + Template.File = file; + return this; + } + + public TemplateDescriptor Id(string id) + { + Template.Id = id; + return this; + } + + public TemplateDescriptor Query(Func, QueryContainer> query) where T : class + { + query.ThrowIfNull("query"); + var q = new QueryDescriptor(); + ((IQueryContainer)q).IsStrict = true; + Template.Query = query(q); + return this; + } + } +} diff --git a/src/Nest/DSL/Template/TemplateSectionQueryContainer.cs b/src/Nest/DSL/Template/TemplateSectionQueryContainer.cs new file mode 100644 index 00000000000..87e017f3c28 --- /dev/null +++ b/src/Nest/DSL/Template/TemplateSectionQueryContainer.cs @@ -0,0 +1,24 @@ +using Nest.Resolvers.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Nest +{ + [JsonConverter(typeof(TemplateSectionConverter))] + public class TemplateSectionQueryContainer : QueryContainer, ITemplateSection + { + private readonly QueryContainer _queryContainer; + private readonly string _variable; + object ITemplateSection.Instance { get { return _queryContainer; } } + string ITemplateSection.Variable { get { return _variable; } } + + public TemplateSectionQueryContainer(string variable, QueryContainer o) + { + _variable = variable; + _queryContainer = o; + } + } +} diff --git a/src/Nest/DSL/Template/TemplateSectionSortFieldDescriptor.cs b/src/Nest/DSL/Template/TemplateSectionSortFieldDescriptor.cs new file mode 100644 index 00000000000..9c936b0a21c --- /dev/null +++ b/src/Nest/DSL/Template/TemplateSectionSortFieldDescriptor.cs @@ -0,0 +1,23 @@ +using Nest.Resolvers.Converters; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Nest +{ + [JsonConverter(typeof(TemplateSectionConverter))] + public class TemplateSectionSortFieldDescriptor : SortFieldDescriptor, ITemplateSection + { + private readonly IFieldSort _queryContainer; + private readonly string _variable; + object ITemplateSection.Instance { get { return _queryContainer; } } + string ITemplateSection.Variable { get { return _variable; } } + public TemplateSectionSortFieldDescriptor(string variable, IFieldSort o) + { + _variable = variable; + _queryContainer = o; + } + } +} diff --git a/src/Nest/DSL/Termplates/CreateWarmerDescriptor.cs b/src/Nest/DSL/Warmers/CreateWarmerDescriptor.cs similarity index 100% rename from src/Nest/DSL/Termplates/CreateWarmerDescriptor.cs rename to src/Nest/DSL/Warmers/CreateWarmerDescriptor.cs diff --git a/src/Nest/DSL/_Descriptors.generated.cs b/src/Nest/DSL/_Descriptors.generated.cs index 6d38d281adf..de4c770f304 100644 --- a/src/Nest/DSL/_Descriptors.generated.cs +++ b/src/Nest/DSL/_Descriptors.generated.cs @@ -4858,13 +4858,13 @@ protected override void UpdatePathInfo(IConnectionSettingsValues settings, Elast ///http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-template.html /// /// - public partial class SearchTemplateDescriptor : BaseRequest + public partial class SearchTemplateDescriptor { ///Whether specified concrete indices should be ignored when unavailable (missing or closed) - public SearchTemplateDescriptor IgnoreUnavailable(bool ignore_unavailable = true) + public SearchTemplateDescriptor IgnoreUnavailable(bool ignore_unavailable = true) { this.Request.RequestParameters.IgnoreUnavailable(ignore_unavailable); return this; @@ -4872,7 +4872,7 @@ public SearchTemplateDescriptor 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 SearchTemplateDescriptor AllowNoIndices(bool allow_no_indices = true) + public SearchTemplateDescriptor AllowNoIndices(bool allow_no_indices = true) { this.Request.RequestParameters.AllowNoIndices(allow_no_indices); return this; @@ -4880,7 +4880,7 @@ public SearchTemplateDescriptor AllowNoIndices(bool allow_no_indices = true) ///Whether to expand wildcard expression to concrete indices that are open, closed or both. - public SearchTemplateDescriptor ExpandWildcards(ExpandWildcards expand_wildcards) + public SearchTemplateDescriptor ExpandWildcards(ExpandWildcards expand_wildcards) { this.Request.RequestParameters.ExpandWildcards(expand_wildcards); return this; @@ -4888,7 +4888,7 @@ public SearchTemplateDescriptor ExpandWildcards(ExpandWildcards expand_wildcards ///Specify the node or shard the operation should be performed on (default: random) - public SearchTemplateDescriptor Preference(string preference) + public SearchTemplateDescriptor Preference(string preference) { this.Request.RequestParameters.Preference(preference); return this; @@ -4896,7 +4896,7 @@ public SearchTemplateDescriptor Preference(string preference) ///A comma-separated list of specific routing values - public SearchTemplateDescriptor Routing(params string[] routing) + public SearchTemplateDescriptor Routing(params string[] routing) { this.Request.RequestParameters.Routing(routing); return this; @@ -4904,7 +4904,7 @@ public SearchTemplateDescriptor Routing(params string[] routing) ///Specify how long a consistent view of the index should be maintained for scrolled search - public SearchTemplateDescriptor Scroll(string scroll) + public SearchTemplateDescriptor Scroll(string scroll) { this.Request.RequestParameters.Scroll(scroll); return this; @@ -4912,18 +4912,12 @@ public SearchTemplateDescriptor Scroll(string scroll) ///Search operation type - public SearchTemplateDescriptor SearchType(SearchType search_type) + public SearchTemplateDescriptor SearchType(SearchType search_type) { this.Request.RequestParameters.SearchType(search_type); return this; } - - protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) - { - throw new NotImplementedException(); - } - } diff --git a/src/Nest/DSL/_Requests.generated.cs b/src/Nest/DSL/_Requests.generated.cs index bafcba1be34..89eb2f87753 100644 --- a/src/Nest/DSL/_Requests.generated.cs +++ b/src/Nest/DSL/_Requests.generated.cs @@ -4359,7 +4359,7 @@ protected override void UpdatePathInfo(IConnectionSettingsValues settings, Elast ///http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-template.html /// /// - public partial class SearchTemplateRequest : BasePathRequest + public partial class SearchTemplateRequest { ///Whether specified concrete indices should be ignored when unavailable (missing or closed) @@ -4417,12 +4417,6 @@ public SearchType SearchType set { this.Request.RequestParameters.AddQueryString("search_type", value); } } - - protected override void UpdatePathInfo(IConnectionSettingsValues settings, ElasticsearchPathInfo pathInfo) - { - throw new NotImplementedException(); - } - } @@ -6067,6 +6061,72 @@ public string SuggestText } + ///Request parameters for SearchTemplateGet + ///
+	///http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-template.html
+	///
+ ///
+ public partial class SearchTemplateRequest + { + + ///Whether specified concrete indices should be ignored when unavailable (missing or closed) + public bool IgnoreUnavailable + { + get { return this.Request.RequestParameters.GetQueryStringValue("ignore_unavailable"); } + set { this.Request.RequestParameters.AddQueryString("ignore_unavailable", value); } + } + + + ///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 bool AllowNoIndices + { + get { return this.Request.RequestParameters.GetQueryStringValue("allow_no_indices"); } + set { this.Request.RequestParameters.AddQueryString("allow_no_indices", value); } + } + + + ///Whether to expand wildcard expression to concrete indices that are open, closed or both. + public ExpandWildcards ExpandWildcards + { + get { return this.Request.RequestParameters.GetQueryStringValue("expand_wildcards"); } + set { this.Request.RequestParameters.AddQueryString("expand_wildcards", value); } + } + + + ///Specify the node or shard the operation should be performed on (default: random) + public string Preference + { + get { return this.Request.RequestParameters.GetQueryStringValue("preference"); } + set { this.Request.RequestParameters.AddQueryString("preference", value); } + } + + + ///A comma-separated list of specific routing values + public string[] Routing + { + get { return this.Request.RequestParameters.GetQueryStringValue< string[]>("routing"); } + set { this.Request.RequestParameters.AddQueryString("routing", value); } + } + + + ///Specify how long a consistent view of the index should be maintained for scrolled search + public string Scroll + { + get { return this.Request.RequestParameters.GetQueryStringValue("scroll"); } + set { this.Request.RequestParameters.AddQueryString("scroll", value); } + } + + + ///Search operation type + public SearchType SearchType + { + get { return this.Request.RequestParameters.GetQueryStringValue("search_type"); } + set { this.Request.RequestParameters.AddQueryString("search_type", value); } + } + + } + + ///Request parameters for TermvectorGet ///
 	///http://www.elasticsearch.org/guide/en/elasticsearch/reference/1.x/docs-termvectors.html
diff --git a/src/Nest/Domain/Template/ITemplateSection.cs b/src/Nest/Domain/Template/ITemplateSection.cs
new file mode 100644
index 00000000000..bbf69359fd2
--- /dev/null
+++ b/src/Nest/Domain/Template/ITemplateSection.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Nest
+{
+	public interface ITemplateSection
+	{
+		object Instance { get; }
+		string Variable { get; }
+	}
+}
diff --git a/src/Nest/Domain/Template/TemplateBuilder.cs b/src/Nest/Domain/Template/TemplateBuilder.cs
new file mode 100644
index 00000000000..acb78966d94
--- /dev/null
+++ b/src/Nest/Domain/Template/TemplateBuilder.cs
@@ -0,0 +1,38 @@
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Nest
+{
+	public static class TemplateBuilder
+	{
+		public static Func, IFieldSort> Section(string variable, Func, IFieldSort> s)
+			where T : class
+		{
+			return new Func, IFieldSort>(inner => new TemplateSectionSortFieldDescriptor(variable, s(inner)));
+		}
+
+		public static QueryContainer Section(string variable, QueryContainer o)
+		{
+			return new TemplateSectionQueryContainer(variable, o);
+		}
+
+		public static string Variable(string name, string defaultValue = null)
+		{
+			if (!string.IsNullOrWhiteSpace(defaultValue))
+				return "{{" + name + "}}{{^" + name + "}}" + defaultValue + "{{/" + name + "}}";
+			return "{{" + name + "}}";
+		}
+
+		public static IEnumerable Array(string name)
+		{
+			return new[] {
+				"{{#"+name+"}",
+				"{{.}}",
+				"{{/" + name + "}"
+			};
+		}
+	}
+}
diff --git a/src/Nest/ElasticClient-SearchTemplate.cs b/src/Nest/ElasticClient-SearchTemplate.cs
new file mode 100644
index 00000000000..65f1569a009
--- /dev/null
+++ b/src/Nest/ElasticClient-SearchTemplate.cs
@@ -0,0 +1,63 @@
+using Elasticsearch.Net;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace Nest
+{
+	public partial class ElasticClient
+	{
+		public ISearchResponse SearchTemplate(Func, SearchTemplateDescriptor> selector) where T : class
+		{
+			return this.SearchTemplate(selector);
+		}
+
+		public ISearchResponse SearchTemplate(Func, SearchTemplateDescriptor> selector)
+			where T : class
+			where TResult : class
+		{
+			selector.ThrowIfNull("searchSelector");
+			var descriptor = selector(new SearchTemplateDescriptor());
+
+			IPathInfo p = descriptor;
+			var pathInfo = p
+				.ToPathInfo(_connectionSettings)
+				.DeserializationState(this.CreateSearchDeserializer(descriptor));
+
+			var status = this.RawDispatch.SearchTemplateDispatch>(pathInfo, descriptor);
+			return status.Success ? status.Response : CreateInvalidInstance>(status);
+		}
+
+		private SearchResponse FieldsSearchDeserializer(IElasticsearchResponse response, Stream stream, ISearchTemplateRequest d)
+			where T : class
+			where TResult : class
+		{
+			var converter = this.CreateCovariantSearchSelector(d);
+			var dict = response.Success
+				? Serializer.DeserializeInternal>(stream, converter)
+				: null;
+			return dict;
+		}
+
+		private Func> CreateSearchDeserializer(ISearchTemplateRequest request)
+			where T : class
+			where TResult : class
+		{
+
+			Func> responseCreator =
+					(r, s) => this.FieldsSearchDeserializer(r, s, request);
+			return responseCreator;
+		}
+
+		private JsonConverter CreateCovariantSearchSelector(ISearchTemplateRequest originalSearchDescriptor)
+			where T : class
+			where TResult : class
+		{
+			SearchTemplatePathInfo.CloseOverAutomagicCovariantResultSelector(this.Infer, originalSearchDescriptor);
+			return originalSearchDescriptor.TypeSelector == null ? null : new ConcreteTypeConverter(originalSearchDescriptor.TypeSelector);
+		}
+	}
+}
diff --git a/src/Nest/IElasticClient.cs b/src/Nest/IElasticClient.cs
index c5350819b77..c201ff0ac6d 100644
--- a/src/Nest/IElasticClient.cs
+++ b/src/Nest/IElasticClient.cs
@@ -774,6 +774,9 @@ Task> SearchAsync(ISearchRequest request)
 			where T : class
 			where TResult : class;
 
+		ISearchResponse SearchTemplate(Func, SearchTemplateDescriptor> selector)
+			where T : class;
+
 		/// 
 		/// The multi search API allows to execute several search requests within the same API.
 		/// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-multi-search.html
diff --git a/src/Nest/Nest.csproj b/src/Nest/Nest.csproj
index 1bd5fd157d2..aa5384f9efd 100644
--- a/src/Nest/Nest.csproj
+++ b/src/Nest/Nest.csproj
@@ -194,6 +194,10 @@
     
     
     
+    
+    
+    
+    
     
     
     
@@ -209,6 +213,7 @@
     
     
     
+    
     
     
     
@@ -299,6 +304,7 @@
     
     
     
+    
     
     
     
@@ -542,7 +548,7 @@
       Code
     
     
-    
+    
     
     
     
@@ -891,6 +897,7 @@
     
     
     
+    
     
     
     
diff --git a/src/Nest/Resolvers/Converters/TemplateSectionConverter.cs b/src/Nest/Resolvers/Converters/TemplateSectionConverter.cs
new file mode 100644
index 00000000000..2f004ba5cb3
--- /dev/null
+++ b/src/Nest/Resolvers/Converters/TemplateSectionConverter.cs
@@ -0,0 +1,35 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Nest.Resolvers.Converters
+{
+	public class TemplateSectionConverter : JsonConverter
+	{
+		public override bool CanWrite { get { return true; } }
+
+		public override bool CanRead { get { return false; } }
+
+		public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+		{
+			var v = value as ITemplateSection;
+			if (v == null) writer.WriteNull();
+
+			writer.WriteRaw("{{#" + v.Variable + "}}");
+			serializer.Serialize(writer, v.Instance);
+			writer.WriteRaw("{{/" + v.Variable + "}}");
+		}
+
+		public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+		{
+			return null;
+		}
+
+		public override bool CanConvert(Type objectType)
+		{
+			return true;
+		}
+	}
+}
diff --git a/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj b/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj
index 77e9441ad9a..aaafb84ca2e 100644
--- a/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj
+++ b/src/Tests/Nest.Tests.Unit/Nest.Tests.Unit.csproj
@@ -505,6 +505,9 @@
     
     
     
+    
+      Code
+    
     
     
   
@@ -1246,6 +1249,7 @@
       Always
     
   
+