Skip to content

Commit e90204f

Browse files
committed
Requests now responsible for building urls, came up with a decent first attempt at cached builders
1 parent 422f2d5 commit e90204f

File tree

13 files changed

+832
-92
lines changed

13 files changed

+832
-92
lines changed

src/CodeGeneration/ApiGenerator/ApiGenerator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.IO;
55
using System.Linq;
66
using ApiGenerator.Domain;
7-
using CsQuery.EquationParser.Implementation;
87
using Newtonsoft.Json.Linq;
98
using RazorLight;
109
using ShellProgressBar;

src/CodeGeneration/ApiGenerator/Views/_Descriptors.Generated.cshtml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ namespace Nest
3737
///<summary>descriptor for @method.FullName <pre>@method.Documentation</pre></summary>
3838
public partial class @Raw(type) @(Raw(string.Format(" : RequestDescriptorBase<{0},{1}, {2}>, {2}", type, method.QueryStringParamName, concreteInterface)))
3939
{
40+
internal override ApiUrls ApiUrls => @(Raw(method.RequestType)).Urls;
4041
@foreach (Constructor c in Constructor.DescriptorConstructors(method))
4142
{
4243
<text> @(Raw(CodeGenerator.Constructor(c)))

src/CodeGeneration/ApiGenerator/Views/_Requests.Generated.cshtml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ namespace Nest
7676
public partial class @Raw(method.RequestType) @Raw(string.Format(": PlainRequestBase<{0}>, {1}", method.QueryStringParamName, method.InterfaceType))
7777
{
7878
protected @(Raw(method.InterfaceType)) Self => this;
79+
internal static ApiUrls Urls = new ApiUrls(new [] {@Raw(string.Join(", ", method.Url.ExposedApiPaths.Select(p=>$"\"{p.Path}\"")))});
80+
internal override ApiUrls ApiUrls => Urls;
7981
@foreach (Constructor c in Constructor.RequestConstructors(method, inheritsFromPlainRequestBase: true))
8082
{
8183
<text> @(Raw(CodeGenerator.Constructor(c)))

src/Nest/CommonAbstractions/LowLevelDispatch/LowLevelDispatch.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,21 @@ internal static string PrettyPrintEndpoint(IRequest request, string endpoint)
4141
var key = m.Groups[1].Value.ToLowerInvariant();
4242
switch (key)
4343
{
44-
case "index": return PrettyParam(key, request.RouteValues.Index);
45-
case "name": return PrettyParam(key, request.RouteValues.Name);
46-
case "feature": return PrettyParam(key, request.RouteValues.Feature);
47-
case "field": return PrettyParam(key, request.RouteValues.Field);
48-
case "fields": return PrettyParam(key, request.RouteValues.Fields);
49-
case "id": return PrettyParam(key, request.RouteValues.Id);
50-
case "index_metric": return PrettyParam(key, request.RouteValues.IndexMetric);
51-
case "lang": return PrettyParam(key, request.RouteValues.Lang);
52-
case "metric": return PrettyParam(key, request.RouteValues.Metric);
53-
case "nodes": return PrettyParam(key, request.RouteValues.NodeId);
54-
case "node_id": return PrettyParam(key, request.RouteValues.NodeId);
55-
case "repository": return PrettyParam(key, request.RouteValues.Repository);
56-
case "scroll_id": return PrettyParam(key, request.RouteValues.ScrollId);
57-
case "snapshot": return PrettyParam(key, request.RouteValues.Snapshot);
58-
case "type": return PrettyParam(key, request.RouteValues.Type);
44+
// case "index": return PrettyParam(key, request.RouteValues.Index);
45+
// case "name": return PrettyParam(key, request.RouteValues.Name);
46+
// case "feature": return PrettyParam(key, request.RouteValues.Feature);
47+
// case "field": return PrettyParam(key, request.RouteValues.Field);
48+
// case "fields": return PrettyParam(key, request.RouteValues.Fields);
49+
// case "id": return PrettyParam(key, request.RouteValues.Id);
50+
// case "index_metric": return PrettyParam(key, request.RouteValues.IndexMetric);
51+
// case "lang": return PrettyParam(key, request.RouteValues.Lang);
52+
// case "metric": return PrettyParam(key, request.RouteValues.Metric);
53+
// case "nodes": return PrettyParam(key, request.RouteValues.NodeId);
54+
// case "node_id": return PrettyParam(key, request.RouteValues.NodeId);
55+
// case "repository": return PrettyParam(key, request.RouteValues.Repository);
56+
// case "scroll_id": return PrettyParam(key, request.RouteValues.ScrollId);
57+
// case "snapshot": return PrettyParam(key, request.RouteValues.Snapshot);
58+
// case "type": return PrettyParam(key, request.RouteValues.Type);
5959
default: return PrettyParam(key, "<Unknown route variable>");
6060
}
6161
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using LookupTuple = System.ValueTuple<System.Func<Nest.RouteValues, bool>, System.Func<Nest.RouteValues, Nest.IConnectionSettingsValues, string>>;
6+
7+
namespace Nest
8+
{
9+
internal class ApiUrls
10+
{
11+
private readonly string _fixedUrl;
12+
public Dictionary<int, List<LookupTuple>> Routes { get; }
13+
14+
private readonly string _errorMessageSuffix;
15+
16+
/// <summary> Only intended to be created once per request and stored in a static </summary>
17+
internal ApiUrls(string[] routes)
18+
{
19+
if (routes == null || routes.Length == 0) throw new ArgumentException(nameof(routes), "urls is null or empty");
20+
if (routes.Length == 1 && !routes[0].Contains("{")) _fixedUrl = routes[0];
21+
else
22+
{
23+
foreach (var route in routes)
24+
{
25+
var bracketsCount = route.Count(c => c.Equals('{'));
26+
if (Routes == null) Routes = new Dictionary<int, List<LookupTuple>>();
27+
if (Routes.ContainsKey(bracketsCount))
28+
Routes[bracketsCount].Add(FromRoute(route));
29+
else
30+
Routes.Add(bracketsCount, new List<LookupTuple>() { FromRoute(route) });
31+
}
32+
}
33+
34+
_errorMessageSuffix = string.Join(",", routes);
35+
36+
// received multiple urls without brackets we resolve to first
37+
if (Routes == null) _fixedUrl = routes[0];
38+
}
39+
40+
private static LookupTuple FromRoute(string route)
41+
{
42+
var tokenized = route.Replace("{", "{@")
43+
.Split(new[] { '{', '}' }, StringSplitOptions.RemoveEmptyEntries);
44+
45+
var parts = tokenized
46+
.Where(p => p.StartsWith("@"))
47+
.Select(p => p.Remove(0, 1))
48+
.ToArray();
49+
50+
Func<RouteValues, bool> lookup;
51+
Func<RouteValues, IConnectionSettingsValues, string> toString;
52+
lookup = r => parts.All(p => r.ContainsKey(p));
53+
toString = (r ,s) =>
54+
{
55+
var sb = new StringBuilder();
56+
var i = 0;
57+
foreach (var t in tokenized)
58+
{
59+
if (t[0] == '@')
60+
{
61+
if (r.TryGetValue(parts[i], out var v))
62+
sb.Append(Uri.EscapeDataString(v.GetString(s)));
63+
else throw new Exception($"No value provided for {parts[i]} on url {route}");
64+
65+
i++;
66+
}
67+
else sb.Append(t);
68+
}
69+
return sb.ToString();
70+
};
71+
return (lookup, toString);
72+
}
73+
74+
public string Resolve(RouteValues routeValues, IConnectionSettingsValues settings)
75+
{
76+
if (_fixedUrl != null) return _fixedUrl;
77+
if (!Routes.TryGetValue(routeValues.Count, out var routes))
78+
throw new Exception($"No route taking {routeValues.Count} parameters" + _errorMessageSuffix);
79+
80+
if (routes.Count == 1)
81+
return routes[0].Item2(routeValues, settings);
82+
83+
//find the first url that has all provided parameters
84+
foreach (var u in routes)
85+
{
86+
if (u.Item1(routeValues))
87+
return u.Item2(routeValues, settings);
88+
}
89+
throw new Exception($"No route taking {routeValues.Count} parameters" + _errorMessageSuffix);
90+
}
91+
}
92+
}

src/Nest/CommonAbstractions/Request/RequestBase.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public interface IRequest
1717
// TODO refactor RequestParameters
1818
[IgnoreDataMember]
1919
IRequestParameters RequestParametersInternal { get; }
20+
21+
string GetUrl(IConnectionSettingsValues settings);
2022
}
2123

2224
public interface IRequest<TParameters> : IRequest
@@ -54,6 +56,10 @@ protected RequestBase(Func<RouteValues, RouteValues> pathSelector)
5456
[IgnoreDataMember]
5557
RouteValues IRequest.RouteValues { get; } = new RouteValues();
5658

59+
internal abstract ApiUrls ApiUrls { get; }
60+
61+
string IRequest.GetUrl(IConnectionSettingsValues settings) => ApiUrls.Resolve(RequestState.RouteValues, settings);
62+
5763
IRequestParameters IRequest.RequestParametersInternal => RequestState.RequestParameters;
5864

5965
// TODO remove this is only used to make sure requests set typed_keys automatically, find better approach for this
Lines changed: 15 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,25 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using Elasticsearch.Net;
34

45
namespace Nest
56
{
6-
public class RouteValues
7+
public class RouteValues : Dictionary<string, IUrlParameter>
78
{
8-
private readonly Dictionary<string, string> _resolved = new Dictionary<string, string>();
9-
private readonly Dictionary<string, IUrlParameter> _routeValues = new Dictionary<string, IUrlParameter>();
10-
public string ActionId => GetResolved("action_id");
11-
public string Alias => GetResolved("alias");
12-
public string CategoryId => GetResolved("category_id");
13-
public string ForecastId => GetResolved("forecast_id");
14-
public string Context => GetResolved("context");
15-
public string DatafeedId => GetResolved("datafeed_id");
16-
public string Feature => GetResolved("feature");
17-
public string Field => GetResolved("field");
18-
public string Fields => GetResolved("fields");
19-
public string FilterId => GetResolved("filter_id");
20-
public string Id => GetResolved("id");
21-
public string CalendarId => GetResolved("calendar_id");
22-
public string EventId => GetResolved("event_id");
23-
24-
public string Index => GetResolved("index");
25-
public string IndexMetric => GetResolved("index_metric");
26-
public string JobId => GetResolved("job_id");
27-
public string Lang => GetResolved("lang");
28-
public string Metric => GetResolved("metric");
29-
public string Name => GetResolved("name");
30-
public string NewIndex => GetResolved("new_index");
31-
public string NodeId => GetResolved("node_id");
32-
public string Realms => GetResolved("realms");
33-
public string Repository => GetResolved("repository");
34-
public string ScrollId => GetResolved("scroll_id");
35-
public string Snapshot => GetResolved("snapshot");
36-
public string SnapshotId => GetResolved("snapshot_id");
37-
public string Target => GetResolved("target");
38-
public string TaskId => GetResolved("task_id");
39-
public string ThreadPoolPatterns => GetResolved("thread_pool_patterns");
40-
public string Timestamp => GetResolved("timestamp");
41-
public string Type => GetResolved("type");
42-
public string Application => GetResolved("application");
43-
public string User => GetResolved("user");
44-
public string Username => GetResolved("username");
45-
public WatcherStatsMetric? WatcherStatsMetric => GetResolved("watcher_stats_metric").ToEnum<WatcherStatsMetric>();
46-
public string WatchId => GetResolved("watch_id");
47-
48-
private string GetResolved(string route) => _resolved.TryGetValue(route, out var resolved) ? resolved : null;
9+
// TODO Remove
10+
public string Id => throw new Exception("need to be refactored out");
4911

5012
private RouteValues Route(string name, IUrlParameter routeValue, bool required = true)
5113
{
52-
if (routeValue == null && !required)
53-
{
54-
if (_routeValues.ContainsKey(name))
55-
_routeValues.Remove(name);
56-
return this;
57-
}
58-
if (routeValue == null) return this;
59-
60-
_routeValues[name] = routeValue;
61-
return this;
62-
}
63-
64-
public void Resolve(IConnectionSettingsValues settings)
65-
{
66-
foreach (var kv in _routeValues)
67-
{
68-
var key = kv.Value.GetString(settings);
69-
_resolved[kv.Key] = key.IsNullOrEmpty() ? key : key;
14+
switch (routeValue) {
15+
case null when !required: {
16+
if (ContainsKey(name)) Remove(name);
17+
return this;
18+
}
19+
case null: throw new ArgumentNullException(name, $"{name} is required to build a url to this API");
20+
default:
21+
this[name] = routeValue;
22+
return this;
7023
}
7124
}
7225

@@ -81,16 +34,10 @@ public void Resolve(IConnectionSettingsValues settings)
8134
internal TActual Get<TActual>(string route) where TActual : class, IUrlParameter
8235
{
8336
IUrlParameter actual;
84-
if (_routeValues.TryGetValue(route, out actual) && actual != null)
37+
if (TryGetValue(route, out actual) && actual != null)
8538
return (TActual)actual;
8639

8740
return null;
8841
}
89-
90-
public void Remove(string route)
91-
{
92-
_resolved.Remove(route);
93-
_routeValues.Remove(route);
94-
}
9542
}
9643
}

src/Nest/Document/Single/Source/ElasticClient-Source.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ public Task<TDocument> SourceAsync<TDocument>(
5959
/// <inheritdoc />
6060
public async Task<TDocument> SourceAsync<TDocument>(ISourceRequest request, CancellationToken ct = default) where TDocument : class
6161
{
62-
request.RouteValues.Resolve(ConnectionSettings);
6362
request.RequestParameters.DeserializationOverride = ToSourceResponse<TDocument>;
6463
var result = await DoRequestAsync<ISourceRequest, ISourceResponse<TDocument>, SourceResponse<TDocument>>(request, request.RequestParameters, ct)
6564
.ConfigureAwait(false);

src/Nest/ElasticClient.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ internal TResponse DoRequest<TRequest, TResponse>(TRequest p, IRequestParameters
4545
{
4646
if (forceConfiguration != null) ForceConfiguration(p, forceConfiguration);
4747

48-
p.RouteValues.Resolve(ConnectionSettings);
48+
var url = p.GetUrl(ConnectionSettings);
4949
var b = (p.HttpMethod == HttpMethod.GET || p.HttpMethod == HttpMethod.HEAD) ? null : new SerializableData<TRequest>(p);
5050

51-
return LowLevel.DoRequest<TResponse>(p.HttpMethod, p.RouteValues.ToString(), b, parameters);
51+
return LowLevel.DoRequest<TResponse>(p.HttpMethod, url, b, parameters);
5252
}
5353

5454
internal Task<TResponseInterface> DoRequestAsync<TRequest, TResponseInterface, TResponse>(
@@ -63,18 +63,18 @@ internal Task<TResponseInterface> DoRequestAsync<TRequest, TResponseInterface, T
6363
{
6464
if (forceConfiguration != null) ForceConfiguration(p, forceConfiguration);
6565

66-
p.RouteValues.Resolve(ConnectionSettings);
66+
var url = p.GetUrl(ConnectionSettings);
6767
var b = (p.HttpMethod == HttpMethod.GET || p.HttpMethod == HttpMethod.HEAD) ? null : new SerializableData<TRequest>(p);
6868

69-
return LowLevel.DoRequestAsync<TResponse>(p.HttpMethod, p.RouteValues.ToString(), ct, b, parameters)
69+
return LowLevel.DoRequestAsync<TResponse>(p.HttpMethod, url, ct, b, parameters)
7070
.ToBaseTask<TResponse, TResponseInterface>();
7171
}
7272

7373
private static void ForceConfiguration(IRequest request, Action<IRequestConfiguration> forceConfiguration)
7474
{
7575
if (forceConfiguration == null) return;
7676
var configuration = request.RequestParametersInternal.RequestConfiguration ?? new RequestConfiguration();
77-
forceConfiguration(request.RequestParametersInternal.RequestConfiguration);
77+
forceConfiguration(configuration);
7878
request.RequestParametersInternal.RequestConfiguration = configuration;
7979
}
8080

0 commit comments

Comments
 (0)