Skip to content

Commit 9454a97

Browse files
committed
Add product check implementation
1 parent af23b3b commit 9454a97

File tree

12 files changed

+345
-37
lines changed

12 files changed

+345
-37
lines changed

Elasticsearch.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
4848
EndProject
4949
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elastic.Clients.Elasticsearch", "src\Elastic.Clients.Elasticsearch\Elastic.Clients.Elasticsearch.csproj", "{F8A7E60C-0C48-4D76-AF7F-7881DF5A263D}"
5050
EndProject
51+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlaygroundFx", "src\PlaygroundFx\PlaygroundFx.csproj", "{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}"
52+
EndProject
5153
Global
5254
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5355
Debug|Any CPU = Debug|Any CPU
@@ -178,6 +180,18 @@ Global
178180
{F8A7E60C-0C48-4D76-AF7F-7881DF5A263D}.Release|x64.Build.0 = Release|Any CPU
179181
{F8A7E60C-0C48-4D76-AF7F-7881DF5A263D}.Release|x86.ActiveCfg = Release|Any CPU
180182
{F8A7E60C-0C48-4D76-AF7F-7881DF5A263D}.Release|x86.Build.0 = Release|Any CPU
183+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
184+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Debug|Any CPU.Build.0 = Debug|Any CPU
185+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Debug|x64.ActiveCfg = Debug|Any CPU
186+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Debug|x64.Build.0 = Debug|Any CPU
187+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Debug|x86.ActiveCfg = Debug|Any CPU
188+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Debug|x86.Build.0 = Debug|Any CPU
189+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Release|Any CPU.ActiveCfg = Release|Any CPU
190+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Release|Any CPU.Build.0 = Release|Any CPU
191+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Release|x64.ActiveCfg = Release|Any CPU
192+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Release|x64.Build.0 = Release|Any CPU
193+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Release|x86.ActiveCfg = Release|Any CPU
194+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10}.Release|x86.Build.0 = Release|Any CPU
181195
EndGlobalSection
182196
GlobalSection(SolutionProperties) = preSolution
183197
HideSolutionNode = FALSE
@@ -193,6 +207,7 @@ Global
193207
{7141AB85-10C5-42AE-8FC7-B14A4216A89F} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
194208
{5222D7CD-3663-49ED-98EA-4B5ECDF705BF} = {B7B8819B-3197-4AB6-B61B-9E1BFD1EC302}
195209
{F8A7E60C-0C48-4D76-AF7F-7881DF5A263D} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
210+
{11362CEE-B4B3-4EFE-A9A1-A6CDEEFCEA10} = {D455EC79-E1E0-4509-B297-0DA3AED8DFF7}
196211
EndGlobalSection
197212
GlobalSection(ExtensibilityGlobals) = postSolution
198213
SolutionGuid = {CE74F821-B001-4C69-A58D-CF81F8B0B632}

src/Elastic.Clients.Elasticsearch/Client/ElasticClient.cs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
15
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
28
using System.Threading;
39
using System.Threading.Tasks;
410
using Elastic.Transport;
@@ -69,6 +75,15 @@ public ElasticClient(ITransport<IElasticsearchClientSettings> transport)
6975
public Serializer RequestResponseSerializer => _transport.Settings.RequestResponseSerializer;
7076
public Serializer SourceSerializer => _transport.Settings.SourceSerializer;
7177

78+
private ProductCheckStatus _productCheckStatus;
79+
80+
private enum ProductCheckStatus
81+
{
82+
NotChecked,
83+
Succeeded,
84+
Failed
85+
}
86+
7287
private partial void SetupNamespaces();
7388

