Skip to content

Commit a07eb36

Browse files
Mpdreamzflobernd
andauthored
Add AOT support to Elastic.Transport. (#162)
Promote ApiKey as default implementation (also documentated default as part of client bootstrapping instruction on serverless). Make it easier to bootstrap a transport/cloudnodepool using a cloud endpoint an authentication method. No need to externally construct a cloud id. --------- Co-authored-by: Florian Bernd <[email protected]>
1 parent ba4258c commit a07eb36

32 files changed

+335
-256
lines changed

.editorconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ resharper_redundant_case_label_highlighting=do_not_show
133133
resharper_redundant_argument_default_value_highlighting=do_not_show
134134
# Do not penalize code that explicitly lists generic arguments
135135
resharper_redundant_type_arguments_of_method_highlighting=do_not_show
136+
resharper_switch_expression_handles_some_known_enum_values_with_exception_in_default_highlighting=error
136137

137138
[Jenkinsfile]
138139
indent_style = space

Playground/Playground.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<TargetFramework>net8.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8+
<PublishAot>true</PublishAot>
89
</PropertyGroup>
910

1011
<ItemGroup>

Playground/Program.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,27 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using Elastic.Transport;
56
using Elastic.Transport.Products.Elasticsearch;
7+
using HttpMethod = Elastic.Transport.HttpMethod;
68

79
var registration = new ElasticsearchProductRegistration(typeof(Elastic.Clients.Elasticsearch.ElasticsearchClient));
810

11+
var apiKey = Environment.GetEnvironmentVariable("ELASTIC_API_KEY") ?? throw new Exception();
12+
var url = Environment.GetEnvironmentVariable("ELASTIC_URL") ?? throw new Exception();
13+
14+
var configuration = new TransportConfiguration(new Uri(url), new ApiKey(apiKey), ElasticsearchProductRegistration.Default)
15+
{
16+
DebugMode = true
17+
};
18+
var transport = new DistributedTransport(configuration);
19+
20+
var response = transport.Request<EsResponse>(HttpMethod.GET, "/does-not-exist");
21+
Console.WriteLine(response.DebugInformation);
22+
23+
var dynamicResponse = transport.Request<DynamicResponse>(HttpMethod.GET, "/");
24+
Console.WriteLine(dynamicResponse.Body.Get<string>("version.build_flavor"));
25+
926
Console.WriteLine(registration.DefaultContentType ?? "NOT SPECIFIED");
27+
28+
public class EsResponse : ElasticsearchResponse;

src/Elastic.Transport/Components/NodePool/CloudNodePool.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ namespace Elastic.Transport;
1616
/// </summary>
1717
public sealed class CloudNodePool : SingleNodePool
1818
{
19+
private readonly record struct ParsedCloudId(string ClusterName, Uri Uri);
20+
1921
/// <summary>
2022
/// An <see cref="NodePool"/> implementation that can be seeded with a cloud id
2123
/// and will signal the right defaults for the client to use for Elastic Cloud to <see cref="ITransportConfiguration"/>.
@@ -39,8 +41,17 @@ public sealed class CloudNodePool : SingleNodePool
3941
public CloudNodePool(string cloudId, AuthorizationHeader credentials) : this(ParseCloudId(cloudId)) =>
4042
AuthenticationHeader = credentials;
4143

44+
/// <summary>
45+
/// An <see cref="NodePool"/> implementation that can be seeded with a cloud enpoint
46+
/// and will signal the right defaults for the client to use for Elastic Cloud to <see cref="ITransportConfiguration"/>.
47+
/// </summary>
48+
/// <param name="cloudEndpoint">Elastic Cloud endpoint</param>
49+
/// <param name="credentials">The credentials to use with cloud</param>
50+
public CloudNodePool(Uri cloudEndpoint, AuthorizationHeader credentials) : this(CreateCloudId(cloudEndpoint)) =>
51+
AuthenticationHeader = credentials;
52+
4253
private CloudNodePool(ParsedCloudId parsedCloudId) : base(parsedCloudId.Uri) =>
43-
ClusterName = parsedCloudId.Name;
54+
ClusterName = parsedCloudId.ClusterName;
4455

4556
//TODO implement debugger display for NodePool implementations and display it there and its ToString()
4657
// ReSharper disable once UnusedAutoPropertyAccessor.Local
@@ -49,16 +60,13 @@ private CloudNodePool(ParsedCloudId parsedCloudId) : base(parsedCloudId.Uri) =>
4960
/// <inheritdoc cref="AuthorizationHeader"/>
5061
public AuthorizationHeader AuthenticationHeader { get; }
5162

52-
private readonly struct ParsedCloudId
63+
private static ParsedCloudId CreateCloudId(Uri uri)
5364
{
54-
public ParsedCloudId(string clusterName, Uri uri)
55-
{
56-
Name = clusterName;
57-
Uri = uri;
58-
}
59-
60-
public string Name { get; }
61-
public Uri Uri { get; }
65+
var moniker = $"{uri.Host}${Guid.NewGuid():N}";
66+
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(moniker));
67+
var cloudId = $"name:{base64}";
68+
return new ParsedCloudId(cloudId, uri);
69+
6270
}
6371

6472
private static ParsedCloudId ParseCloudId(string cloudId)

src/Elastic.Transport/Components/Pipeline/PipelineFailure.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System;
56
using Elastic.Transport.Products;
67

78
namespace Elastic.Transport;
@@ -56,3 +57,21 @@ public enum PipelineFailure
5657
/// </summary>
5758
NoNodesAttempted
5859
}
60+
61+
internal static class PipelineFailureExtensions
62+
{
63+
public static string ToStringFast(this PipelineFailure failure) => failure switch
64+
{
65+
PipelineFailure.BadAuthentication => nameof(PipelineFailure.BadAuthentication),
66+
PipelineFailure.BadResponse => nameof(PipelineFailure.BadResponse),
67+
PipelineFailure.PingFailure => nameof(PipelineFailure.PingFailure),
68+
PipelineFailure.SniffFailure => nameof(PipelineFailure.SniffFailure),
69+
PipelineFailure.CouldNotStartSniffOnStartup => nameof(PipelineFailure.CouldNotStartSniffOnStartup),
70+
PipelineFailure.MaxTimeoutReached => nameof(PipelineFailure.MaxTimeoutReached),
71+
PipelineFailure.MaxRetriesReached => nameof(PipelineFailure.MaxRetriesReached),
72+
PipelineFailure.Unexpected => nameof(PipelineFailure.Unexpected),
73+
PipelineFailure.BadRequest => nameof(PipelineFailure.BadRequest),
74+
PipelineFailure.NoNodesAttempted => nameof(PipelineFailure.NoNodesAttempted),
75+
_ => throw new ArgumentOutOfRangeException(nameof(failure), failure, null)
76+
};
77+
}

