Skip to content
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 @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFrameworks>$(LocalStackExtensionsTargetFrameworks)</TargetFrameworks>
<IsAotCompatible>$(IsLocalStackModernTarget)</IsAotCompatible>
<AssemblyName>LocalStack.Client.Extensions</AssemblyName>
<RootNamespace>LocalStack.Client.Extensions</RootNamespace>
<Version>$(PackageExtensionVersion)</Version>
Expand Down
507 changes: 326 additions & 181 deletions src/LocalStack.Client.Generators/AwsRegistryGenerator.cs

Large diffs are not rendered by default.

64 changes: 0 additions & 64 deletions src/LocalStack.Client.Generators/GeneratedReflection.cs

This file was deleted.

1 change: 1 addition & 0 deletions src/LocalStack.Client/LocalStack.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFrameworks>$(LocalStackClientTargetFrameworks)</TargetFrameworks>
<IsAotCompatible>$(IsLocalStackModernTarget)</IsAotCompatible>
<AssemblyName>LocalStack.Client</AssemblyName>
<RootNamespace>LocalStack.Client</RootNamespace>

Expand Down
2 changes: 2 additions & 0 deletions src/LocalStack.Client/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("LocalStack.Client.Extensions")]
[assembly: InternalsVisibleTo("LocalStack.Client.Tests")]
[assembly: InternalsVisibleTo("LocalStack.Client.Integration.Tests")]
90 changes: 63 additions & 27 deletions src/LocalStack.Client/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class Session : ISession
private readonly ISessionOptions _sessionOptions;
private readonly ISessionReflection _sessionReflection;

#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
[DynamicDependency(nameof(RegisterGeneratedClient), typeof(Session))]
[DynamicDependency(nameof(RegisterGeneratedConfig), typeof(Session))]
#endif
Expand Down Expand Up @@ -56,6 +56,13 @@ public AmazonServiceClient CreateClientByImplementation(
clientConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(_sessionOptions.RegionName);
}

#if NET10_0_OR_GREATER
if (implType == null)
{
throw new ArgumentNullException(nameof(implType));
}
#endif

var clientInstance = CreateImplementation(implType, awsCredentials, clientConfig);

return clientInstance;
Expand Down Expand Up @@ -84,7 +91,7 @@ public AmazonServiceClient CreateClientByInterface(
}

var clientTypeName = $"{serviceInterfaceType.Namespace}.{serviceInterfaceType.Name.Substring(1)}Client";
Type? clientType = FindClientType(serviceInterfaceType.GetTypeInfo().Assembly, clientTypeName);
Type? clientType = FindClientType(serviceInterfaceType.Assembly, clientTypeName);

if (clientType == null)
{
Expand Down Expand Up @@ -119,17 +126,15 @@ public AmazonServiceClient CreateClientByInterface(
clientConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(_sessionOptions.RegionName);
}

ConstructorInfo? constructor = FindClientConstructor(clientType, clientConfig.GetType());
AmazonServiceClient? client = CreateInterface(clientType, awsCredentials, clientConfig);

if (constructor == null)
if (client == null)
{
var message = $"Service client {clientTypeName} missing a constructor with parameters AWSCredentials and {clientConfig.GetType().FullName}.";

throw new AmazonClientException(message);
}

var client = (AmazonServiceClient)constructor.Invoke(new object[] { awsCredentials, clientConfig });

return client;
}

Expand Down Expand Up @@ -177,72 +182,103 @@ private AmazonServiceClient CreateClientByStaticFactory<TClient>(bool useService

private static Type? FindClientType(Assembly assembly, string name)
{
#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
Type? generated = GeneratedClientRegistry.FindClient(assembly, name);
if (generated != null || !System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
if (generated != null)
{
return generated;
}
#endif
if (ReflectionFallback.IsSupported)
{
return FindLegacyClientType(assembly, name);
}

return null;
#else
return FindLegacyClientType(assembly, name);
#endif
}

private static ConstructorInfo? FindClientConstructor(Type type, Type configType)
private static AmazonServiceClient? CreateInterface(Type type, AWSCredentials credentials, ClientConfig config)
{
#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
if (GeneratedClientRegistry.TryGetClient(type, out GeneratedClientRegistry.ClientEntry? entry))
{
return entry.Constructor(configType);
return CreateGenerated(entry, credentials, config);
}

if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
if (ReflectionFallback.IsSupported)
{
return CreateLegacyInterface(type, credentials, config);
}
else
{
return null;
}
#else
return CreateLegacyInterface(type, credentials, config);
#endif
return FindLegacyClientConstructor(type, configType);
}

private static AmazonServiceClient CreateImplementation(Type type, AWSCredentials credentials, ClientConfig config)
{
#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
if (GeneratedClientRegistry.TryGetClient(type, out GeneratedClientRegistry.ClientEntry? entry))
{
return entry.Create(credentials, config);
return CreateGenerated(entry, credentials, config)
?? throw new MissingMethodException($"Constructor on type '{type.FullName}' not found.");
}

if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported)
if (ReflectionFallback.IsSupported)
{
return CreateLegacyImplementation(type, credentials, config);
}
else
{
throw new NotSupportedException($"Client type {type} was not present when the application was compiled.");
}
#endif
#else
return CreateLegacyImplementation(type, credentials, config);
#endif
}

#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode("Dynamically loaded client types require their original reflection metadata.")]
#endif
private static Type? FindLegacyClientType(Assembly assembly, string name) => assembly.GetType(name);

#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode("Dynamically loaded client constructors require their original reflection metadata.")]
#endif
private static ConstructorInfo? FindLegacyClientConstructor(Type type, Type configType) => type.GetConstructor(new[] { typeof(AWSCredentials), configType });
private static AmazonServiceClient? CreateLegacyInterface(Type type, AWSCredentials credentials, ClientConfig config) =>
(AmazonServiceClient?)type.GetConstructor(new[] { typeof(AWSCredentials), config.GetType() })?.Invoke(new object[] { credentials, config });

#if NET10_0_OR_GREATER
private static AmazonServiceClient? CreateGenerated(GeneratedClientRegistry.ClientEntry entry, AWSCredentials credentials, ClientConfig config)
{
try
{
return entry.Create(credentials, config);
}
catch (Exception exception)
{
throw new TargetInvocationException(exception);
}
}

#if NET8_0_OR_GREATER
[RequiresUnreferencedCode("Dynamically loaded client constructors require their original reflection metadata.")]
#endif
private static AmazonServiceClient CreateLegacyImplementation(Type type, AWSCredentials credentials, ClientConfig config) =>
(AmazonServiceClient)Activator.CreateInstance(type, credentials, config)!;

#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
// The generated caller uses UnsafeAccessor. Only delegates cross this boundary: no DAM
// requirements are imposed on an accessor signature or on existing consumer Type APIs.
private static void RegisterGeneratedClient(Type type, Func<IServiceMetadata> metadata, Func<ClientConfig> config,
Func<Type, ConstructorInfo?> constructor, Func<AWSCredentials, ClientConfig, AmazonServiceClient> create) =>
GeneratedClientRegistry.RegisterClient(type, metadata, config, constructor, create);
Func<AWSCredentials, ClientConfig, AmazonServiceClient?> create) =>
GeneratedClientRegistry.RegisterClient(type, metadata, config, create);

