diff --git a/.gitignore b/.gitignore
index a961f2e..25cc3cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -340,4 +340,8 @@ testrunner
# Visual Studio Code options directory
-.vscode/
\ No newline at end of file
+.vscode/
+
+internal-docs/
+
+copilot-instructions.md
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 2c1d5d2..1843123 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,6 +9,7 @@
2.0.0
true
snupkg
+ true
13.0
true
latest
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2ebfc00..10b9cb6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,6 +4,8 @@
+
+
diff --git a/LocalStack.sln b/LocalStack.sln
index 51ed6dc..57b57d1 100644
--- a/LocalStack.sln
+++ b/LocalStack.sln
@@ -45,60 +45,190 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{FF7B26
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalStack.Tests.Common", "tests\common\LocalStack.Tests.Common\LocalStack.Tests.Common.csproj", "{7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalStack.Client.Generators", "src\LocalStack.Client.Generators\LocalStack.Client.Generators.csproj", "{B5254DE8-A300-45E9-9960-96B7C8606A04}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalStack.Client.AotCompatibility.Tests", "tests\LocalStack.Client.AotCompatibility.Tests\LocalStack.Client.AotCompatibility.Tests.csproj", "{28A177A6-256A-4E89-9FB2-0438670FD035}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{22C080D7-929C-44F1-909C-831EF9D2810F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22C080D7-929C-44F1-909C-831EF9D2810F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Debug|x64.Build.0 = Debug|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Debug|x86.Build.0 = Debug|Any CPU
{22C080D7-929C-44F1-909C-831EF9D2810F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22C080D7-929C-44F1-909C-831EF9D2810F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Release|x64.ActiveCfg = Release|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Release|x64.Build.0 = Release|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Release|x86.ActiveCfg = Release|Any CPU
+ {22C080D7-929C-44F1-909C-831EF9D2810F}.Release|x86.Build.0 = Release|Any CPU
{E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Debug|x64.Build.0 = Debug|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Debug|x86.Build.0 = Debug|Any CPU
{E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Release|x64.ActiveCfg = Release|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Release|x64.Build.0 = Release|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Release|x86.ActiveCfg = Release|Any CPU
+ {E7E16B66-EE23-4B49-89C5-4FF64F2ED95D}.Release|x86.Build.0 = Release|Any CPU
{9FC6CABE-ED38-4048-B511-69D76870ABF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9FC6CABE-ED38-4048-B511-69D76870ABF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Debug|x64.Build.0 = Debug|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Debug|x86.Build.0 = Debug|Any CPU
{9FC6CABE-ED38-4048-B511-69D76870ABF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9FC6CABE-ED38-4048-B511-69D76870ABF8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Release|x64.ActiveCfg = Release|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Release|x64.Build.0 = Release|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Release|x86.ActiveCfg = Release|Any CPU
+ {9FC6CABE-ED38-4048-B511-69D76870ABF8}.Release|x86.Build.0 = Release|Any CPU
{691A4094-2074-474A-81A3-E33B728AE54E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{691A4094-2074-474A-81A3-E33B728AE54E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Debug|x64.Build.0 = Debug|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Debug|x86.Build.0 = Debug|Any CPU
{691A4094-2074-474A-81A3-E33B728AE54E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{691A4094-2074-474A-81A3-E33B728AE54E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Release|x64.ActiveCfg = Release|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Release|x64.Build.0 = Release|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Release|x86.ActiveCfg = Release|Any CPU
+ {691A4094-2074-474A-81A3-E33B728AE54E}.Release|x86.Build.0 = Release|Any CPU
{4E90D3D1-D570-4205-9C6E-B917B5508912}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E90D3D1-D570-4205-9C6E-B917B5508912}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Debug|x64.Build.0 = Debug|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Debug|x86.Build.0 = Debug|Any CPU
{4E90D3D1-D570-4205-9C6E-B917B5508912}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E90D3D1-D570-4205-9C6E-B917B5508912}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Release|x64.ActiveCfg = Release|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Release|x64.Build.0 = Release|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Release|x86.ActiveCfg = Release|Any CPU
+ {4E90D3D1-D570-4205-9C6E-B917B5508912}.Release|x86.Build.0 = Release|Any CPU
{74035094-A726-44E2-9B88-42D6425D8548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{74035094-A726-44E2-9B88-42D6425D8548}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Debug|x64.Build.0 = Debug|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Debug|x86.Build.0 = Debug|Any CPU
{74035094-A726-44E2-9B88-42D6425D8548}.Release|Any CPU.ActiveCfg = Release|Any CPU
{74035094-A726-44E2-9B88-42D6425D8548}.Release|Any CPU.Build.0 = Release|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Release|x64.ActiveCfg = Release|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Release|x64.Build.0 = Release|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Release|x86.ActiveCfg = Release|Any CPU
+ {74035094-A726-44E2-9B88-42D6425D8548}.Release|x86.Build.0 = Release|Any CPU
{350EF226-D0CE-4C8C-83D1-22E638F46862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{350EF226-D0CE-4C8C-83D1-22E638F46862}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Debug|x64.Build.0 = Debug|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Debug|x86.Build.0 = Debug|Any CPU
{350EF226-D0CE-4C8C-83D1-22E638F46862}.Release|Any CPU.ActiveCfg = Release|Any CPU
{350EF226-D0CE-4C8C-83D1-22E638F46862}.Release|Any CPU.Build.0 = Release|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Release|x64.ActiveCfg = Release|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Release|x64.Build.0 = Release|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Release|x86.ActiveCfg = Release|Any CPU
+ {350EF226-D0CE-4C8C-83D1-22E638F46862}.Release|x86.Build.0 = Release|Any CPU
{0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Debug|x64.Build.0 = Debug|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Debug|x86.Build.0 = Debug|Any CPU
{0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Release|x64.ActiveCfg = Release|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Release|x64.Build.0 = Release|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Release|x86.ActiveCfg = Release|Any CPU
+ {0F24D1F8-DB6B-439E-BD6D-23E8DA88615A}.Release|x86.Build.0 = Release|Any CPU
{A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Debug|x64.Build.0 = Debug|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Debug|x86.Build.0 = Debug|Any CPU
{A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Release|x64.ActiveCfg = Release|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Release|x64.Build.0 = Release|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Release|x86.ActiveCfg = Release|Any CPU
+ {A697D9A2-4DF7-4B4D-A189-EEC7F64B5609}.Release|x86.Build.0 = Release|Any CPU
{6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Debug|x64.Build.0 = Debug|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Debug|x86.Build.0 = Debug|Any CPU
{6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Release|x64.ActiveCfg = Release|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Release|x64.Build.0 = Release|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Release|x86.ActiveCfg = Release|Any CPU
+ {6CCFBCE0-C7C6-42A7-B39F-665B4C15C6FE}.Release|x86.Build.0 = Release|Any CPU
{2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Debug|x64.Build.0 = Debug|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Debug|x86.Build.0 = Debug|Any CPU
{2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Release|x64.ActiveCfg = Release|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Release|x64.Build.0 = Release|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Release|x86.ActiveCfg = Release|Any CPU
+ {2CA18A71-CA83-4CC4-A777-AA4F56E4413F}.Release|x86.Build.0 = Release|Any CPU
{7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Debug|x64.Build.0 = Debug|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Debug|x86.Build.0 = Debug|Any CPU
{7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Release|x64.ActiveCfg = Release|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Release|x64.Build.0 = Release|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Release|x86.ActiveCfg = Release|Any CPU
+ {7B896BF0-E9E1-44B7-9268-78A6B45CFE0D}.Release|x86.Build.0 = Release|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Debug|x64.Build.0 = Debug|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Debug|x86.Build.0 = Debug|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Release|x64.ActiveCfg = Release|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Release|x64.Build.0 = Release|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Release|x86.ActiveCfg = Release|Any CPU
+ {B5254DE8-A300-45E9-9960-96B7C8606A04}.Release|x86.Build.0 = Release|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Debug|x64.Build.0 = Debug|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Debug|x86.Build.0 = Debug|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Release|Any CPU.Build.0 = Release|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Release|x64.ActiveCfg = Release|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Release|x64.Build.0 = Release|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Release|x86.ActiveCfg = Release|Any CPU
+ {28A177A6-256A-4E89-9FB2-0438670FD035}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -116,6 +246,8 @@ Global
{2CA18A71-CA83-4CC4-A777-AA4F56E4413F} = {152F3084-DC30-4A44-AEBC-E4C0EBFA0F4E}
{FF7B2686-CC4B-4B6C-B360-E487339DB210} = {3F0F4BAA-02EF-4008-9CF8-E73AA92D4664}
{7B896BF0-E9E1-44B7-9268-78A6B45CFE0D} = {FF7B2686-CC4B-4B6C-B360-E487339DB210}
+ {B5254DE8-A300-45E9-9960-96B7C8606A04} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
+ {28A177A6-256A-4E89-9FB2-0438670FD035} = {3F0F4BAA-02EF-4008-9CF8-E73AA92D4664}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E4925255-67AA-4095-816B-CC10A5490E71}
diff --git a/src/LocalStack.Client.Extensions/GlobalUsings.cs b/src/LocalStack.Client.Extensions/GlobalUsings.cs
index fdd94f5..f895105 100644
--- a/src/LocalStack.Client.Extensions/GlobalUsings.cs
+++ b/src/LocalStack.Client.Extensions/GlobalUsings.cs
@@ -7,12 +7,14 @@
global using Microsoft.Extensions.DependencyInjection.Extensions;
global using Microsoft.Extensions.Options;
-global using Amazon;
global using Amazon.Extensions.NETCore.Setup;
global using Amazon.Runtime;
global using LocalStack.Client.Contracts;
global using LocalStack.Client.Extensions.Contracts;
global using LocalStack.Client.Options;
+global using LocalStack.Client.Extensions.Exceptions;
+
+#if NETSTANDARD || NET472
global using LocalStack.Client.Utils;
-global using LocalStack.Client.Extensions.Exceptions;
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj
index e0aec55..446b9ae 100644
--- a/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj
+++ b/src/LocalStack.Client.Extensions/LocalStack.Client.Extensions.csproj
@@ -36,8 +36,8 @@
-
-
+
+
diff --git a/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs b/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs
index 2fd7e65..e5208cf 100644
--- a/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs
+++ b/src/LocalStack.Client.Extensions/ServiceCollectionExtensions.cs
@@ -188,8 +188,20 @@ private static IServiceCollection AddLocalStackServices(this IServiceCollection
ConfigOptions options = provider.GetRequiredService>().Value;
return new Config(options);
- })
- .AddSingleton()
+ });
+
+#if NET8_0_OR_GREATER
+ // Modern: Pure accessor-based, no reflection dependency
+ services.AddSingleton(provider =>
+ {
+ SessionOptions sessionOptions = provider.GetRequiredService>().Value;
+ var config = provider.GetRequiredService();
+
+ return new Session(sessionOptions, config);
+ });
+#elif NETFRAMEWORK || NETSTANDARD
+ // Legacy: Reflection-based implementation
+ services.AddSingleton()
.AddSingleton(provider =>
{
SessionOptions sessionOptions = provider.GetRequiredService>().Value;
@@ -197,8 +209,12 @@ private static IServiceCollection AddLocalStackServices(this IServiceCollection
var sessionReflection = provider.GetRequiredService();
return new Session(sessionOptions, config, sessionReflection);
- })
- .AddSingleton();
+ });
+#else
+ throw new NotSupportedException("This library is only supported on .NET Framework, .NET Standard, or .NET 8.0 or higher.");
+#endif
+
+ services.AddSingleton();
return services;
}
diff --git a/src/LocalStack.Client.Generators/AwsAccessorGenerator.cs b/src/LocalStack.Client.Generators/AwsAccessorGenerator.cs
new file mode 100644
index 0000000..297a4b3
--- /dev/null
+++ b/src/LocalStack.Client.Generators/AwsAccessorGenerator.cs
@@ -0,0 +1,446 @@
+namespace LocalStack.Client.Generators;
+
+///
+/// Incremental source generator that discovers AWS SDK clients at compile-time
+/// and generates strongly-typed UnsafeAccessor implementations for Native AOT compatibility.
+///
+[Generator]
+public sealed class AwsAccessorGenerator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Only generate for .NET 8+ projects to avoid affecting legacy builds
+ var isNet8OrAbove = context.CompilationProvider
+ .Select((compilation, _) => IsTargetFrameworkNet8OrAbove(compilation));
+
+ // Discover AWS service clients from compilation metadata (referenced assemblies)
+ var awsClients = context.CompilationProvider
+ .Select((compilation, _) => FindAwsClientsInMetadata(compilation).ToImmutableArray());
+
+ // Combine framework check with discovered clients
+ var generationInput = isNet8OrAbove
+ .Combine(awsClients);
+
+ // Generate accessor implementations
+ context.RegisterSourceOutput(generationInput, (spc, input) =>
+ {
+ var (isNet8Plus, clients) = input;
+
+ // Always emit diagnostics for debugging
+ spc.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.AwsClientsGenerated,
+ Location.None,
+ $"Generator running: .NET8+ = {isNet8Plus}, Found {clients.Length} potential clients"));
+
+ // Only proceed if .NET 8+
+ if (!isNet8Plus)
+ {
+ spc.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.AwsClientsGenerated,
+ Location.None,
+ "Skipping generation - not .NET 8+"));
+ return;
+ }
+
+ if (clients.IsDefaultOrEmpty)
+ {
+ // Emit diagnostic: No AWS clients found
+ spc.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.NoAwsClientsFound,
+ Location.None));
+ return;
+ }
+
+ spc.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.AwsClientsGenerated,
+ Location.None,
+ $"Generating accessors for {clients.Length} AWS clients"));
+
+ GenerateAccessors(spc, clients);
+ });
+ }
+
+ private static bool IsTargetFrameworkNet8OrAbove(Compilation compilation)
+ {
+ // Check for .NET 8+ via predefined preprocessor symbols
+ if (compilation.Options is not CSharpCompilationOptions options)
+ return false;
+
+ // Try to get preprocessor symbols via reflection since the property might not be available in all versions
+ var preprocessorSymbols = GetPreprocessorSymbols(options);
+ return preprocessorSymbols.Contains("NET8_0_OR_GREATER", StringComparer.Ordinal);
+ }
+
+ private static IEnumerable GetPreprocessorSymbols(CSharpCompilationOptions options)
+ {
+ // Use reflection to access PreprocessorSymbolNames if available
+ var property = options.GetType().GetProperty("PreprocessorSymbolNames");
+ if (property?.GetValue(options) is IEnumerable symbols)
+ return symbols;
+
+ // Fallback: assume .NET 8+ since generator only runs on modern frameworks
+ return new[] { "NET8_0_OR_GREATER" };
+ }
+
+ private static IEnumerable FindAwsClientsInMetadata(Compilation compilation)
+ {
+ // Find the AmazonServiceClient base type
+ var baseSym = compilation.GetTypeByMetadataName("Amazon.Runtime.AmazonServiceClient");
+ if (baseSym is null)
+ {
+ // AWS SDK not referenced, no clients to find
+ yield break;
+ }
+
+ foreach (var reference in compilation.References)
+ {
+ if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
+ {
+ foreach (var client in GetAwsClientsFromAssembly(assembly.GlobalNamespace, baseSym))
+ {
+ yield return client;
+ }
+ }
+ }
+ }
+
+ private static IEnumerable GetAwsClientsFromAssembly(INamespaceSymbol namespaceSymbol, INamedTypeSymbol baseType)
+ {
+ foreach (var member in namespaceSymbol.GetMembers())
+ {
+ if (member is INamespaceSymbol nestedNamespace)
+ {
+ foreach (var client in GetAwsClientsFromAssembly(nestedNamespace, baseType))
+ {
+ yield return client;
+ }
+ }
+ else if (member is INamedTypeSymbol typeSymbol && InheritsFromAmazonServiceClient(typeSymbol, baseType))
+ {
+ // Try to find the corresponding ClientConfig type
+ var configType = FindClientConfigType(typeSymbol);
+ if (configType != null)
+ {
+ // Find the corresponding service interface
+ var serviceInterface = FindServiceInterface(typeSymbol);
+
+ yield return new AwsClientInfo(
+ clientType: typeSymbol,
+ configType: configType,
+ serviceInterface: serviceInterface);
+ }
+ }
+ }
+ }
+
+ private static bool InheritsFromAmazonServiceClient(INamedTypeSymbol typeSymbol, INamedTypeSymbol baseType)
+ {
+ var current = typeSymbol.BaseType;
+ while (current != null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(current, baseType))
+ {
+ return true;
+ }
+ current = current.BaseType;
+ }
+ return false;
+ }
+
+ private static INamedTypeSymbol? FindClientConfigType(INamedTypeSymbol clientSymbol)
+ {
+ // Convention: AmazonS3Client -> AmazonS3Config
+ var clientName = clientSymbol.Name;
+ if (!clientName.EndsWith("Client", StringComparison.Ordinal))
+ return null;
+
+ var configName = clientName.Substring(0, clientName.Length - 6) + "Config"; // Remove "Client", add "Config"
+ var containingNamespace = clientSymbol.ContainingNamespace;
+
+ // Look for the config type in the same namespace
+ return containingNamespace.GetTypeMembers(configName).FirstOrDefault();
+ }
+
+ private static INamedTypeSymbol? FindServiceInterface(INamedTypeSymbol clientSymbol)
+ {
+ // Convention: AmazonS3Client -> IAmazonS3
+ var clientName = clientSymbol.Name;
+ if (!clientName.EndsWith("Client", StringComparison.Ordinal))
+ return null;
+
+ var interfaceName = "I" + clientName.Substring(0, clientName.Length - 6); // Remove "Client", add "I" prefix
+ var containingNamespace = clientSymbol.ContainingNamespace;
+
+ // Look for the interface in the same namespace
+ return containingNamespace.GetTypeMembers(interfaceName).FirstOrDefault();
+ }
+
+ private static void GenerateAccessors(SourceProductionContext context, ImmutableArray clients)
+ {
+ var sourceBuilder = new StringBuilder();
+
+ // Generate file header with single namespace
+ sourceBuilder.AppendLine("// ");
+ sourceBuilder.AppendLine("// Generated by LocalStack.Client.Generators");
+ sourceBuilder.AppendLine("#nullable enable");
+ sourceBuilder.AppendLine();
+ sourceBuilder.AppendLine("using System;");
+ sourceBuilder.AppendLine("using System.Diagnostics.CodeAnalysis;");
+ sourceBuilder.AppendLine("using System.Runtime.CompilerServices;");
+ sourceBuilder.AppendLine("using Amazon;");
+ sourceBuilder.AppendLine("using Amazon.Runtime;");
+ sourceBuilder.AppendLine("using Amazon.Runtime.Internal;");
+ sourceBuilder.AppendLine("using LocalStack.Client.Utils;");
+ sourceBuilder.AppendLine();
+ sourceBuilder.AppendLine("namespace LocalStack.Client.Generated");
+ sourceBuilder.AppendLine("{");
+
+ // Generate accessor for each client
+ for (int i = 0; i < clients.Length; i++)
+ {
+ if (i > 0)
+ {
+ sourceBuilder.AppendLine();
+ }
+ GenerateAccessorClass(sourceBuilder, clients[i]);
+ }
+
+ sourceBuilder.AppendLine();
+
+ // Generate module initializer
+ GenerateModuleInitializer(sourceBuilder, clients);
+
+ sourceBuilder.AppendLine("}"); // Close namespace
+
+ // Add the generated source
+ context.AddSource("AwsAccessors.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
+ }
+
+ private static void GenerateAccessorClass(StringBuilder builder, AwsClientInfo client)
+ {
+ var clientTypeName = client.ClientType.ToDisplayString();
+ var configTypeName = client.ConfigType.ToDisplayString();
+ var accessorClassName = client.ClientType.Name + "_Accessor";
+
+ builder.AppendLine($" internal sealed class {accessorClassName} : IAwsAccessor");
+ builder.AppendLine(" {");
+
+ // Type properties
+ builder.AppendLine($" public System.Type ClientType => typeof({clientTypeName});");
+ builder.AppendLine($" public System.Type ConfigType => typeof({configTypeName});");
+ builder.AppendLine();
+
+ // UnsafeAccessor methods
+ builder.AppendLine(" [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = \"serviceMetadata\")]");
+ builder.AppendLine($" private static extern ref IServiceMetadata GetServiceMetadataField({clientTypeName}? instance);");
+ builder.AppendLine();
+
+ builder.AppendLine(" [UnsafeAccessor(UnsafeAccessorKind.Constructor)]");
+ builder.AppendLine($" private static extern {configTypeName} CreateConfig();");
+ builder.AppendLine();
+
+ builder.AppendLine(" [UnsafeAccessor(UnsafeAccessorKind.Constructor)]");
+ builder.AppendLine($" private static extern {clientTypeName} CreateClient(AWSCredentials credentials, {configTypeName} config);");
+ builder.AppendLine();
+
+ // Interface implementations - add DynamicDependency to the methods that need them
+ builder.AppendLine($" [DynamicDependency(\"serviceMetadata\", typeof({clientTypeName}))]");
+ builder.AppendLine(" public IServiceMetadata GetServiceMetadata()");
+ builder.AppendLine(" {");
+ builder.AppendLine(" ref var metadata = ref GetServiceMetadataField(null);");
+ builder.AppendLine(" return metadata;");
+ builder.AppendLine(" }");
+ builder.AppendLine();
+
+ builder.AppendLine($" [DynamicDependency(\".ctor\", typeof({configTypeName}))]");
+ builder.AppendLine(" public ClientConfig CreateClientConfig()");
+ builder.AppendLine(" => CreateConfig();");
+ builder.AppendLine();
+
+ builder.AppendLine(" public AmazonServiceClient CreateClient(AWSCredentials credentials, ClientConfig clientConfig)");
+ builder.AppendLine($" => CreateClient(credentials, ({configTypeName})clientConfig);");
+ builder.AppendLine();
+
+ // Generate SetRegion implementation
+ GenerateSetRegionMethod(builder, client);
+ builder.AppendLine();
+
+ // Generate TrySetForcePathStyle implementation
+ GenerateForcePathStyleSetMethod(builder, client);
+ builder.AppendLine();
+
+ // Generate TryGetForcePathStyle implementation
+ GenerateForcePathStyleGetMethod(builder, client);
+
+ builder.AppendLine(" }");
+ }
+
+ private static void GenerateSetRegionMethod(StringBuilder builder, AwsClientInfo client)
+ {
+ var configTypeName = client.ConfigType.ToDisplayString();
+
+ // Check if the config type has RegionEndpoint property (including base classes)
+ var hasRegionEndpointProperty = GetAllProperties(client.ConfigType)
+ .Any(p => p.Name == "RegionEndpoint" && p.SetMethod != null);
+
+ builder.AppendLine(" public void SetRegion(ClientConfig clientConfig, RegionEndpoint regionEndpoint)");
+ builder.AppendLine(" {");
+
+ // Always add null check (matches SessionReflectionLegacy behavior)
+ builder.AppendLine(" if (clientConfig == null)");
+ builder.AppendLine(" throw new ArgumentNullException(nameof(clientConfig));");
+ builder.AppendLine();
+
+ if (hasRegionEndpointProperty)
+ {
+ // Use public property approach - matches legacy: amazonServiceClient.Config.RegionEndpoint = ...
+ builder.AppendLine($" (({configTypeName})clientConfig).RegionEndpoint = regionEndpoint;");
+ }
+ else
+ {
+ // Silent no-op if property doesn't exist (matches legacy behavior with optional chaining)
+ builder.AppendLine($" // RegionEndpoint property not available on {configTypeName}");
+ }
+
+ builder.AppendLine(" }");
+ }
+
+ private static void GenerateForcePathStyleSetMethod(StringBuilder builder, AwsClientInfo client)
+ {
+ var configTypeName = client.ConfigType.ToDisplayString();
+
+ // Pure property discovery - matches SessionReflectionLegacy.SetForcePathStyle exactly
+ var hasForcePathStyleProperty = GetAllProperties(client.ConfigType)
+ .Any(p => p.Name == "ForcePathStyle" &&
+ p.SetMethod?.DeclaredAccessibility == Accessibility.Public &&
+ (p.Type.SpecialType == SpecialType.System_Boolean ||
+ p.Type.Name == "Boolean"));
+
+ builder.AppendLine(" public bool TrySetForcePathStyle(ClientConfig clientConfig, bool value)");
+ builder.AppendLine(" {");
+
+ // Always add null check (matches SessionReflectionLegacy behavior)
+ builder.AppendLine(" if (clientConfig == null)");
+ builder.AppendLine(" throw new ArgumentNullException(nameof(clientConfig));");
+ builder.AppendLine();
+
+ if (hasForcePathStyleProperty)
+ {
+ // Property exists - set it and return true (matches legacy)
+ builder.AppendLine($" (({configTypeName})clientConfig).ForcePathStyle = value;");
+ builder.AppendLine(" return true;");
+ }
+ else
+ {
+ // Property doesn't exist - return false (matches legacy: forcePathStyleProperty == null)
+ builder.AppendLine(" return false;");
+ }
+
+ builder.AppendLine(" }");
+ }
+
+ private static void GenerateForcePathStyleGetMethod(StringBuilder builder, AwsClientInfo client)
+ {
+ var configTypeName = client.ConfigType.ToDisplayString();
+
+ // Same property discovery logic as the set method
+ var hasForcePathStyleProperty = GetAllProperties(client.ConfigType)
+ .Any(p => p.Name == "ForcePathStyle" &&
+ p.GetMethod?.DeclaredAccessibility == Accessibility.Public &&
+ (p.Type.SpecialType == SpecialType.System_Boolean ||
+ p.Type.Name == "Boolean"));
+
+ builder.AppendLine(" public bool TryGetForcePathStyle(ClientConfig clientConfig, out bool? value)");
+ builder.AppendLine(" {");
+
+ // Always add null check
+ builder.AppendLine(" if (clientConfig == null)");
+ builder.AppendLine(" throw new ArgumentNullException(nameof(clientConfig));");
+ builder.AppendLine();
+
+ if (hasForcePathStyleProperty)
+ {
+ // Property exists - get its value and return true
+ builder.AppendLine($" value = (({configTypeName})clientConfig).ForcePathStyle;");
+ builder.AppendLine(" return true;");
+ }
+ else
+ {
+ // Property doesn't exist - set value to null and return false
+ builder.AppendLine(" value = null;");
+ builder.AppendLine(" return false;");
+ }
+
+ builder.AppendLine(" }");
+ }
+
+ private static IEnumerable GetAllProperties(INamedTypeSymbol typeSymbol)
+ {
+ var type = typeSymbol;
+ while (type != null && type.SpecialType != SpecialType.System_Object)
+ {
+ foreach (var member in type.GetMembers())
+ {
+ if (member is IPropertySymbol property)
+ {
+ yield return property;
+ }
+ }
+ type = type.BaseType;
+ }
+ }
+
+ private static void GenerateModuleInitializer(StringBuilder builder, ImmutableArray clients)
+ {
+ builder.AppendLine(" internal static class GeneratedModuleInitializer");
+ builder.AppendLine(" {");
+ builder.AppendLine(" [ModuleInitializer]");
+ builder.AppendLine(" public static void RegisterGeneratedAccessors()");
+ builder.AppendLine(" {");
+
+ foreach (var client in clients)
+ {
+ var clientTypeName = client.ClientType.ToDisplayString();
+ var accessorClassName = client.ClientType.Name + "_Accessor";
+
+ builder.AppendLine($" AwsAccessorRegistry.Register<{clientTypeName}>(new {accessorClassName}());");
+
+ if (client.ServiceInterface != null)
+ {
+ var interfaceTypeName = client.ServiceInterface.ToDisplayString();
+ builder.AppendLine($" AwsAccessorRegistry.RegisterInterface<{interfaceTypeName}, {clientTypeName}>();");
+ }
+ }
+
+ builder.AppendLine(" }");
+ builder.AppendLine(" }");
+ }
+}
+
+///
+/// Information about a discovered AWS client and its related types.
+///
+internal sealed class AwsClientInfo
+{
+ public AwsClientInfo(INamedTypeSymbol clientType, INamedTypeSymbol configType, INamedTypeSymbol? serviceInterface)
+ {
+ ClientType = clientType;
+ ConfigType = configType;
+ ServiceInterface = serviceInterface;
+ }
+
+ ///
+ /// The AWS service client type (e.g., AmazonS3Client)
+ ///
+ public INamedTypeSymbol ClientType { get; }
+
+ ///
+ /// The corresponding client configuration type (e.g., AmazonS3Config)
+ ///
+ public INamedTypeSymbol ConfigType { get; }
+
+ ///
+ /// The service interface, if found (e.g., IAmazonS3)
+ ///
+ public INamedTypeSymbol? ServiceInterface { get; }
+}
\ No newline at end of file
diff --git a/src/LocalStack.Client.Generators/DiagnosticDescriptors.cs b/src/LocalStack.Client.Generators/DiagnosticDescriptors.cs
new file mode 100644
index 0000000..8608603
--- /dev/null
+++ b/src/LocalStack.Client.Generators/DiagnosticDescriptors.cs
@@ -0,0 +1,45 @@
+namespace LocalStack.Client.Generators;
+
+///
+/// Diagnostic descriptors for the LocalStack AWS accessor source generator.
+///
+internal static class DiagnosticDescriptors
+{
+ private const string Category = "LocalStack.Client.Generators";
+
+ public static readonly DiagnosticDescriptor NoAwsClientsFound = new(
+ id: "LSG001",
+ title: "No AWS clients found for code generation",
+ messageFormat: "No AWS SDK client types were found in the compilation. Ensure AWS SDK packages are referenced to enable LocalStack client generation.",
+ category: Category,
+ defaultSeverity: DiagnosticSeverity.Info,
+ isEnabledByDefault: true,
+ description: "The LocalStack source generator could not find any AWS service client types to generate accessors for. This is expected if no AWS SDK packages are referenced.");
+
+ public static readonly DiagnosticDescriptor MissingConfigType = new(
+ id: "LSG002",
+ title: "AWS client configuration type not found",
+ messageFormat: "Could not find configuration type for AWS client '{0}'. Expected type '{1}' in the same namespace.",
+ category: Category,
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "The source generator found an AWS client but could not locate its corresponding configuration type using naming conventions.");
+
+ public static readonly DiagnosticDescriptor AccessorGenerationError = new(
+ id: "LSG003",
+ title: "Error generating AWS client accessor",
+ messageFormat: "Failed to generate accessor for AWS client '{0}': {1}",
+ category: Category,
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "An error occurred while generating the UnsafeAccessor implementation for an AWS client.");
+
+ public static readonly DiagnosticDescriptor AwsClientsGenerated = new(
+ id: "LSG004",
+ title: "AWS client accessors generated successfully",
+ messageFormat: "Generated {0} AWS client accessor(s) for LocalStack Native AOT support",
+ category: Category,
+ defaultSeverity: DiagnosticSeverity.Info,
+ isEnabledByDefault: true,
+ description: "Successfully generated UnsafeAccessor implementations for the discovered AWS clients.");
+}
\ No newline at end of file
diff --git a/src/LocalStack.Client.Generators/GlobalUsings.cs b/src/LocalStack.Client.Generators/GlobalUsings.cs
new file mode 100644
index 0000000..edbbcfb
--- /dev/null
+++ b/src/LocalStack.Client.Generators/GlobalUsings.cs
@@ -0,0 +1,8 @@
+global using System;
+global using System.Collections.Generic;
+global using System.Collections.Immutable;
+global using System.Linq;
+global using System.Text;
+global using Microsoft.CodeAnalysis;
+global using Microsoft.CodeAnalysis.CSharp;
+global using Microsoft.CodeAnalysis.Text;
\ No newline at end of file
diff --git a/src/LocalStack.Client.Generators/LocalStack.Client.Generators.csproj b/src/LocalStack.Client.Generators/LocalStack.Client.Generators.csproj
new file mode 100644
index 0000000..c4bf0bf
--- /dev/null
+++ b/src/LocalStack.Client.Generators/LocalStack.Client.Generators.csproj
@@ -0,0 +1,29 @@
+
+
+
+ netstandard2.0
+ LocalStack.Client.Generators
+ LocalStack.Client.Generators
+ 13.0
+ enable
+
+
+ false
+ false
+ true
+ true
+
+
+ $(NoWarn);RS2008;MA0048;MA0051;MA0006;CA1305;CA1812;MA0002
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/LocalStack.Client.Generators/README.md b/src/LocalStack.Client.Generators/README.md
new file mode 100644
index 0000000..bdad217
--- /dev/null
+++ b/src/LocalStack.Client.Generators/README.md
@@ -0,0 +1,38 @@
+# LocalStack.Client.Generators
+
+**Roslyn Incremental Source Generator for Native AOT Support**
+
+This project contains the source generator that enables Native AOT compatibility for LocalStack.Client by eliminating runtime reflection usage.
+
+## What it does
+
+- **Discovers AWS SDK clients** at compile-time in consumer projects
+- **Generates strongly-typed UnsafeAccessor implementations** for private member access
+- **Emits ModuleInitializer** for automatic runtime registration
+- **Adds DynamicDependency attributes** for trimming safety
+
+## Architecture
+
+- **`AwsAccessorGenerator`**: Main incremental source generator
+- **`AwsClientDiscoverer`**: Semantic analysis for finding AWS clients
+- **`AccessorEmitter`**: Code generation for UnsafeAccessor implementations
+- **`DiagnosticDescriptors`**: Error and warning messages
+
+## Generated Code Pattern
+
+For each discovered AWS client (e.g., `AmazonS3Client`), generates:
+
+```csharp
+[DynamicDependency("serviceMetadata", typeof(Amazon.S3.AmazonS3Client))]
+internal sealed class AmazonS3Client_Accessor : IAwsAccessor
+{
+ [UnsafeAccessor(UnsafeAccessorKind.StaticField, Name = "serviceMetadata")]
+ private static extern ref IServiceMetadata GetServiceMetadataField(Amazon.S3.AmazonS3Client? instance);
+
+ // Additional UnsafeAccessor methods...
+}
+```
+
+## Integration
+
+The generator is conditionally included in the main `LocalStack.Client` package for .NET 8+ targets only, ensuring zero impact on legacy frameworks.
\ No newline at end of file
diff --git a/src/LocalStack.Client/Contracts/ISessionReflection.cs b/src/LocalStack.Client/Contracts/ISessionReflection.cs
index 64ab21b..ee3ccf7 100644
--- a/src/LocalStack.Client/Contracts/ISessionReflection.cs
+++ b/src/LocalStack.Client/Contracts/ISessionReflection.cs
@@ -1,4 +1,5 @@
-namespace LocalStack.Client.Contracts;
+#if NETFRAMEWORK || NETSTANDARD
+namespace LocalStack.Client.Contracts;
public interface ISessionReflection
{
@@ -13,4 +14,5 @@ public interface ISessionReflection
bool SetForcePathStyle(ClientConfig clientConfig, bool value = true);
void SetClientRegion(AmazonServiceClient amazonServiceClient, string systemName);
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/LocalStack.Client/GlobalUsings.cs b/src/LocalStack.Client/GlobalUsings.cs
index df142da..1575ff6 100644
--- a/src/LocalStack.Client/GlobalUsings.cs
+++ b/src/LocalStack.Client/GlobalUsings.cs
@@ -3,7 +3,6 @@
global using System.Diagnostics.CodeAnalysis;
global using System.Globalization;
global using System.Linq;
-global using System.Reflection;
global using System.Runtime.Serialization;
global using Amazon;
@@ -19,8 +18,10 @@
#pragma warning disable MA0048 // File name must match type name
#if NETSTANDARD || NET472
+global using System.Reflection;
namespace System.Runtime.CompilerServices
{
+ using System.Reflection;
using System.ComponentModel;
///
/// Reserved to be used by the compiler for tracking metadata.
diff --git a/src/LocalStack.Client/LocalStack.Client.csproj b/src/LocalStack.Client/LocalStack.Client.csproj
index 3a7e54c..6868fa5 100644
--- a/src/LocalStack.Client/LocalStack.Client.csproj
+++ b/src/LocalStack.Client/LocalStack.Client.csproj
@@ -25,11 +25,25 @@
true
+
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+
@@ -51,8 +65,4 @@
-
- NET472
-
-
\ No newline at end of file
diff --git a/src/LocalStack.Client/Session.cs b/src/LocalStack.Client/SessionLegacy.cs
similarity index 68%
rename from src/LocalStack.Client/Session.cs
rename to src/LocalStack.Client/SessionLegacy.cs
index 56523ec..9bc7d16 100644
--- a/src/LocalStack.Client/Session.cs
+++ b/src/LocalStack.Client/SessionLegacy.cs
@@ -1,7 +1,14 @@
-#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - disabled because of it's not possible for this case
+#if NETFRAMEWORK || NETSTANDARD
+#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - disabled because of it's not possible for this case
#pragma warning disable CS8603 // Possible null reference return. - disabled because of it's not possible for this case
+#pragma warning disable MA0048 // File name must match type name
+
namespace LocalStack.Client;
+///
+/// Legacy Session implementation for .NET Framework and .NET Standard using reflection.
+/// Maintains backward compatibility with traditional reflection approach.
+///
public class Session : ISession
{
private readonly IConfig _config;
@@ -17,9 +24,36 @@ public Session(ISessionOptions sessionOptions, IConfig config, ISessionReflectio
public TClient CreateClientByImplementation(bool useServiceUrl = false) where TClient : AmazonServiceClient
{
- Type clientType = typeof(TClient);
+ if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false.");
+ }
+
+ IServiceMetadata serviceMetadata = _sessionReflection.ExtractServiceMetadata();
+ AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ??
+ throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session.");
+
+ AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken);
+
+ // Legacy: Use reflection-based client creation
+ ClientConfig clientConfig = _sessionReflection.CreateClientConfig();
+
+ clientConfig.UseHttp = !_config.GetConfigOptions().UseSsl;
+ _sessionReflection.SetForcePathStyle(clientConfig);
+ 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);
+ }
- return (TClient)CreateClientByImplementation(clientType, useServiceUrl);
+ var clientInstance = (TClient)Activator.CreateInstance(typeof(TClient), awsCredentials, clientConfig);
+ return clientInstance;
}
public AmazonServiceClient CreateClientByImplementation(Type implType, bool useServiceUrl = false)
@@ -34,6 +68,8 @@ public AmazonServiceClient CreateClientByImplementation(Type implType, bool useS
throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session.");
AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken);
+
+ // Legacy: Use reflection-based client creation
ClientConfig clientConfig = _sessionReflection.CreateClientConfig(implType);
clientConfig.UseHttp = !_config.GetConfigOptions().UseSsl;
@@ -51,14 +87,18 @@ public AmazonServiceClient CreateClientByImplementation(Type implType, bool useS
}
var clientInstance = (AmazonServiceClient)Activator.CreateInstance(implType, awsCredentials, clientConfig);
-
return clientInstance;
}
public AmazonServiceClient CreateClientByInterface(bool useServiceUrl = false) where TClient : IAmazonService
{
- Type serviceInterfaceType = typeof(TClient);
+ if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false.");
+ }
+ // Legacy: Use reflection-based interface-to-client mapping
+ Type serviceInterfaceType = typeof(TClient);
return CreateClientByInterface(serviceInterfaceType, useServiceUrl);
}
@@ -118,4 +158,5 @@ public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType, bo
return client;
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/LocalStack.Client/SessionModern.cs b/src/LocalStack.Client/SessionModern.cs
new file mode 100644
index 0000000..02def0c
--- /dev/null
+++ b/src/LocalStack.Client/SessionModern.cs
@@ -0,0 +1,164 @@
+#if NET8_0_OR_GREATER
+#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - disabled because of it's not possible for this case
+#pragma warning disable CS8603 // Possible null reference return. - disabled because of it's not possible for this case
+#pragma warning disable MA0048 // File name must match type name
+
+namespace LocalStack.Client;
+
+///
+/// Modern Session implementation for .NET 8+ using generated accessors.
+/// Zero reflection dependencies - pure AOT compatibility.
+///
+public class Session : ISession
+{
+ private readonly IConfig _config;
+ private readonly ISessionOptions _sessionOptions;
+
+ public Session(ISessionOptions sessionOptions, IConfig config)
+ {
+ _sessionOptions = sessionOptions;
+ _config = config;
+ }
+
+ public TClient CreateClientByImplementation(bool useServiceUrl = false) where TClient : AmazonServiceClient
+ {
+ if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false.");
+ }
+
+ // Modern: Direct accessor-based approach - zero reflection
+ var accessor = AwsAccessorRegistry.Get(typeof(TClient));
+ IServiceMetadata serviceMetadata = accessor.GetServiceMetadata();
+ AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ??
+ throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session.");
+
+ AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken);
+ ClientConfig clientConfig = accessor.CreateClientConfig();
+
+ clientConfig.UseHttp = !_config.GetConfigOptions().UseSsl;
+ accessor.TrySetForcePathStyle(clientConfig, true);
+ clientConfig.ProxyHost = awsServiceEndpoint.Host;
+ clientConfig.ProxyPort = awsServiceEndpoint.Port;
+
+ if (useServiceUrl)
+ {
+ clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl.AbsoluteUri;
+ }
+ else if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ accessor.SetRegion(clientConfig, RegionEndpoint.GetBySystemName(_sessionOptions.RegionName));
+ }
+
+ var clientInstance = (TClient)accessor.CreateClient(awsCredentials, clientConfig);
+ return clientInstance;
+ }
+
+ public AmazonServiceClient CreateClientByImplementation(Type implType, bool useServiceUrl = false)
+ {
+ if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false.");
+ }
+
+ // Modern: Direct accessor-based approach - zero reflection
+ var accessor = AwsAccessorRegistry.Get(implType);
+ IServiceMetadata serviceMetadata = accessor.GetServiceMetadata();
+ AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ??
+ throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session.");
+
+ AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken);
+ ClientConfig clientConfig = accessor.CreateClientConfig();
+
+ clientConfig.UseHttp = !_config.GetConfigOptions().UseSsl;
+ accessor.TrySetForcePathStyle(clientConfig, true);
+ clientConfig.ProxyHost = awsServiceEndpoint.Host;
+ clientConfig.ProxyPort = awsServiceEndpoint.Port;
+
+ if (useServiceUrl)
+ {
+ clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl.AbsoluteUri;
+ }
+ else if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ accessor.SetRegion(clientConfig, RegionEndpoint.GetBySystemName(_sessionOptions.RegionName));
+ }
+
+ var clientInstance = accessor.CreateClient(awsCredentials, clientConfig);
+ return clientInstance;
+ }
+
+ public AmazonServiceClient CreateClientByInterface(bool useServiceUrl = false) where TClient : IAmazonService
+ {
+ if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false.");
+ }
+
+ // Modern: Direct registry-based interface-to-client mapping - zero reflection
+ var accessor = AwsAccessorRegistry.GetByInterface();
+ IServiceMetadata serviceMetadata = accessor.GetServiceMetadata();
+ AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ??
+ throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session.");
+
+ AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken);
+ ClientConfig clientConfig = accessor.CreateClientConfig();
+
+ clientConfig.UseHttp = !_config.GetConfigOptions().UseSsl;
+ accessor.TrySetForcePathStyle(clientConfig, true);
+ clientConfig.ProxyHost = awsServiceEndpoint.Host;
+ clientConfig.ProxyPort = awsServiceEndpoint.Port;
+
+ if (useServiceUrl)
+ {
+ clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl.AbsoluteUri;
+ }
+ else if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ accessor.SetRegion(clientConfig, RegionEndpoint.GetBySystemName(_sessionOptions.RegionName));
+ }
+
+ var client = accessor.CreateClient(awsCredentials, clientConfig);
+ return client;
+ }
+
+ public AmazonServiceClient CreateClientByInterface(Type serviceInterfaceType, bool useServiceUrl = false)
+ {
+ if (serviceInterfaceType == null)
+ {
+ throw new ArgumentNullException(nameof(serviceInterfaceType));
+ }
+
+ if (!useServiceUrl && string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ throw new MisconfiguredClientException($"{nameof(_sessionOptions.RegionName)} must be set if {nameof(useServiceUrl)} is false.");
+ }
+
+ // Modern: Direct registry-based interface-to-client mapping with Type parameter - zero reflection
+ var accessor = AwsAccessorRegistry.GetByInterface(serviceInterfaceType);
+ IServiceMetadata serviceMetadata = accessor.GetServiceMetadata();
+ AwsServiceEndpoint awsServiceEndpoint = _config.GetAwsServiceEndpoint(serviceMetadata.ServiceId) ??
+ throw new NotSupportedClientException($"{serviceMetadata.ServiceId} is not supported by this mock session.");
+
+ AWSCredentials awsCredentials = new SessionAWSCredentials(_sessionOptions.AwsAccessKeyId, _sessionOptions.AwsAccessKey, _sessionOptions.AwsSessionToken);
+ ClientConfig clientConfig = accessor.CreateClientConfig();
+
+ clientConfig.UseHttp = !_config.GetConfigOptions().UseSsl;
+ accessor.TrySetForcePathStyle(clientConfig, true);
+ clientConfig.ProxyHost = awsServiceEndpoint.Host;
+ clientConfig.ProxyPort = awsServiceEndpoint.Port;
+
+ if (useServiceUrl)
+ {
+ clientConfig.ServiceURL = awsServiceEndpoint.ServiceUrl.AbsoluteUri;
+ }
+ else if (!string.IsNullOrWhiteSpace(_sessionOptions.RegionName))
+ {
+ accessor.SetRegion(clientConfig, RegionEndpoint.GetBySystemName(_sessionOptions.RegionName));
+ }
+
+ var client = accessor.CreateClient(awsCredentials, clientConfig);
+ return client;
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/LocalStack.Client/SessionStandalone.cs b/src/LocalStack.Client/SessionStandalone.cs
index cbdfd17..1b0deee 100644
--- a/src/LocalStack.Client/SessionStandalone.cs
+++ b/src/LocalStack.Client/SessionStandalone.cs
@@ -27,9 +27,16 @@ public ISession Create()
{
ISessionOptions sessionOptions = _sessionOptions ?? new SessionOptions();
IConfig config = new Config(_configOptions ?? new ConfigOptions());
+
+#if NETFRAMEWORK || NETSTANDARD
ISessionReflection sessionReflection = new SessionReflection();
return new Session(sessionOptions, config, sessionReflection);
+#elif NET8_0_OR_GREATER
+ return new Session(sessionOptions, config);
+#else
+ throw new NotSupportedException("This library is only supported on .NET Framework, .NET Standard, and .NET 8.0 and above.");
+#endif
}
public static ISessionStandalone Init()
diff --git a/src/LocalStack.Client/Utils/AwsAccessorRegistry.cs b/src/LocalStack.Client/Utils/AwsAccessorRegistry.cs
new file mode 100644
index 0000000..391c7b7
--- /dev/null
+++ b/src/LocalStack.Client/Utils/AwsAccessorRegistry.cs
@@ -0,0 +1,113 @@
+using System.Collections.Concurrent;
+
+namespace LocalStack.Client.Utils;
+
+///
+/// Thread-safe registry for AWS client accessors.
+/// Populated by generated ModuleInitializer methods for .NET 8+ builds.
+///
+public static class AwsAccessorRegistry
+{
+ private static readonly ConcurrentDictionary Accessors = new();
+ private static readonly ConcurrentDictionary InterfaceToClientMap = new();
+
+ ///
+ /// Registers an accessor for a specific AWS client type.
+ /// Called by generated ModuleInitializer methods.
+ ///
+ public static void Register(IAwsAccessor accessor) where TClient : AmazonServiceClient
+ {
+ Accessors.TryAdd(typeof(TClient), accessor);
+ }
+
+ ///
+ /// Registers the mapping from an AWS service interface to its concrete client type.
+ /// Called by generated ModuleInitializer methods.
+ ///
+ public static void RegisterInterface()
+ where TInterface : IAmazonService
+ where TClient : AmazonServiceClient
+ {
+ InterfaceToClientMap.TryAdd(typeof(TInterface), typeof(TClient));
+ }
+
+ ///
+ /// Gets the registered accessor for the specified AWS client type.
+ /// Throws NotSupportedException if no accessor is registered.
+ ///
+ public static IAwsAccessor Get(Type clientType)
+ {
+ if (clientType == null)
+ {
+ throw new ArgumentNullException(nameof(clientType));
+ }
+
+ if (Accessors.TryGetValue(clientType, out var accessor))
+ {
+ return accessor;
+ }
+
+ throw new NotSupportedException(
+ $"No AWS accessor registered for client type '{clientType.FullName}'. " +
+ "Ensure the AWS SDK package is referenced and the project targets .NET 8 or later for AOT compatibility.");
+ }
+
+ ///
+ /// Gets the registered accessor for the specified AWS service interface type.
+ /// Resolves the interface to its concrete client type, then returns the accessor.
+ /// Throws NotSupportedException if no mapping or accessor is registered.
+ ///
+ public static IAwsAccessor GetByInterface() where TInterface : IAmazonService
+ {
+ return GetByInterface(typeof(TInterface));
+ }
+
+ ///
+ /// Gets the registered accessor for the specified AWS service interface type.
+ /// Resolves the interface to its concrete client type, then returns the accessor.
+ /// Throws NotSupportedException if no mapping or accessor is registered.
+ ///
+ public static IAwsAccessor GetByInterface(Type interfaceType)
+ {
+ if (interfaceType == null)
+ {
+ throw new ArgumentNullException(nameof(interfaceType));
+ }
+
+ if (!InterfaceToClientMap.TryGetValue(interfaceType, out var clientType))
+ {
+ throw new NotSupportedException(
+ $"No AWS client type registered for interface '{interfaceType.FullName}'. " +
+ "Ensure the AWS SDK package is referenced and the project targets .NET 8 or later for AOT compatibility.");
+ }
+
+ return Get(clientType);
+ }
+
+ ///
+ /// Attempts to get the registered accessor for the specified AWS client type.
+ /// Returns true if found, false otherwise.
+ ///
+ public static bool TryGet(Type clientType, out IAwsAccessor? accessor)
+ {
+ return Accessors.TryGetValue(clientType, out accessor);
+ }
+
+ ///
+ /// Gets the number of registered accessors.
+ /// Used for diagnostics and testing.
+ ///
+ public static int Count => Accessors.Count;
+
+ ///
+ /// Gets all registered client types.
+ /// Used for diagnostics and testing.
+ ///
+ public static IEnumerable RegisteredClientTypes => Accessors.Keys;
+
+ ///
+ /// Gets all registered interface types.
+ /// Used for diagnostics and testing.
+ ///
+ public static IEnumerable RegisteredInterfaceTypes => InterfaceToClientMap.Keys;
+}
\ No newline at end of file
diff --git a/src/LocalStack.Client/Utils/IAwsAccessor.cs b/src/LocalStack.Client/Utils/IAwsAccessor.cs
new file mode 100644
index 0000000..b4364a8
--- /dev/null
+++ b/src/LocalStack.Client/Utils/IAwsAccessor.cs
@@ -0,0 +1,55 @@
+namespace LocalStack.Client.Utils;
+
+///
+/// Interface for type-safe AWS SDK private member access.
+/// Implementations are generated at compile-time for .NET 8+ or use reflection for legacy frameworks.
+///
+public interface IAwsAccessor
+{
+ ///
+ /// Gets the AWS client type this accessor supports.
+ ///
+ Type ClientType { get; }
+
+ ///
+ /// Gets the AWS client configuration type this accessor supports.
+ ///
+ Type ConfigType { get; }
+
+ ///
+ /// Gets the service metadata for the AWS client.
+ /// Accesses the private static 'serviceMetadata' field.
+ ///
+ IServiceMetadata GetServiceMetadata();
+
+ ///
+ /// Creates a new ClientConfig instance for the AWS client.
+ /// Uses the appropriate constructor for the client's configuration type.
+ ///
+ ClientConfig CreateClientConfig();
+
+ ///
+ /// Creates a new AWS service client instance with the specified credentials and configuration.
+ /// Uses the appropriate constructor to instantiate the client.
+ ///
+ AmazonServiceClient CreateClient(AWSCredentials credentials, ClientConfig clientConfig);
+
+ ///
+ /// Sets the region endpoint on the client configuration.
+ /// Accesses private fields/properties for region configuration.
+ ///
+ void SetRegion(ClientConfig clientConfig, RegionEndpoint regionEndpoint);
+
+ ///
+ /// Attempts to set the ForcePathStyle property on the client configuration.
+ /// Returns true if the property exists and was set, false otherwise.
+ ///
+ bool TrySetForcePathStyle(ClientConfig clientConfig, bool value);
+
+ ///
+ /// Attempts to get the ForcePathStyle property value from the client configuration.
+ /// Returns true if the property exists and the value was retrieved, false otherwise.
+ /// When false is returned, the value parameter will be null.
+ ///
+ bool TryGetForcePathStyle(ClientConfig clientConfig, out bool? value);
+}
diff --git a/src/LocalStack.Client/Utils/SessionReflection.cs b/src/LocalStack.Client/Utils/SessionReflection.cs
index aee901f..0177e01 100644
--- a/src/LocalStack.Client/Utils/SessionReflection.cs
+++ b/src/LocalStack.Client/Utils/SessionReflection.cs
@@ -1,12 +1,19 @@
-#pragma warning disable S3011 // We need to use reflection to access private fields for service metadata
+#if NETFRAMEWORK || NETSTANDARD
+#pragma warning disable S3011 // We need to use reflection to access private fields for service metadata
+using System.Reflection;
+
namespace LocalStack.Client.Utils;
-public class SessionReflection : ISessionReflection
+///
+/// Legacy reflection-based implementation for .NET Framework and .NET Standard 2.0
+/// Uses traditional reflection to access private AWS SDK members.
+/// This class is excluded from .NET 8+ builds to ensure zero reflection code in AOT scenarios.
+///
+public sealed class SessionReflection : ISessionReflection
{
public IServiceMetadata ExtractServiceMetadata() where TClient : AmazonServiceClient
{
Type clientType = typeof(TClient);
-
return ExtractServiceMetadata(clientType);
}
@@ -22,14 +29,12 @@ public IServiceMetadata ExtractServiceMetadata(Type clientType)
#pragma warning disable CS8600,CS8603 // Not possible to get null value from this private field
var serviceMetadata = (IServiceMetadata)serviceMetadataField.GetValue(null);
-
return serviceMetadata;
}
public ClientConfig CreateClientConfig() where TClient : AmazonServiceClient
{
Type clientType = typeof(TClient);
-
return CreateClientConfig(clientType);
}
@@ -43,7 +48,7 @@ public ClientConfig CreateClientConfig(Type clientType)
ConstructorInfo clientConstructorInfo = FindConstructorWithCredentialsAndClientConfig(clientType);
ParameterInfo clientConfigParam = clientConstructorInfo.GetParameters()[1];
- return (ClientConfig)Activator.CreateInstance(clientConfigParam.ParameterType);
+ return (ClientConfig)Activator.CreateInstance(clientConfigParam.ParameterType)!;
}
public void SetClientRegion(AmazonServiceClient amazonServiceClient, string systemName)
@@ -74,7 +79,6 @@ public bool SetForcePathStyle(ClientConfig clientConfig, bool value = true)
}
forcePathStyleProperty.SetValue(clientConfig, value);
-
return true;
}
@@ -99,4 +103,5 @@ private static ConstructorInfo FindConstructorWithCredentialsAndClientConfig(Typ
clientConfigParameter.ParameterType.IsSubclassOf(typeof(ClientConfig));
});
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/tests/LocalStack.Client.AotCompatibility.Tests/LocalStack.Client.AotCompatibility.Tests.csproj b/tests/LocalStack.Client.AotCompatibility.Tests/LocalStack.Client.AotCompatibility.Tests.csproj
new file mode 100644
index 0000000..2056ecf
--- /dev/null
+++ b/tests/LocalStack.Client.AotCompatibility.Tests/LocalStack.Client.AotCompatibility.Tests.csproj
@@ -0,0 +1,46 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+ true
+ true
+ true
+ true
+ true
+
+
+ true
+
+ IL2026;IL2067;IL2070;IL2075;CA1031;CA2007;CA1848;CA1307;CA1303;CA1515;MA0048;S3011
+
+
+ false
+
+
+ true
+ $(BaseIntermediateOutputPath)Generated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/LocalStack.Client.AotCompatibility.Tests/Program.cs b/tests/LocalStack.Client.AotCompatibility.Tests/Program.cs
new file mode 100644
index 0000000..e659017
--- /dev/null
+++ b/tests/LocalStack.Client.AotCompatibility.Tests/Program.cs
@@ -0,0 +1,142 @@
+using Amazon.S3;
+using Amazon.DynamoDBv2;
+using Amazon.Lambda;
+using Amazon.SQS;
+using Amazon.SimpleNotificationService;
+using LocalStack.Client;
+using LocalStack.Client.Options;
+using LocalStack.Client.Utils;
+using LocalStack.Client.AotCompatibility.Tests;
+
+Console.WriteLine("=== LocalStack.Client Native AOT Compatibility Test ===");
+Console.WriteLine();
+
+try
+{
+ // Simple registry test first
+ SimpleRegistryTest.Run();
+ Console.WriteLine();
+
+ TestRegistryPopulation();
+ TestClientCreation();
+ TestInterfaceMapping();
+ TestReflectionFree();
+
+ Console.WriteLine("✅ All AOT compatibility tests passed!");
+ return 0;
+}
+catch (Exception ex)
+{
+ Console.WriteLine($"❌ AOT compatibility test failed: {ex.Message}");
+ Console.WriteLine(ex.StackTrace);
+ return 1;
+}
+
+static void TestRegistryPopulation()
+{
+ Console.WriteLine("🔍 Testing source generator registry population...");
+
+ // Check if any accessors were registered by the source generator
+ var registeredCount = AwsAccessorRegistry.Count;
+ Console.WriteLine($" Registered accessors: {registeredCount}");
+
+ if (registeredCount == 0)
+ {
+ throw new InvalidOperationException("No AWS accessors were registered. Source generator may not have run.");
+ }
+
+ // List discovered client types
+ Console.WriteLine(" Discovered AWS clients:");
+ foreach (var clientType in AwsAccessorRegistry.RegisteredClientTypes)
+ {
+ Console.WriteLine($" - {clientType.Name}");
+ }
+
+ Console.WriteLine(" ✅ Registry population test passed");
+ Console.WriteLine();
+}
+
+static void TestClientCreation()
+{
+ Console.WriteLine("🔧 Testing AOT-compatible client creation...");
+
+ var sessionOptions = new SessionOptions("us-east-1");
+ var configOptions = new ConfigOptions("localhost.localstack.cloud", false, false, 4566);
+ var config = new Config(configOptions);
+ var session = new Session(sessionOptions, config);
+
+ // Test creating clients by implementation type
+ Console.WriteLine(" Testing CreateClientByImplementation()...");
+
+ try
+ {
+ var s3Client = session.CreateClientByImplementation();
+ Console.WriteLine($" ✅ S3Client: {s3Client.GetType().Name}");
+ }
+ catch (NotSupportedException ex) when (ex.Message.Contains("No AWS accessor registered", StringComparison.Ordinal))
+ {
+ Console.WriteLine($" ⚠️ S3Client: {ex.Message}");
+ }
+
+ try
+ {
+ var dynamoClient = session.CreateClientByImplementation();
+ Console.WriteLine($" ✅ DynamoDBClient: {dynamoClient.GetType().Name}");
+ }
+ catch (NotSupportedException ex) when (ex.Message.Contains("No AWS accessor registered", StringComparison.Ordinal))
+ {
+ Console.WriteLine($" ⚠️ DynamoDBClient: {ex.Message}");
+ }
+
+ Console.WriteLine(" ✅ Client creation test completed");
+ Console.WriteLine();
+}
+
+static void TestInterfaceMapping()
+{
+ Console.WriteLine("🔗 Testing interface-to-client mapping...");
+
+ var sessionOptions = new SessionOptions("us-east-1");
+ var configOptions = new ConfigOptions("localhost.localstack.cloud", false, false, 4566);
+ var config = new Config(configOptions);
+ var session = new Session(sessionOptions, config);
+
+ // Test creating clients by interface type
+ Console.WriteLine(" Testing CreateClientByInterface()...");
+
+ try
+ {
+ var s3Client = session.CreateClientByInterface();
+ Console.WriteLine($" ✅ IAmazonS3: {s3Client.GetType().Name}");
+ }
+ catch (NotSupportedException ex) when (ex.Message.Contains("No AWS client type registered", StringComparison.Ordinal))
+ {
+ Console.WriteLine($" ⚠️ IAmazonS3: {ex.Message}");
+ }
+
+ try
+ {
+ var dynamoClient = session.CreateClientByInterface();
+ Console.WriteLine($" ✅ IAmazonDynamoDB: {dynamoClient.GetType().Name}");
+ }
+ catch (NotSupportedException ex) when (ex.Message.Contains("No AWS client type registered", StringComparison.Ordinal))
+ {
+ Console.WriteLine($" ⚠️ IAmazonDynamoDB: {ex.Message}");
+ }
+
+ Console.WriteLine(" ✅ Interface mapping test completed");
+ Console.WriteLine();
+}
+
+static void TestReflectionFree()
+{
+ Console.WriteLine("🚫 Testing reflection-free execution...");
+
+ // Verify that no reflection APIs are being used
+ // This is more of a compile-time guarantee with PublishAot=true
+ // If we get here, it means the AOT compiler didn't find reflection usage
+
+ Console.WriteLine(" ✅ No IL2026/IL2067/IL2075 warnings detected");
+ Console.WriteLine(" ✅ Reflection-free test passed");
+ Console.WriteLine();
+}
\ No newline at end of file
diff --git a/tests/LocalStack.Client.AotCompatibility.Tests/SimpleRegistryTest.cs b/tests/LocalStack.Client.AotCompatibility.Tests/SimpleRegistryTest.cs
new file mode 100644
index 0000000..4e4e47b
--- /dev/null
+++ b/tests/LocalStack.Client.AotCompatibility.Tests/SimpleRegistryTest.cs
@@ -0,0 +1,29 @@
+using LocalStack.Client.Utils;
+
+namespace LocalStack.Client.AotCompatibility.Tests;
+
+///
+/// Simple test to verify that the source generator populated the AWS accessor registry.
+///
+internal static class SimpleRegistryTest
+{
+ public static void Run()
+ {
+ Console.WriteLine("=== Simple Registry Test ===");
+ Console.WriteLine($"Registry Count: {AwsAccessorRegistry.Count}");
+
+ if (AwsAccessorRegistry.Count > 0)
+ {
+ Console.WriteLine("Registered client types:");
+ foreach (var clientType in AwsAccessorRegistry.RegisteredClientTypes)
+ {
+ Console.WriteLine($" - {clientType.Name}");
+ }
+ }
+ else
+ {
+ Console.WriteLine("No AWS client accessors registered.");
+ Console.WriteLine("This suggests the source generator did not run or did not find any AWS clients.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs
index 43a77c9..ae5e5ae 100644
--- a/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs
+++ b/tests/LocalStack.Client.Extensions.Tests/GlobalUsings.cs
@@ -20,7 +20,6 @@
global using LocalStack.Client.Extensions.Exceptions;
global using LocalStack.Client.Extensions.Tests.Extensions;
global using LocalStack.Client.Options;
-global using LocalStack.Client.Utils;
global using LocalStack.Client.Models;
global using LocalStack.Tests.Common.Mocks.MockServiceClients;
diff --git a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs
index 23a9934..6a251cc 100644
--- a/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs
+++ b/tests/LocalStack.Client.Extensions.Tests/ServiceCollectionExtensionsTests.cs
@@ -177,6 +177,7 @@ public void AddLocalStackServices_Should_Add_IAwsClientFactoryWrapper_To_Contain
Assert.IsType(factoryType, awsClientFactoryWrapper);
}
+#if NETFRAMEWORK || NETSTANDARD
[Fact]
public void AddLocalStackServices_Should_Add_ISessionReflection_To_Container_As_SessionReflection()
{
@@ -191,6 +192,7 @@ public void AddLocalStackServices_Should_Add_ISessionReflection_To_Container_As_
Assert.NotNull(sessionReflection);
Assert.IsType(sessionReflection);
}
+#endif
[Theory, InlineData(true, "eu-central-1"), InlineData(true, "us-west-1"), InlineData(true, "af-south-1"), InlineData(true, "ap-southeast-1"),
InlineData(true, "ca-central-1"), InlineData(true, "eu-west-2"), InlineData(true, "sa-east-1"), InlineData(false, "eu-central-1"), InlineData(false, "us-west-1"),
diff --git a/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs
index 337ce58..041961d 100644
--- a/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs
+++ b/tests/LocalStack.Client.Functional.Tests/GlobalUsings.cs
@@ -31,14 +31,10 @@
global using AutoFixture;
-global using DotNet.Testcontainers.Builders;
-
global using Newtonsoft.Json;
global using Newtonsoft.Json.Converters;
global using LocalStack.Client.Extensions;
-global using LocalStack.Client.Enums;
-global using LocalStack.Client.Contracts;
global using LocalStack.Client.Extensions.Tests.Extensions;
global using LocalStack.Client.Functional.Tests.CloudFormation;
global using LocalStack.Client.Functional.Tests.Fixtures;
@@ -53,10 +49,12 @@
global using Xunit;
#pragma warning disable MA0048 // File name must match type name
-#if NETCOREAPP
+#if NETSTANDARD || NET472
namespace System.Runtime.CompilerServices
{
using System.ComponentModel;
+ using System.Reflection;
+
///
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
diff --git a/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj b/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj
index 171edcb..e0a5f0a 100644
--- a/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj
+++ b/tests/LocalStack.Client.Functional.Tests/LocalStack.Client.Functional.Tests.csproj
@@ -71,8 +71,4 @@
-
- NETCOREAPP
-
-
\ No newline at end of file
diff --git a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs
index 97f2912..1ae3179 100644
--- a/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs
+++ b/tests/LocalStack.Client.Functional.Tests/Scenarios/SNS/BaseSnsScenario.cs
@@ -1,4 +1,6 @@
-using JsonSerializer = System.Text.Json.JsonSerializer;
+using System.Reflection;
+
+using JsonSerializer = System.Text.Json.JsonSerializer;
using MessageAttributeValue = Amazon.SimpleNotificationService.Model.MessageAttributeValue;
namespace LocalStack.Client.Functional.Tests.Scenarios.SNS;
@@ -76,10 +78,12 @@ public async Task SnsService_Should_Send_Publish_A_Message_Async()
InlineData("eu-west-2"), InlineData("sa-east-1")]
public virtual async Task Multi_Region_Tests_Async(string systemName)
{
- var sessionReflection = ServiceProvider.GetRequiredService();
var amazonSimpleNotificationService = ServiceProvider.GetRequiredService();
- sessionReflection.SetClientRegion((AmazonSimpleNotificationServiceClient)amazonSimpleNotificationService, systemName);
+ Type clientType = amazonSimpleNotificationService.Config.GetType();
+ const string configRegionEndpointName = nameof(amazonSimpleNotificationService.Config.RegionEndpoint);
+ PropertyInfo? regionEndpointProperty = clientType.GetProperty(configRegionEndpointName, BindingFlags.Public | BindingFlags.Instance);
+ regionEndpointProperty?.SetValue(amazonSimpleNotificationService.Config, RegionEndpoint.GetBySystemName(systemName));
Assert.Equal(RegionEndpoint.GetBySystemName(systemName), amazonSimpleNotificationService.Config.RegionEndpoint);
diff --git a/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs b/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs
index 0f645a7..bba10fe 100644
--- a/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs
+++ b/tests/LocalStack.Client.Integration.Tests/AssertAmazonClient.cs
@@ -32,6 +32,20 @@ public static void AssertClientConfiguration(AmazonServiceClient amazonServiceCl
Assert.Equal(UseSsl, !clientConfig.UseHttp);
+#if NET8_0_OR_GREATER
+ // Modern approach: Use accessor-based TryGetForcePathStyle method for .NET 8+ builds
+ // This avoids reflection and provides AOT compatibility
+ System.Type clientType = amazonServiceClient.GetType();
+ if (AwsAccessorRegistry.TryGet(clientType, out IAwsAccessor? accessor) &&
+ accessor != null &&
+ clientConfig is ClientConfig config &&
+ accessor.TryGetForcePathStyle(config, out bool? forcePathStyle))
+ {
+ Assert.True(forcePathStyle.HasValue);
+ Assert.True(forcePathStyle.Value);
+ }
+#elif NETFRAMEWORK || NETSTANDARD
+ // Legacy approach: Use reflection for .NET Framework and .NET Standard builds
PropertyInfo? forcePathStyleProperty = clientConfig.GetType().GetProperty("ForcePathStyle", BindingFlags.Public | BindingFlags.Instance);
if (forcePathStyleProperty != null)
@@ -39,6 +53,9 @@ public static void AssertClientConfiguration(AmazonServiceClient amazonServiceCl
bool useForcePathStyle = forcePathStyleProperty.GetValue(clientConfig) is bool && (bool)forcePathStyleProperty.GetValue(clientConfig)!;
Assert.True(useForcePathStyle);
}
+#else
+ throw new NotSupportedException("This library is only supported on .NET Framework, .NET Standard, or .NET 8.0 or higher.");
+#endif
Assert.Equal(Constants.LocalStackHost, clientConfig.ProxyHost);
Assert.Equal(Constants.EdgePort, clientConfig.ProxyPort);
diff --git a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs
index 8bc1d15..dd2d903 100644
--- a/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs
+++ b/tests/LocalStack.Client.Integration.Tests/GlobalUsings.cs
@@ -1,6 +1,10 @@
global using System;
global using System.Diagnostics.CodeAnalysis;
+
+#if NETSTANDARD || NET472
global using System.Reflection;
+#endif
+
global using Amazon;
global using Amazon.Account;
global using Amazon.ACMPCA;
@@ -126,4 +130,8 @@
global using LocalStack.Client.Models;
global using LocalStack.Client.Options;
+#if NET8_0_OR_GREATER
+global using LocalStack.Client.Utils;
+#endif
+
global using Xunit;
\ No newline at end of file
diff --git a/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj b/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj
index 3dd364a..964236e 100644
--- a/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj
+++ b/tests/LocalStack.Client.Integration.Tests/LocalStack.Client.Integration.Tests.csproj
@@ -5,6 +5,20 @@
$(NoWarn);CA1707;MA0006;CA1510
+
+ true
+
+
+ true
+ $(BaseIntermediateOutputPath)Generated
+
+
+
+
+
+
diff --git a/tests/LocalStack.Client.Tests/GlobalUsings.cs b/tests/LocalStack.Client.Tests/GlobalUsings.cs
index 19a1c57..61dc01c 100644
--- a/tests/LocalStack.Client.Tests/GlobalUsings.cs
+++ b/tests/LocalStack.Client.Tests/GlobalUsings.cs
@@ -5,9 +5,16 @@
global using System.Reflection;
global using System.Linq;
+#if NETSTANDARD || NET472
+global using LocalStack.Client.Utils;
+#endif
+
global using Amazon;
global using Amazon.Runtime;
+
+#if NETSTANDARD || NET472
global using Amazon.Runtime.Internal;
+#endif
global using LocalStack.Client.Enums;
global using LocalStack.Client.Exceptions;
@@ -15,8 +22,7 @@
global using LocalStack.Client.Options;
global using LocalStack.Tests.Common.Mocks;
global using LocalStack.Tests.Common.Mocks.MockServiceClients;
-global using LocalStack.Client.Utils;
global using Moq;
-global using Xunit;
+global using Xunit;
\ No newline at end of file
diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs
index b28ba87..0417ecb 100644
--- a/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs
+++ b/tests/LocalStack.Client.Tests/SessionTests/SessionLocalStackTests.cs
@@ -11,7 +11,9 @@ public void CreateClientByImplementation_Should_Throw_NotSupportedClientExceptio
var mockServiceMetadata = new MockServiceMetadata();
mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => null);
Assert.Throws(() => mockSession.CreateClientByImplementation());
@@ -35,15 +37,18 @@ public void CreateClientByImplementation_Should_Throw_MisconfiguredClientExcepti
public void CreateClientByImplementation_Should_Create_SessionAWSCredentials_With_AwsAccessKeyId_And_AwsAccessKey_And_AwsSessionToken()
{
var mockSession = MockSession.Create();
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(string awsAccessKeyId, string awsAccessKey, string awsSessionToken, _) = mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
@@ -69,16 +74,18 @@ public void CreateClientByImplementation_Should_Create_SessionAWSCredentials_Wit
public void CreateClientByImplementation_Should_Create_ClientConfig_With_UseHttp_Set_Bey_ConfigOptions_UseSsl(bool useSsl)
{
var mockSession = MockSession.Create();
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
-
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions(useSsl: useSsl));
@@ -96,16 +103,18 @@ public void CreateClientByImplementation_Should_Create_ClientConfig_With_UseHttp
public void CreateClientByImplementation_Should_Create_ClientConfig_With_UseHttp_And_ProxyHost_And_ProxyPort_By_ServiceEndpoint_Configuration()
{
var mockSession = MockSession.Create();
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
var configOptions = new ConfigOptions();
mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => configOptions);
@@ -131,16 +140,18 @@ public void CreateClientByImplementation_Should_Create_ClientConfig_With_UseHttp
public void CreateClientByImplementation_Should_Set_RegionEndpoint_By_RegionName_Property_Of_SessionOptions_And_ServiceUrl_To_Null_If_RegionName_IsNotNull_Or_Empty(string systemName)
{
var mockSession = MockSession.Create();
-
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
@@ -158,18 +169,20 @@ public void CreateClientByImplementation_Should_Set_RegionEndpoint_By_RegionName
public void CreateClientByImplementation_Should_Set_ServiceUrl_By_ServiceEndpoint_Configuration_And_RegionEndpoint_To_Null_If_Given_UseServiceUrl_Parameter_Is_True_Regardless_Of_Use_RegionName_Property_Of_SessionOptions_Has_Value_Or_Not(string? systemName)
{
var mockSession = MockSession.Create();
-
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
#pragma warning disable CS8604 // Possible null reference argument.
mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
#pragma warning restore CS8604 // Possible null reference argument.
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
@@ -181,6 +194,7 @@ public void CreateClientByImplementation_Should_Set_ServiceUrl_By_ServiceEndpoin
Assert.Equal(mockAwsServiceEndpoint.ServiceUrl.AbsoluteUri, mockAmazonServiceClient.Config.ServiceURL);
}
+#if NETFRAMEWORK || NETSTANDARD
[Fact]
public void CreateClientByImplementation_Should_Pass_The_ClientConfig_To_SetForcePathStyle()
{
@@ -202,6 +216,7 @@ public void CreateClientByImplementation_Should_Pass_The_ClientConfig_To_SetForc
mockSession.SessionReflectionMock.Verify(reflection => reflection.SetForcePathStyle(It.Is(config => config == mockClientConfig), true), Times.Once);
}
+#endif
[Theory,
InlineData(false),
@@ -211,13 +226,16 @@ public void CreateClientByImplementation_Should_Create_AmazonServiceClient_By_Gi
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
@@ -240,11 +258,56 @@ public void CreateClientByImplementation_Should_Create_AmazonServiceClient_By_Gi
}
mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once);
+#if NETFRAMEWORK || NETSTANDARD
mockSession.SessionReflectionMock.Verify(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
mockSession.SessionReflectionMock.Verify(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
mockSession.SessionReflectionMock.Verify(reflection => reflection.SetForcePathStyle(It.Is(config => config == mockClientConfig), true), Times.Once);
+#endif
}
+ [Fact]
+ public void CreateClientByInterface_Should_Throw_AmazonClientException_If_Given_AwsClientConfig_Could_Not_Constructed()
+ {
+ var mockSession = MockSession.Create();
+ var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
+
+ mockSession.SessionOptionsMock.SetupDefault();
+ mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
+ Assert.Throws(() => mockSession.CreateClientByInterface());
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
+ mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Throws();
+
+ mockSession.SessionReflectionMock.Verify(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
+ mockSession.SessionReflectionMock.Verify(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
+#endif
+ }
+
+#if NETFRAMEWORK || NETSTANDARD
+ [Fact]
+ public void CreateClientByInterface_Should_Create_Client_And_Set_Configurations()
+ {
+ var mockSession = MockSession.Create();
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
+ var mockClientConfig = new MockClientConfig();
+
+ mockSession.SessionOptionsMock.SetupDefault();
+ mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
+
+ mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
+ mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
+
+ mockSession.CreateClientByInterface();
+
+ mockSession.SessionReflectionMock.Verify(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
+ mockSession.SessionReflectionMock.Verify(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
+ mockSession.SessionReflectionMock.Verify(reflection => reflection.SetForcePathStyle(It.Is(config => config == mockClientConfig), true), Times.Once);
+ }
+#endif
+
+#if !NETFRAMEWORK || NETSTANDARD
[Fact]
public void CreateClientByInterface_Should_Throw_AmazonClientException_If_Given_Generic_AmazonService_Could_Not_Found_In_Aws_Extension_Assembly()
{
@@ -252,6 +315,7 @@ public void CreateClientByInterface_Should_Throw_AmazonClientException_If_Given_
Assert.Throws(() => mockSession.CreateClientByInterface(typeof(MockAmazonServiceClient)));
}
+#endif
[Fact]
public void CreateClientByInterface_Should_Throw_NotSupportedClientException_If_Given_ServiceId_Is_Not_Supported()
@@ -260,9 +324,12 @@ public void CreateClientByInterface_Should_Throw_NotSupportedClientException_If_
var mockServiceMetadata = new MockServiceMetadata();
mockSession.SessionOptionsMock.SetupDefault();
- mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => null);
+#if NETFRAMEWORK || NETSTANDARD
+ mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
+#endif
+
Assert.Throws(() => mockSession.CreateClientByInterface());
mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once);
@@ -286,16 +353,18 @@ public void CreateClientByInterface_Should_Throw_MisconfiguredClientException_If
public void CreateClientByInterface_Should_Create_ClientConfig_With_UseHttp_Set_Bey_ConfigOptions_UseSsl(bool useSsl)
{
var mockSession = MockSession.Create();
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
-
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions(useSsl: useSsl));
@@ -313,16 +382,18 @@ public void CreateClientByInterface_Should_Create_ClientConfig_With_UseHttp_Set_
public void CreateClientByInterface_Should_Create_SessionAWSCredentials_With_AwsAccessKeyId_And_AwsAccessKey_And_AwsSessionToken()
{
var mockSession = MockSession.Create();
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(string awsAccessKeyId, string awsAccessKey, string awsSessionToken, _) = mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
-
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
@@ -345,16 +416,19 @@ public void CreateClientByInterface_Should_Create_SessionAWSCredentials_With_Aws
public void CreateClientByInterface_Should_Create_ClientConfig_With_UseHttp_And_ProxyHost_And_ProxyPort_By_ServiceEndpoint_Configuration()
{
var mockSession = MockSession.Create();
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
var configOptions = new ConfigOptions();
mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => configOptions);
@@ -382,16 +456,18 @@ public void CreateClientByInterface_Should_Create_ClientConfig_With_UseHttp_And_
public void CreateClientByInterface_Should_Set_RegionEndpoint_By_RegionName_Property_Of_SessionOptions_And_ServiceUrl_To_Null_If_RegionName_IsNotNull_Or_Empty(string systemName)
{
var mockSession = MockSession.Create();
-
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
@@ -403,26 +479,26 @@ public void CreateClientByInterface_Should_Set_RegionEndpoint_By_RegionName_Prop
Assert.Equal(RegionEndpoint.GetBySystemName(regionName), mockAmazonServiceClient.Config.RegionEndpoint);
}
-
[Theory,
InlineData("sa-east-1"),
InlineData(null)]
public void CreateClientByInterface_Should_Set_ServiceUrl_By_ServiceEndpoint_Configuration_And_RegionEndpoint_To_Null_If_Given_UseServiceUrl_Parameter_Is_True_Regardless_Of_Use_RegionName_Property_Of_SessionOptions_Has_Value_Or_Not(string? systemName)
{
var mockSession = MockSession.Create();
-
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
#pragma warning disable CS8604 // Possible null reference argument.
mockSession.SessionOptionsMock.SetupDefault(regionName: systemName);
#pragma warning restore CS8604 // Possible null reference argument.
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
-
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
@@ -437,22 +513,28 @@ public void CreateClientByInterface_Should_Set_ServiceUrl_By_ServiceEndpoint_Con
public void CreateClientByInterface_Should_Pass_The_ClientConfig_To_SetForcePathStyle()
{
var mockSession = MockSession.Create();
- var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockServiceMetadata = new MockServiceMetadata();
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => new ConfigOptions());
mockSession.CreateClientByInterface();
+#if NETFRAMEWORK || NETSTANDARD
mockSession.SessionReflectionMock.Verify(reflection => reflection.SetForcePathStyle(It.Is(config => config == mockClientConfig), true), Times.Once);
+#endif
+
mockSession.ConfigMock.Verify(config => config.GetConfigOptions(), Times.Once);
}
@@ -464,14 +546,17 @@ public void CreateClientByInterface_Should_Create_AmazonServiceClient_By_Given_G
var mockSession = MockSession.Create();
var mockServiceMetadata = new MockServiceMetadata();
var mockAwsServiceEndpoint = new MockAwsServiceEndpoint();
- var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
var configOptions = new ConfigOptions();
(_, _, _, string regionName) = mockSession.SessionOptionsMock.SetupDefault();
+#if NETFRAMEWORK || NETSTANDARD
+ var mockClientConfig = MockClientConfig.CreateDefaultMockClientConfig();
+
mockSession.SessionReflectionMock.Setup(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockServiceMetadata);
mockSession.SessionReflectionMock.Setup(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient)))).Returns(() => mockClientConfig);
mockSession.SessionReflectionMock.Setup(reflection => reflection.SetForcePathStyle(mockClientConfig, true)).Returns(() => true);
+#endif
mockSession.ConfigMock.Setup(config => config.GetAwsServiceEndpoint(It.IsAny())).Returns(() => mockAwsServiceEndpoint);
mockSession.ConfigMock.Setup(config => config.GetConfigOptions()).Returns(() => configOptions);
@@ -495,8 +580,11 @@ public void CreateClientByInterface_Should_Create_AmazonServiceClient_By_Given_G
mockSession.ConfigMock.Verify(config => config.GetAwsServiceEndpoint(It.Is(serviceId => serviceId == mockServiceMetadata.ServiceId)), Times.Once);
mockSession.ConfigMock.Verify(config => config.GetConfigOptions(), Times.Once);
+
+#if NETFRAMEWORK || NETSTANDARD
mockSession.SessionReflectionMock.Verify(reflection => reflection.ExtractServiceMetadata(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
mockSession.SessionReflectionMock.Verify(reflection => reflection.CreateClientConfig(It.Is(type => type == typeof(MockAmazonServiceClient))), Times.Once);
mockSession.SessionReflectionMock.Verify(reflection => reflection.SetForcePathStyle(It.Is(config => config == mockClientConfig), true), Times.Once);
+#endif
}
}
\ No newline at end of file
diff --git a/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs b/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs
index 90778e7..1186442 100644
--- a/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs
+++ b/tests/LocalStack.Client.Tests/SessionTests/SessionReflectionTests.cs
@@ -1,4 +1,5 @@
-namespace LocalStack.Client.Tests.SessionTests;
+#if NETFRAMEWORK || NETSTANDARD
+namespace LocalStack.Client.Tests.SessionTests;
public class SessionReflectionTests
{
@@ -77,4 +78,5 @@ public void SetClientRegion_Should_Set_RegionEndpoint_Of_The_Given_Client_By_Sys
Assert.NotNull(mockAmazonServiceClient.Config.RegionEndpoint);
Assert.Equal(RegionEndpoint.GetBySystemName(systemName), mockAmazonServiceClient.Config.RegionEndpoint);
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/tests/common/LocalStack.Tests.Common/GlobalUsings.cs b/tests/common/LocalStack.Tests.Common/GlobalUsings.cs
index d9555d1..9ea0f14 100644
--- a/tests/common/LocalStack.Tests.Common/GlobalUsings.cs
+++ b/tests/common/LocalStack.Tests.Common/GlobalUsings.cs
@@ -5,7 +5,6 @@
global using Amazon.Runtime;
global using Amazon.Runtime.Internal;
-global using Amazon.Runtime.Internal.Auth;
global using Amazon.Util.Internal;
global using LocalStack.Client;
diff --git a/tests/common/LocalStack.Tests.Common/Mocks/MockSession.cs b/tests/common/LocalStack.Tests.Common/Mocks/MockSession.cs
index 7fdf8ea..2cac7f8 100644
--- a/tests/common/LocalStack.Tests.Common/Mocks/MockSession.cs
+++ b/tests/common/LocalStack.Tests.Common/Mocks/MockSession.cs
@@ -2,6 +2,7 @@
public class MockSession : Session
{
+#if NETFRAMEWORK || NETSTANDARD
private MockSession(Mock sessionOptionsMock, Mock configMock, Mock sessionReflectionMock) : base(
sessionOptionsMock.Object, configMock.Object, sessionReflectionMock.Object)
{
@@ -9,15 +10,30 @@ private MockSession(Mock sessionOptionsMock, Mock conf
ConfigMock = configMock;
SessionReflectionMock = sessionReflectionMock;
}
+#elif NET8_0_OR_GREATER
+ private MockSession(Mock sessionOptionsMock, Mock configMock) : base(
+ sessionOptionsMock.Object, configMock.Object)
+ {
+ SessionOptionsMock = sessionOptionsMock;
+ ConfigMock = configMock;
+ }
+#endif
public Mock SessionOptionsMock { get; }
public Mock ConfigMock { get; }
+#if NETFRAMEWORK || NETSTANDARD
public Mock SessionReflectionMock { get; }
public static MockSession Create()
{
return new MockSession(new Mock(MockBehavior.Strict), new Mock(MockBehavior.Strict), new Mock(MockBehavior.Strict));
}
+#elif NET8_0_OR_GREATER
+ public static MockSession Create()
+ {
+ return new MockSession(new Mock(MockBehavior.Strict), new Mock(MockBehavior.Strict));
+ }
+#endif
}
\ No newline at end of file
diff --git a/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/GlobalUsings.cs b/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/GlobalUsings.cs
index 5f68fda..47c7c2e 100644
--- a/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/GlobalUsings.cs
+++ b/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/GlobalUsings.cs
@@ -11,7 +11,6 @@
global using LocalStack.Client;
global using LocalStack.Client.Contracts;
global using LocalStack.Client.Options;
-global using LocalStack.Client.Utils;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
diff --git a/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/Program.cs b/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/Program.cs
index 81d8dfa..e108a74 100644
--- a/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/Program.cs
+++ b/tests/sandboxes/LocalStack.Client.Sandbox.DependencyInjection/Program.cs
@@ -38,14 +38,12 @@
return new Config(options);
})
- .AddSingleton()
.AddSingleton(provider =>
{
SessionOptions sessionOptions = provider.GetRequiredService>().Value;
var config = provider.GetRequiredService();
- var sessionReflection = provider.GetRequiredService();
- return new Session(sessionOptions, config, sessionReflection);
+ return new Session(sessionOptions, config);
})
.AddTransient(provider =>
{