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
48 changes: 48 additions & 0 deletions src/LocalStack.Client/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public AmazonServiceClient CreateClientByImplementation(

public AmazonServiceClient CreateClientByInterface<TClient>(bool useServiceUrl = false) where TClient : IAmazonService
{
#if NET10_0_OR_GREATER
if (_sessionReflection.GetType() == typeof(SessionReflection))
{
return CreateClientByStaticFactory<TClient>(useServiceUrl);
}
#endif
Type serviceInterfaceType = typeof(TClient);

return CreateClientByInterface(serviceInterfaceType, useServiceUrl);
Expand Down Expand Up @@ -127,6 +133,48 @@ public AmazonServiceClient CreateClientByInterface(
return client;
}

#if NET10_0_OR_GREATER
private AmazonServiceClient CreateClientByStaticFactory<TClient>(bool useServiceUrl) where TClient : IAmazonService
{
if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
{
throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false.");
}

ClientConfig clientConfig = TClient.CreateDefaultClientConfig();
string serviceId = clientConfig.ServiceId;
AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceId) ??
throw new NotSupportedClientException($"{serviceId} is not supported by this mock session.");
AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken);

clientConfig.UseHttp = !_config.GetConfigOptions().UseSsl;
S3ForcePathStyle.TrySet(clientConfig, true);
clientConfig.ProxyHost = awsServiceEndpoint.Host;
clientConfig.ProxyPort = awsServiceEndpoint.Port;

if (useServiceUrl)
{
clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl.AbsoluteUri;
}
else if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
{
clientConfig.RegionEndpoint = RegionEndpoint.GetBySystemName(_sessionOptions.RegionName);
}

IAmazonService client;
try
{
client = TClient.CreateDefaultServiceClient(awsCredentials, clientConfig);
}
catch (Exception exception)
{
throw new TargetInvocationException(exception);
}

return (AmazonServiceClient)client;
}
#endif

private static Type? FindClientType(Assembly assembly, string name)
{
#if NET8_0_OR_GREATER
Expand Down
22 changes: 22 additions & 0 deletions src/LocalStack.Client/Utils/S3ForcePathStyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#if NET10_0_OR_GREATER
using System.Runtime.CompilerServices;

namespace LocalStack.Client.Utils;

internal static class S3ForcePathStyle
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ForcePathStyle")]
private static extern void SetForcePathStyle([UnsafeAccessorType("Amazon.S3.AmazonS3Config, AWSSDK.S3")] object config, bool value);

internal static bool TrySet(ClientConfig clientConfig, bool value)
{
if (string.Equals(clientConfig.ServiceId, "S3", StringComparison.Ordinal))
{
SetForcePathStyle(clientConfig, value);
return true;
}

return false;
}
}
#endif
6 changes: 6 additions & 0 deletions src/LocalStack.Client/Utils/SessionReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ public bool SetForcePathStyle(ClientConfig clientConfig, bool value = true)
throw new ArgumentNullException(nameof(clientConfig));
}

#if NET10_0_OR_GREATER
if (S3ForcePathStyle.TrySet(clientConfig, value))
{
return true;
}
#endif
#if NET8_0_OR_GREATER
if (GeneratedClientRegistry.TryGetConfig(clientConfig.GetType(), out GeneratedClientRegistry.ConfigEntry? entry))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ static CreateClientByInterfaceTests()
{
}

[Fact]
public void SetForcePathStyle_Should_Set_S3_Config_And_Return_False_For_Sqs_Config()
{
var reflection = new Utils.SessionReflection();
var s3Config = new AmazonS3Config();

Assert.False(s3Config.ForcePathStyle);
Assert.True(reflection.SetForcePathStyle(s3Config));
Assert.True(s3Config.ForcePathStyle);
Assert.False(reflection.SetForcePathStyle(new AmazonSQSConfig()));
}

[Fact]
public void Should_Able_To_Create_AmazonAPIGatewayClient()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#if NET10_0_OR_GREATER
namespace LocalStack.Client.Tests.SessionTests;

