Skip to content

[9.1] More AOT annotations (#8661) #8665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices;

using System.Text.Json.Serialization.Metadata;
using Elastic.Clients.Elasticsearch.Esql;
using Elastic.Clients.Elasticsearch.Requests;
using Elastic.Clients.Elasticsearch.Serialization;
Expand Down Expand Up @@ -86,10 +86,12 @@ public ElasticsearchClientSettings(InMemoryRequestInvoker inMemoryTransportClien
public ElasticsearchClientSettings(
NodePool nodePool,
IRequestInvoker requestInvoker,
SourceSerializerFactory sourceSerializer,
IPropertyMappingProvider propertyMappingProvider) : base(nodePool, requestInvoker, sourceSerializer, propertyMappingProvider)
SourceSerializerFactory? sourceSerializer,
IPropertyMappingProvider? propertyMappingProvider
) : base(nodePool, requestInvoker, sourceSerializer, propertyMappingProvider)
{
}

}

/// <inheritdoc cref="IElasticsearchClientSettings" />
Expand Down Expand Up @@ -121,21 +123,23 @@ public abstract class ElasticsearchClientSettingsBase<TConnectionSettings> :

protected ElasticsearchClientSettingsBase(
NodePool nodePool,
IRequestInvoker requestInvoker,
IRequestInvoker? requestInvoker,
ElasticsearchClientSettings.SourceSerializerFactory? sourceSerializerFactory,
IPropertyMappingProvider propertyMappingProvider)
IPropertyMappingProvider? propertyMappingProvider
)
: base(nodePool, requestInvoker, null, ElasticsearchClientProductRegistration.DefaultForElasticsearchClientsElasticsearch)
{
var requestResponseSerializer = new DefaultRequestResponseSerializer(this);
var sourceSerializer = new DefaultSourceSerializer(this);

UseThisRequestResponseSerializer = requestResponseSerializer;

_propertyMappingProvider = propertyMappingProvider ?? new DefaultPropertyMappingProvider();
_sourceSerializer = sourceSerializerFactory?.Invoke(sourceSerializer, this) ?? sourceSerializer;
_propertyMappingProvider = propertyMappingProvider ?? sourceSerializer as IPropertyMappingProvider ?? new DefaultPropertyMappingProvider();
_defaultFieldNameInferrer = _sourceSerializer.TryGetJsonSerializerOptions(out var options)
? p => options.PropertyNamingPolicy?.ConvertName(p) ?? p
: p => p.ToCamelCase();

_defaultIndices = new FluentDictionary<Type, string>();
_defaultRelationNames = new FluentDictionary<Type, string>();
_inferrer = new Inferrer(this);
Expand Down Expand Up @@ -394,9 +398,12 @@ public abstract class ConnectionConfigurationBase<TConnectionConfiguration> :
{
private bool _includeServerStackTraceOnError;

protected ConnectionConfigurationBase(NodePool nodePool, IRequestInvoker requestInvoker,
protected ConnectionConfigurationBase(
NodePool nodePool,
IRequestInvoker? requestInvoker,
Serializer? serializer,
ProductRegistration registration = null)
ProductRegistration? registration = null
)
: base(nodePool, requestInvoker, serializer, registration ?? new ElasticsearchProductRegistration(typeof(ElasticsearchClient)))
{
UserAgent(ConnectionConfiguration.DefaultUserAgent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

using Elastic.Clients.Elasticsearch.Json;
using Elastic.Transport;

namespace Elastic.Clients.Elasticsearch;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
using Elastic.Clients.Elasticsearch.Serialization;
using Elastic.Transport;

namespace Elastic.Clients.Elasticsearch;
namespace Elastic.Clients.Elasticsearch.Json;

internal sealed class IdConverter : JsonConverter<Id>
public sealed class IdConverter : JsonConverter<Id>
{
private IElasticsearchClientSettings? _settings;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

using Elastic.Clients.Elasticsearch.Json;
using Elastic.Transport;

namespace Elastic.Clients.Elasticsearch;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
using System.Text.Json.Serialization;
using Elastic.Clients.Elasticsearch.Serialization;

namespace Elastic.Clients.Elasticsearch;
namespace Elastic.Clients.Elasticsearch.Json;

internal sealed class RoutingConverter : JsonConverter<Routing>
public sealed class RoutingConverter : JsonConverter<Routing>
{
private IElasticsearchClientSettings _settings;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ public ContextProvider(TContext context)
/// If no <see cref="ContextProvider{TContext}"/> for <typeparamref name="TContext"/> is registered to the given
/// <see cref="JsonSerializerOptions"/> instance.
/// </exception>
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
public static TContext GetContext(JsonSerializerOptions options)
{
#pragma warning disable IL2026, IL3050
if (options.GetConverter(typeof(Marker)) is not Converter provider)
#pragma warning restore IL2026, IL3050
{
throw new InvalidOperationException($"No context provider for type '{typeof(TContext).Name}' is " +
$"registered for the given 'JsonSerializerOptions' instance.");
Expand All @@ -55,11 +55,11 @@ public static TContext GetContext(JsonSerializerOptions options)
/// <see langword="true"/> if the context was successfully retrieved from the given <see cref="JsonSerializerOptions"/>
/// or <see langword="false"/>, if not.
/// </returns>
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
public static bool TryGetContext(JsonSerializerOptions options, [MaybeNullWhen(false)] out TContext context)
{
#pragma warning disable IL2026, IL3050
if (options.GetConverter(typeof(Marker)) is not Converter provider)
#pragma warning restore IL2026, IL3050
{
context = default;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

using System.Text.Json.Serialization;
Expand All @@ -13,20 +13,18 @@ namespace Elastic.Clients.Elasticsearch.Serialization;

internal static partial class JsonSerializerOptionsExtensions
{
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JsonConverter<T> GetConverter<T>(this JsonSerializerOptions options, Type? markerType)
{
// Mimics the internal behavior of `JsonSerializer.Serialize()` and as well seems to be required in order
// to directly use converters like we do.
// When getting a default generic converter from `JsonSerializerOptions` that are not read-only, a
// `NotSupportedException` is thrown as soon as we call `converter.Read()` or `converter.Write()`.
#pragma warning disable IL2026, IL3050
options.MakeReadOnly(true);
#pragma warning restore IL2026, IL3050

#pragma warning disable IL2026, IL3050
var rawConverter = options.GetConverter(markerType ?? typeof(T));
#pragma warning restore IL2026, IL3050

var converter = (JsonConverter<T>)(rawConverter is IMarkerTypeConverter markerTypeConverter
? markerTypeConverter.WrappedConverter
Expand All @@ -35,6 +33,8 @@ public static JsonConverter<T> GetConverter<T>(this JsonSerializerOptions option
return converter;
}

[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TContext GetContext<TContext>(this JsonSerializerOptions options)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Elastic.Clients.Elasticsearch.Serialization;

internal sealed class ObjectToInferredTypesConverter :
JsonConverter<object>
internal sealed class ObjectToInferredTypesConverter : JsonConverter<object>
{
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType switch
Expand All @@ -26,12 +28,11 @@ JsonTokenType.String when reader.TryGetDateTimeOffset(out var value) => value,
};
}

[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
// TODO: Match `SourceMarker<T>` values and delegate to the `SourceSerializer`.

#pragma warning disable IL2026, IL3050
JsonSerializer.Serialize(writer, value, value.GetType(), options);
#pragma warning restore IL2026, IL3050
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -50,27 +51,23 @@ public override bool CanConvert(Type typeToConvert)
typeToConvert.GetGenericTypeDefinition() == typeof(SourceMarker<>);
}

[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var args = typeToConvert.GetGenericArguments();

#pragma warning disable IL3050 // SourceMarker<T> static constructor roots SourceMarkerConverter<T>.

var converter = (JsonConverter)Activator.CreateInstance(
typeof(SourceMarkerConverter<>).MakeGenericType(args[0]),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: [settings],
culture: null)!;

#pragma warning restore IL3050

return converter;
}
}

internal sealed class SourceConverter<T> :
JsonConverter<T>
internal sealed class SourceConverter<T> : JsonConverter<T>
{
private readonly IElasticsearchClientSettings _settings;

Expand All @@ -79,11 +76,15 @@ public SourceConverter(IElasticsearchClientSettings settings)
_settings = settings;
}

[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return _settings.SourceSerializer.Deserialize<T>(ref reader);
}

[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute'", Justification = "Always using explicit TypeInfoResolver")]
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
_settings.SourceSerializer.Serialize(value, writer);
Expand Down
17 changes: 11 additions & 6 deletions src/Playground/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,45 @@
// See the LICENSE file in the project root for more information.

using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;
using Playground;

namespace Playground
{
[JsonSerializable(typeof(Person))]
internal partial class PlaygroundJsonSerializerContext : JsonSerializerContext;

public class Person
{
public int Id { get; set; }

[System.Text.Json.Serialization.JsonPropertyName("id2")]
[JsonPropertyName("id2")]
public Guid SecondaryId { get; set; } = Guid.NewGuid();
public string? FirstName { get; init; }
public string? LastName { get; init; }
public int? Age { get; init; }
public bool IsDeleted { get; init; }
public bool IsDeleted { get; init; }
public Routing? Routing { get; init; }

public Id Idv3 => "testing";
//public Guid Routing { get; init; } = Guid.NewGuid();

[System.Text.Json.Serialization.JsonIgnore]
[JsonIgnore]
public string? Email { get; init; }

[DataMember(Name = "STEVE")]
[IgnoreDataMember]
public string Data { get; init; } = "NOTHING";

public DateTimeKind Enum { get; init; }

public Query? Q { get; init; }
}

public class PersonV3
{
public Guid SecondaryId { get; set; } = Guid.NewGuid();
}
}



1 change: 0 additions & 1 deletion src/Playground/Playground.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Elastic.Transport" Version="0.10.0" />
<PackageReference Include="System.Text.Json" Version="9.0.8" />
</ItemGroup>

Expand Down
12 changes: 8 additions & 4 deletions src/Playground/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
// See the LICENSE file in the project root for more information.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization.Metadata;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.Serialization;
using Elastic.Transport;
using Elastic.Transport.Extensions;
using static System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes;

using Playground;

var settings = new ElasticsearchClientSettings(new Uri("https://primary.es.europe-west3.gcp.cloud.es.io"))
var pool = new SingleNodePool(new Uri("https://primary.es.europe-west3.gcp.cloud.es.io"));
var settings = new ElasticsearchClientSettings(pool,
sourceSerializer: (_, settings) =>
new DefaultSourceSerializer(settings, PlaygroundJsonSerializerContext.Default)
)
.Authentication(new BasicAuthentication("elastic", "Oov35Wtxj5DzpZNzYAzFb0KZ"))
.DisableDirectStreaming()
.EnableDebugMode(cd =>
Expand All @@ -37,7 +41,7 @@
Console.WriteLine(id);
Console.WriteLine(idByType);
// This still errors on AOT compilation
//Console.WriteLine(client.SourceSerializer.SerializeToString(person));
Console.WriteLine(client.SourceSerializer.SerializeToString(person));

[UnconditionalSuppressMessage("Trimming", "IL2072", Justification = "Can only annotate our implementation")]
[UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "Can only annotate our implementation")]
Expand Down
Loading