private static void RegisterGeneratedConfig(Type type, Func<ClientConfig, bool, bool> style, Action<object, string> region) =>
GeneratedClientRegistry.RegisterConfig(type, style, region);
private static void RegisterGeneratedConfig(Type type, Func<ClientConfig, bool, bool>? style, bool plainRegion) =>
GeneratedClientRegistry.RegisterConfig(type, style, plainRegion);
#endif
}
16 changes: 8 additions & 8 deletions src/LocalStack.Client/Utils/GeneratedClientRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if NET8_0_OR_GREATER
#if NET10_0_OR_GREATER
using System.Collections.Concurrent;

namespace LocalStack.Client.Utils;
Expand All @@ -12,17 +12,17 @@ internal static class GeneratedClientRegistry
private static readonly ConcurrentDictionary<Type, ConfigEntry> Configs = new();

public static void RegisterClient(Type type, Func<IServiceMetadata> metadata, Func<ClientConfig> config,
Func<Type, ConstructorInfo?> constructor, Func<AWSCredentials, ClientConfig, AmazonServiceClient> create)
Func<AWSCredentials, ClientConfig, AmazonServiceClient?> create)
{
Clients.TryAdd(type, new ClientEntry(metadata, config, constructor, create));
Clients.TryAdd(type, new ClientEntry(metadata, config, create));
if (type.FullName is string name)
{
ClientNames.TryAdd((type.Assembly, name), type);
}
}

public static void RegisterConfig(Type type, Func<ClientConfig, bool, bool> style, Action<object, string> region) =>
Configs.TryAdd(type, new ConfigEntry(style, region));
public static void RegisterConfig(Type type, Func<ClientConfig, bool, bool>? style, bool plainRegion) =>
Configs.TryAdd(type, new ConfigEntry(style, plainRegion));

public static Type? FindClient(Assembly assembly, string name) => ClientNames.TryGetValue((assembly, name), out Type? type) ? type : null;

Expand All @@ -34,9 +34,9 @@ public static bool TryGetClient(Type type, [NotNullWhen(true)] out ClientEntry?

public static bool TryGetConfig(Type type, [NotNullWhen(true)] out ConfigEntry? entry) => Configs.TryGetValue(type, out entry);

internal sealed record ClientEntry(Func<IServiceMetadata> Metadata, Func<ClientConfig> Config, Func<Type, ConstructorInfo?> Constructor,
Func<AWSCredentials, ClientConfig, AmazonServiceClient> Create);
internal sealed record ClientEntry(Func<IServiceMetadata> Metadata, Func<ClientConfig> CreateConfig,
Func<AWSCredentials, ClientConfig, AmazonServiceClient?> Create);

internal sealed record ConfigEntry(Func<ClientConfig, bool, bool> Style, Action<object, string> Region);
internal sealed record ConfigEntry(Func<ClientConfig, bool, bool>? Style, bool PlainRegion);
}
#endif
10 changes: 10 additions & 0 deletions src/LocalStack.Client/Utils/ReflectionFallback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace LocalStack.Client.Utils;

internal static class ReflectionFallback
{
#if NET10_0_OR_GREATER
[FeatureGuard(typeof(RequiresUnreferencedCodeAttribute))]
#endif
internal static bool IsSupported { get; } =
!AppContext.TryGetSwitch("LocalStack.Client.ReflectionFallback.IsSupported", out bool enabled) || enabled;
}
Loading
Loading