public class SessionStaticFactoryTests
{
[Fact]
public void CreateClientByInterface_Should_Use_The_Static_Factory_When_The_Client_Name_Does_Not_Follow_The_Convention()
{
Session session = CreateSession();

using AmazonServiceClient client = session.CreateClientByInterface<IMockFactoryAmazonService>();

Assert.IsType<FactoryCreatedMockClient>(client);
Assert.True(client.Config.UseHttp);
Assert.Equal("localhost", client.Config.ProxyHost);
Assert.Equal(1234, client.Config.ProxyPort);
Assert.Equal(RegionEndpoint.EUWest2, client.Config.RegionEndpoint);
}

[Fact]
public void CreateClientByInterface_Should_Wrap_Static_Factory_Failures_In_TargetInvocationException()
{
Session session = CreateSession();

TargetInvocationException exception = Assert.Throws<TargetInvocationException>(() => session.CreateClientByInterface<IMockThrowingFactoryAmazonService>());

Assert.Same(IMockThrowingFactoryAmazonService.Failure, exception.InnerException);
}

[Fact]
public void CreateClientByInterface_Should_Pass_The_Configured_ClientConfig_To_The_Static_Factory()
{
Session session = CreateSession();
FactoryCreatedMockClient.RecordedConfig = null;

using AmazonServiceClient client = session.CreateClientByInterface<IMockFactoryAmazonService>(useServiceUrl: true);

Assert.Same(FactoryCreatedMockClient.RecordedConfig, client.Config);
Assert.Equal("http://localhost:1234/", client.Config.ServiceURL);
}

private static Session CreateSession()
{
var config = new Mock<Contracts.IConfig>(MockBehavior.Strict);
config.Setup(value => value.GetAwsServiceEndpoint(MockServiceMetadata.MockServiceId)).Returns(new MockAwsServiceEndpoint());
config.Setup(value => value.GetConfigOptions()).Returns(new ConfigOptions());
return new Session(new SessionOptions(regionName: "eu-west-2"), config.Object, new SessionReflection());
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#if NET10_0_OR_GREATER
namespace LocalStack.Tests.Common.Mocks.MockServiceClients;

public sealed class FactoryCreatedMockClient(AWSCredentials credentials, MockClientConfig clientConfig)
: MockAmazonServiceClient(credentials, clientConfig), IMockFactoryAmazonService
{
public static ClientConfig? RecordedConfig { get; set; }
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public interface IMockAmazonService : IDisposable, IAmazonService

static IAmazonService IAmazonService.CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
{
return new MockAmazonServiceClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
return new MockAmazonServiceClient(awsCredentials, (MockClientConfig)clientConfig);
}
#pragma warning restore CA1033
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public interface IMockAmazonServiceWithServiceMetadata : IDisposable, IAmazonSer

static IAmazonService IAmazonService.CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
{
return new MockAmazonServiceWithServiceMetadataClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
return new MockAmazonServiceWithServiceMetadataClient(awsCredentials, (MockClientConfig)clientConfig);
}
#pragma warning restore CA1033
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#if NET10_0_OR_GREATER
namespace LocalStack.Tests.Common.Mocks.MockServiceClients;

public interface IMockFactoryAmazonService : IAmazonService
{
#pragma warning disable CA1033
static ClientConfig IAmazonService.CreateDefaultClientConfig() => MockClientConfig.CreateDefaultMockClientConfig();

static IAmazonService IAmazonService.CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
{
FactoryCreatedMockClient.RecordedConfig = clientConfig;
return new FactoryCreatedMockClient(awsCredentials, (MockClientConfig)clientConfig);
}
#pragma warning restore CA1033
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#if NET10_0_OR_GREATER
namespace LocalStack.Tests.Common.Mocks.MockServiceClients;

public interface IMockThrowingFactoryAmazonService : IAmazonService
{
static InvalidOperationException Failure { get; } = new("fixture static factory failure");

#pragma warning disable CA1033
static ClientConfig IAmazonService.CreateDefaultClientConfig() => MockClientConfig.CreateDefaultMockClientConfig();

static IAmazonService IAmazonService.CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig) => throw Failure;
#pragma warning restore CA1033
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static ClientConfig CreateDefaultClientConfig()

public static IAmazonService CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
{
return new MockAmazonServiceClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
return new MockAmazonServiceClient(awsCredentials, (MockClientConfig)clientConfig);
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static ClientConfig CreateDefaultClientConfig()

public static IAmazonService CreateDefaultServiceClient(AWSCredentials awsCredentials, ClientConfig clientConfig)
{
return new MockAmazonServiceWithServiceMetadataClient(awsCredentials, MockClientConfig.CreateDefaultMockClientConfig());
return new MockAmazonServiceWithServiceMetadataClient(awsCredentials, (MockClientConfig)clientConfig);
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public MockClientConfig() : this(new MockConfigurationProvider())

public MockClientConfig(IDefaultConfigurationProvider configurationProvider) : base(configurationProvider)
{
ServiceId = MockServiceMetadata.MockServiceId;
ServiceURL = "http://localhost";
}

Expand All @@ -25,4 +26,4 @@ public override Endpoint DetermineServiceOperationEndpoint(ServiceOperationEndpo
public override string RegionEndpointServiceName => "mock-service";

public static MockClientConfig CreateDefaultMockClientConfig() => new(new MockConfigurationProvider());
}
}
Loading