src/Elastic.Transport/Components/Serialization/Converters/DynamicDictionaryConverter.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using System.Globalization;
88
using System.Text.Json;
99
using System.Text.Json.Serialization;
10+
using System.Text.Json.Serialization.Metadata;
11+
using Elastic.Transport.Products.Elasticsearch;
1012
using JsonSerializer = System.Text.Json.JsonSerializer;
1113

1214
namespace Elastic.Transport;
@@ -17,15 +19,15 @@ public override DynamicDictionary Read(ref Utf8JsonReader reader, Type typeToCon
1719
{
1820
if (reader.TokenType == JsonTokenType.StartArray)
1921
{
20-
var array = JsonSerializer.Deserialize<object[]>(ref reader, options);
22+
var array = JsonSerializer.Deserialize<object[]>(ref reader, ErrorSerializerContext.Default.ObjectArray); // TODO: Test! This might not work without adding `object[]` to `ErrorSerializationContext`
2123
var arrayDict = new Dictionary<string, object>();
2224
for (var i = 0; i < array.Length; i++)
2325
arrayDict[i.ToString(CultureInfo.InvariantCulture)] = new DynamicValue(array[i]);
2426
return DynamicDictionary.Create(arrayDict);
2527
}
2628
if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException();
2729

28-
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, options);
30+
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(ref reader, ErrorSerializerContext.Default.DictionaryStringObject); // TODO: Test! This might not work without adding `Dictionary<string, object>` to `ErrorSerializationContext`
2931
return DynamicDictionary.Create(dict);
3032
}
3133