7489
internal TResponse DoRequest<TRequest, TResponse>(
@@ -78,9 +93,14 @@ internal TResponse DoRequest<TRequest, TResponse>(
7893
where TRequest : class, IRequest
7994
where TResponse : class, ITransportResponse, new()
8095
{
96+
if (_productCheckStatus == ProductCheckStatus.Failed)
97+
throw new UnsupportedProductException(UnsupportedProductException.InvalidProductError);
98+
8199
var (url, postData) = PrepareRequest(request, forceConfiguration);
82-
return _transport.Request<TResponse>(request.HttpMethod, url, postData, parameters);
83-
}
100+
var response = _transport.Request<TResponse>(request.HttpMethod, url, postData, parameters);
101+
PostRequestProductCheck<TRequest, TResponse>(request, response);
102+
return response;
103+
}
84104

85105
internal Task<TResponse> DoRequestAsync<TRequest, TResponse>(
86106
TRequest request,
@@ -89,8 +109,22 @@ internal Task<TResponse> DoRequestAsync<TRequest, TResponse>(
89109
where TRequest : class, IRequest
90110
where TResponse : class, ITransportResponse, new()
91111
{
112+
if (_productCheckStatus == ProductCheckStatus.Failed)
113+
throw new UnsupportedProductException(UnsupportedProductException.InvalidProductError);
114+
92115
var (url, postData) = PrepareRequest(request, null);
93-
return _transport.RequestAsync<TResponse>(request.HttpMethod, url, postData, parameters, cancellationToken);
116+
117+
if (_productCheckStatus == ProductCheckStatus.Succeeded)
118+
return _transport.RequestAsync<TResponse>(request.HttpMethod, url, postData, parameters, cancellationToken);
119+
120+
return SendRequest(request, parameters, url, postData);
121+
122+
async Task<TResponse> SendRequest(TRequest request, IRequestParameters? parameters, string url, PostData postData)
123+
{
124+
var response = await _transport.RequestAsync<TResponse>(request.HttpMethod, url, postData, parameters).ConfigureAwait(false);
125+
PostRequestProductCheck<TRequest, TResponse>(request, response);
126+
return response;
127+
}
94128
}
95129

96130
internal Task<TResponse> DoRequestAsync<TRequest, TResponse>(
@@ -101,8 +135,22 @@ internal Task<TResponse> DoRequestAsync<TRequest, TResponse>(
101135
where TRequest : class, IRequest
102136
where TResponse : class, ITransportResponse, new()
103137
{
138+
if (_productCheckStatus == ProductCheckStatus.Failed)
139+
throw new UnsupportedProductException(UnsupportedProductException.InvalidProductError);
140+
104141
var (url, postData) = PrepareRequest(request, forceConfiguration);
105-
return _transport.RequestAsync<TResponse>(request.HttpMethod, url, postData, parameters, cancellationToken);
142+
143+
if (_productCheckStatus == ProductCheckStatus.Succeeded)
144+
return _transport.RequestAsync<TResponse>(request.HttpMethod, url, postData, parameters, cancellationToken);
145+
146+
return SendRequest(request, parameters, url, postData);
147+
148+
async Task<TResponse> SendRequest(TRequest request, IRequestParameters? parameters, string url, PostData postData)
149+
{
150+
var response = await _transport.RequestAsync<TResponse>(request.HttpMethod, url, postData, parameters).ConfigureAwait(false);
151+
PostRequestProductCheck<TRequest, TResponse>(request, response);
152+
return response;
153+
}
106154
}
107155

108156
private (string url, PostData data) PrepareRequest<TRequest>(TRequest request,
@@ -111,8 +159,13 @@ internal Task<TResponse> DoRequestAsync<TRequest, TResponse>(
111159
{
112160
request.ThrowIfNull(nameof(request), "A request is required.");
113161

162+
// If we have not yet checked the product name, add the product header to the list of headers to parse.
163+
if (_productCheckStatus == ProductCheckStatus.NotChecked)
164+
ElasticsearchClientSettings.ResponseHeadersToParse.TryAdd("x-elastic-product");
165+
114166
if (forceConfiguration is not null)
115167
ForceConfiguration(request, forceConfiguration);
168+
116169
if (request.ContentType is not null)
117170
ForceContentType(request, request.ContentType);
118171

@@ -135,6 +188,25 @@ internal Task<TResponse> DoRequestAsync<TRequest, TResponse>(
135188
return (url, postData);
136189
}
137190

191+
private void PostRequestProductCheck<TRequest, TResponse>(TRequest request, TResponse response)
192+
where TRequest : class, IRequest
193+
where TResponse : class, ITransportResponse, new()
194+
{
195+
if (_productCheckStatus == ProductCheckStatus.NotChecked)
196+
{
197+
if (!response.ApiCall.ParsedHeaders.TryGetValue("x-elastic-product", out var values) || values.Single().Equals("elasticsearch", StringComparison.Ordinal))
198+
{
199+
_productCheckStatus = ProductCheckStatus.Failed;
200+
throw new UnsupportedProductException(UnsupportedProductException.InvalidProductError);
201+
}
202+
203+
// Once validated, remove the header so that subsequent requests do not need to attempt to parse it.
204+
ElasticsearchClientSettings.ResponseHeadersToParse.Remove("x-elastic-product");
205+
206+
_productCheckStatus = ProductCheckStatus.Succeeded;
207+
}
208+
}
209+
138210
private static void ForceConfiguration(IRequest request, Action<IRequestConfiguration> forceConfiguration)
139211
{
140212
var configuration = request.RequestParameters.RequestConfiguration ?? new RequestConfiguration();

src/Elastic.Clients.Elasticsearch/Common/IndexName.cs

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,29 @@
66

77
namespace Elastic.Clients.Elasticsearch
88
{
9-
public readonly struct SearchQuery
10-
{
11-
private readonly int? _intValue;
12-
private readonly string _stringValue;
13-
//private readonly bool? _boolValue;
14-
15-
private SearchQuery(int value)
16-
{
17-
_intValue = value;
18-
_stringValue = null;
19-
//_boolValue = null;
20-
}
21-
22-
private SearchQuery(string value)
23-
{
24-
_intValue = null;
25-
_stringValue = value;
26-
//_boolValue = null;
27-
}
28-
29-
public static SearchQuery String(string value) => new SearchQuery(value);
30-
public static SearchQuery Integer(int value) => new SearchQuery(value);
31-
}
9+
//public readonly struct SearchQuery
10+
//{
11+
// private readonly int? _intValue;
12+
// private readonly string _stringValue;
13+
// //private readonly bool? _boolValue;
14+
15+
// private SearchQuery(int value)
16+
// {
17+
// _intValue = value;
18+
// _stringValue = null;
19+
// //_boolValue = null;
20+
// }
21+
22+
// private SearchQuery(string value)
23+
// {
24+
// _intValue = null;
25+
// _stringValue = value;
26+
// //_boolValue = null;
27+
// }
28+
29+
// public static SearchQuery String(string value) => new SearchQuery(value);
30+
// public static SearchQuery Integer(int value) => new SearchQuery(value);
31+
//}
3232

3333
[DebuggerDisplay("{DebugDisplay,nq}")]
3434
public class IndexName : IEquatable<IndexName>, IUrlParameter

src/Elastic.Clients.Elasticsearch/Common/Response/ResponseBase.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Text;
34
using System.Text.Json.Serialization;
45
using Elastic.Transport;
@@ -38,6 +39,23 @@ internal int? StatusCode
3839
}
3940
}
4041

42+
/// <summary>
43+
/// A collection of warnings returned from Elasticsearch.
44+
/// <para>Used to provide server warnings, for example, when the request uses an API feature that is marked as deprecated.</para>
45+
/// </summary>
46+
[JsonIgnore]
47+
public IEnumerable<string> Warnings
48+
{
49+
get
50+
{
51+
if (ApiCall.ParsedHeaders is not null && ApiCall.ParsedHeaders.TryGetValue("warning", out var warnings))
52+
{
53+
foreach (var warning in warnings)
54+
yield return warning;
55+
}
56+
}
57+
}
58+
4159
/// <inheritdoc />
4260
public string DebugInformation
4361
{
@@ -46,9 +64,18 @@ public string DebugInformation
4664
var sb = new StringBuilder();
4765
sb.Append($"{(!IsValid ? "Inv" : "V")}alid Elastic.Clients.Elasticsearch response built from a ");
4866
sb.AppendLine(ApiCall?.ToString().ToCamelCase() ??
49-
"null ApiCall which is highly exceptional, please open a bug if you see this");
67+
"null ApiCall which is highly exceptional, please open a bug if you see this");
5068
if (!IsValid)
5169
DebugIsValid(sb);
70+
71+
if (ApiCall.ParsedHeaders is not null && ApiCall.ParsedHeaders.TryGetValue("warning", out var warnings))
72+
{
73+
sb.AppendLine($"# Server indicated warnings:");
74+
75+
foreach (var warning in warnings)
76+
sb.AppendLine($"- {warning}");
77+
}
78+
5279
if (ApiCall != null)
5380
ResponseStatics.DebugInformationBuilder(ApiCall, sb);
5481
return sb.ToString();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
7+
namespace Elastic.Clients.Elasticsearch;
8+
9+
/// <summary>
10+
/// Thrown when the client pre-flight check determines that the server is not a supported Elasticsearch product.
11+
/// </summary>
12+
public class UnsupportedProductException : Exception
13+
{
14+
internal const string InvalidProductError =
15+
"The client noticed that the server is not a supported distribution of Elasticsearch or an unknown product.";
16+
17+
public UnsupportedProductException(string error)
18+
: base(error) { }
19+
}

src/Elastic.Clients.Elasticsearch/Serialization/DefaultHighLevelSerializer.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
15
using System;
26
using System.IO;
37
using System.Text;
@@ -55,14 +59,17 @@ public override T Deserialize<T>(Stream stream)
5559
// return default;
5660
using var reader = new StreamReader(stream);
5761

58-
try
59-
{
60-
return JsonSerializer.Deserialize<T>(reader.ReadToEnd(), Options);
61-
}
62-
catch (JsonException ex) when (ex.Message.StartsWith("The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."))
63-
{
64-
return default;
65-
}
62+
// TODO: Remove - Just for testing
63+
return default;
64+
65+
//try
66+
//{
67+
// return JsonSerializer.Deserialize<T>(reader.ReadToEnd(), Options);
68+
//}
69+
//catch (JsonException ex) when (ex.Message.StartsWith("The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."))
70+
//{
71+
// return default;
72+
//}
6673
}
6774

6875
public override object Deserialize(Type type, Stream stream) =>

src/Playground/Program.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using Elastic.Clients.Elasticsearch;
66
using Elastic.Clients.Elasticsearch.QueryDsl;
77
using Elastic.Clients.Elasticsearch.Experimental;
8+
using Elastic.Transport;
9+
using System.Collections.Generic;
810

911
namespace Playground
1012
{
@@ -78,7 +80,29 @@ private static void Main()
7880
// Console.WriteLine(boolQuery.Tag);
7981
// }
8082

81-
var client = new ElasticClient();
83+
var client = new ElasticClient(new ElasticsearchClientSettings(new Uri("https://azure.es.eastus.azure.elastic-cloud.com:9243/"))
84+
.CertificateFingerprint("1E69964DFF1259B9ADE47556144E501F381A84B07E5EEC84B81ECF7D4B850C1D")
85+
.Authentication(new BasicAuthentication("elastic", "Z9vNfZN86RxHJ97Poi1BYhC6")));
86+
87+
// client.ElasticsearchClientSettings.ResponseHeadersToParse
88+
89+
var searchAgain = new SearchRequest()
90+
{
91+
Query = new Elastic.Clients.Elasticsearch.QueryDsl.QueryContainer(new Elastic.Clients.Elasticsearch.QueryDsl.BoolQuery { Boost = 1.2F }),
92+
MinScore = 10.0,
93+
Profile = true,
94+
RequestConfiguration = new RequestConfiguration() { ResponseHeadersToParse = new HeadersList("made-up") }
95+
};
96+
97+
var response = client.Search<Person>(searchAgain);
98+
99+
// TODO: The original search request includes header parsing as the config is cached - we should reset this on the product check flow?
100+
101+
response = client.Search<Person>(searchAgain);
102+
103+
//var response = client.Transport.Request<BytesResponse>(HttpMethod.GET, "test");
104+
105+
82106

83107
//var stream = new MemoryStream();
84108
//IMatchQuery match = new MatchQuery() { QueryName = "test_match", Field = "firstName", Query = "Steve" };

src/PlaygroundFx/App.config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
5+
</startup>
6+
</configuration>

src/PlaygroundFx/Person.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace PlaygroundFx
2+
{
3+
public class Person
4+
{
5+
public string FirstName { get; set; }
6+
public string LastName { get; set; }
7+
public int Age { get; set; }
8+
public string Email { get; set; }
9+
}
10+
}

0 commit comments

Comments
 (0)