@@ -35,11 +37,14 @@ public override void Write(Utf8JsonWriter writer, DynamicDictionary dictionary,
3537

3638
foreach (var kvp in dictionary)
3739
{
38-
if (kvp.Value == null) continue;
40+
if (kvp.Value is null) continue;
3941

4042
writer.WritePropertyName(kvp.Key);
4143

42-
JsonSerializer.Serialize(writer, kvp.Value?.Value, options);
44+
// TODO: Test! We have to make sure all possible "Value" types are registered in the `ErrorSerializationContext`
45+
#pragma warning disable IL2026, IL3050 // ErrorSerializerContext is registered.
46+
JsonSerializer.Serialize(writer, kvp.Value.Value, kvp.Value.GetType(), options);
47+
#pragma warning restore IL2026, IL3050
4348
}
4449

4550
writer.WriteEndObject();

src/Elastic.Transport/Components/Serialization/Converters/ErrorCauseConverter.cs

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ public class ErrorConverter : ErrorCauseConverter<Error>
2222
protected override bool ReadMore(ref Utf8JsonReader reader, JsonSerializerOptions options, string propertyName, Error errorCause)
2323
{
2424
void ReadAssign<T>(ref Utf8JsonReader r, Action<Error, T> set) =>
25+
#pragma warning disable IL2026, IL3050 // ErrorSerializerContext is registered.
2526
set(errorCause, JsonSerializer.Deserialize<T>(ref r, options));
27+
#pragma warning restore IL2026, IL3050
2628
switch (propertyName)
2729
{
2830
case "headers":
@@ -55,9 +57,14 @@ public override TErrorCause Read(ref Utf8JsonReader reader, Type typeToConvert,
5557
errorCause.AdditionalProperties = additionalProperties;
5658

5759
void ReadAssign<T>(ref Utf8JsonReader r, Action<ErrorCause, T> set) =>
60+
#pragma warning disable IL2026, IL3050 // ErrorSerializerContext is registered.
5861
set(errorCause, JsonSerializer.Deserialize<T>(ref r, options));
62+
#pragma warning restore IL2026, IL3050
63+
5964
void ReadAny(ref Utf8JsonReader r, string property, Action<ErrorCause, string, object> set) =>
60-
set(errorCause, property, JsonSerializer.Deserialize<object>(ref r, options));
65+
#pragma warning disable IL2026, IL3050 // ErrorSerializerContext is registered.
66+
set(errorCause, property, JsonSerializer.Deserialize<JsonElement>(ref r, options));
67+
#pragma warning restore IL2026, IL3050
6168

6269
while (reader.Read())
6370
{
@@ -140,45 +147,6 @@ void ReadAny(ref Utf8JsonReader r, string property, Action<ErrorCause, string, o
140147
return errorCause;
141148
}
142149

143-
private static int? ReadIntFromString(ref Utf8JsonReader reader, JsonSerializerOptions options)
144-
{
145-
reader.Read();
146-
switch (reader.TokenType)
147-
{
148-
case JsonTokenType.Null: return null;
149-
case JsonTokenType.Number:
150-
return JsonSerializer.Deserialize<int?>(ref reader, options);
151-
case JsonTokenType.String:
152-
var s = JsonSerializer.Deserialize<string>(ref reader, options);
153-
if (int.TryParse(s, out var i)) return i;
154-
return null;
155-
default:
156-
reader.TrySkip();
157-
return null;
158-
}
159-
}
160-
161-
private static IReadOnlyCollection<string> ReadSingleOrCollection(ref Utf8JsonReader reader, JsonSerializerOptions options)
162-
{
163-
reader.Read();
164-
switch (reader.TokenType)
165-
{
166-
case JsonTokenType.Null: return EmptyReadOnly<string>.Collection;
167-
case JsonTokenType.StartArray:
168-
var list = new List<string>();
169-
while (reader.Read())
170-
{
171-
if (reader.TokenType == JsonTokenType.EndArray)
172-
break;
173-
list.Add(JsonSerializer.Deserialize<string>(ref reader, options));
174-
}
175-
return new ReadOnlyCollection<string>(list);
176-
default:
177-
var v = JsonSerializer.Deserialize<string>(ref reader, options);
178-
return new ReadOnlyCollection<string>(new List<string>(1) { v});
179-
}
180-
}
181-
182150
/// Read additional properties for the particular <see cref="ErrorCause"/> implementation
183151
protected virtual bool ReadMore(ref Utf8JsonReader reader, JsonSerializerOptions options, string propertyName, TErrorCause errorCause) => false;
184152

@@ -189,10 +157,22 @@ public override void Write(Utf8JsonWriter writer, TErrorCause value, JsonSeriali
189157

190158
static void Serialize<T>(Utf8JsonWriter writer, JsonSerializerOptions options, string name, T value)
191159
{
192-
if (value == null) return;
160+
if (value is null) return;
193161

194162
writer.WritePropertyName(name);
163+
#pragma warning disable IL2026, IL3050 // ErrorSerializerContext is registered.
195164
JsonSerializer.Serialize(writer, value, options);
165+
#pragma warning restore IL2026, IL3050
166+
}
167+
168+
static void SerializeDynamic(Utf8JsonWriter writer, JsonSerializerOptions options, string name, object? value, Type inputType)
169+
{
170+
if (value is null) return;
171+
172+
writer.WritePropertyName(name);
173+
#pragma warning disable IL2026, IL3050 // ErrorSerializerContext is registered.
174+
JsonSerializer.Serialize(writer, value, inputType, options);
175+
#pragma warning restore IL2026, IL3050
196176
}
197177

198178
//Serialize(writer, options, "bytes_limit", value.BytesLimit);
@@ -217,7 +197,7 @@ static void Serialize<T>(Utf8JsonWriter writer, JsonSerializerOptions options, s
217197
Serialize(writer, options, "type", value.Type);
218198

219199
foreach (var kv in value.AdditionalProperties)
220-
Serialize(writer, options, kv.Key, kv.Value);
200+
SerializeDynamic(writer, options, kv.Key, kv.Value, kv.Value.GetType());
221201
writer.WriteEndObject();
222202
}
223203
}

src/Elastic.Transport/Components/Serialization/Converters/ExceptionConverter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Generic;
77
using System.Text.Json;
88
using System.Text.Json.Serialization;
9+
using Elastic.Transport.Products.Elasticsearch;
910
using JsonSerializer = System.Text.Json.JsonSerializer;
1011

1112
namespace Elastic.Transport;
@@ -71,7 +72,7 @@ public override void Write(Utf8JsonWriter writer, Exception value, JsonSerialize
7172
foreach (var kv in flattenedException)
7273
{
7374
writer.WritePropertyName(kv.Key);
74-
JsonSerializer.Serialize(writer, kv.Value, options);
75+
JsonSerializer.Serialize(writer, kv.Value, ErrorSerializerContext.Default.Object); // TODO: Test! This might not work without adding `KeyValuePair<string, object>` to `ErrorSerializationContext`
7576
}
7677
writer.WriteEndObject();
7778
}

src/Elastic.Transport/Components/Serialization/LowLevelRequestResponseSerializer.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using System.Collections.Generic;
66
using System.Text.Json;
77
using System.Text.Json.Serialization;
8+
using System.Text.Json.Serialization.Metadata;
9+
using Elastic.Transport.Products.Elasticsearch;
810

911
namespace Elastic.Transport;
1012

@@ -32,6 +34,12 @@ public LowLevelRequestResponseSerializer(IReadOnlyCollection<JsonConverter>? con
3234
new ErrorCauseConverter(),
3335
new ErrorConverter(),
3436
new DynamicDictionaryConverter()
35-
], converters, options => { options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; })) { }
37+
], converters, options =>
38+
{
39+
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
40+
#pragma warning disable IL2026, IL3050
41+
options.TypeInfoResolver = JsonTypeInfoResolver.Combine(new DefaultJsonTypeInfoResolver(), ErrorSerializerContext.Default);
42+
#pragma warning restore IL2026, IL3050
43+
})) { }
3644

3745
}

src/Elastic.Transport/Components/Serialization/SystemTextJsonSerializer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
namespace Elastic.Transport;
1212

13+
#pragma warning disable IL2026, IL3050 // Implementing classes must make sure to use an AOT compatible JsonSerializerOptions.TypeInfoResolver
14+
1315
/// <summary>
1416
/// An abstract implementation of a transport <see cref="Serializer"/> which serializes using the Microsoft
1517
/// <c>System.Text.Json</c> library.
@@ -133,3 +135,5 @@ private static bool TryReturnDefault<T>(Stream? stream, out T deserialize)
133135
return (stream is null) || stream == Stream.Null || (stream.CanSeek && stream.Length == 0);
134136
}
135137
}
138+
139+
#pragma warning restore IL2026, IL3050

0 commit comments

Comments
 (0)