diff --git a/Swashbuckle.AspNetCore.slnx b/Swashbuckle.AspNetCore.slnx index a6e0f06356..5bfe87b48d 100644 --- a/Swashbuckle.AspNetCore.slnx +++ b/Swashbuckle.AspNetCore.slnx @@ -58,6 +58,7 @@ + diff --git a/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs b/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs index 0e874c0178..89dbb3c403 100644 --- a/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs +++ b/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs @@ -4,7 +4,7 @@ internal class CommandRunner(string commandName, string commandDescription, Text { private readonly Dictionary _argumentDescriptors = []; private readonly Dictionary _optionDescriptors = []; - private Func, int> _runFunc = (_) => 1; + private Func, Task> _runFunc = (_) => Task.FromResult(1); private readonly List _subRunners = []; private readonly TextWriter _output = output; @@ -27,7 +27,7 @@ public void Option(string name, string description, bool isFlag = false) _optionDescriptors.Add(name, new OptionDescriptor { Description = description, IsFlag = isFlag }); } - public void OnRun(Func, int> runFunc) + public void OnRun(Func, Task> runFunc) { _runFunc = runFunc; } @@ -39,12 +39,12 @@ public void SubCommand(string name, string description, Action co _subRunners.Add(runner); } - public int Run(IEnumerable args) + public async Task RunAsync(IEnumerable args) { if (args.Any()) { var subRunner = _subRunners.FirstOrDefault(r => r.CommandName.Split(' ').Last() == args.First()); - if (subRunner != null) return subRunner.Run(args.Skip(1)); + if (subRunner != null) return await subRunner.RunAsync(args.Skip(1)); } if (_subRunners.Count != 0 || !TryParseArgs(args, out IDictionary namedArgs)) @@ -53,7 +53,7 @@ public int Run(IEnumerable args) return 1; } - return _runFunc(namedArgs); + return await _runFunc(namedArgs); } private bool TryParseArgs(IEnumerable args, out IDictionary namedArgs) diff --git a/src/Swashbuckle.AspNetCore.Cli/Program.cs b/src/Swashbuckle.AspNetCore.Cli/Program.cs index 14d65ac17a..449526146b 100644 --- a/src/Swashbuckle.AspNetCore.Cli/Program.cs +++ b/src/Swashbuckle.AspNetCore.Cli/Program.cs @@ -16,7 +16,7 @@ internal class Program { private const string OpenApiVersionOption = "--openapiversion"; - public static int Main(string[] args) + public static async Task Main(string[] args) { // Helper to simplify command line parsing etc. var runner = new CommandRunner("dotnet swagger", "Swashbuckle (Swagger) Command Line Tools", Console.Out); @@ -45,7 +45,7 @@ public static int Main(string[] args) using var child = Process.Start("dotnet", subProcessCommandLine); child.WaitForExit(); - return child.ExitCode; + return Task.FromResult(child.ExitCode); }); }); @@ -60,14 +60,16 @@ public static int Main(string[] args) c.Option(OpenApiVersionOption, ""); c.Option("--yaml", "", true); - c.OnRun((namedArgs) => + c.OnRun(async (namedArgs) => { - SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var swaggerProvider, out var swaggerOptions); + SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var asyncSwaggerProvider, out var swaggerProvider, out var swaggerOptions); var swaggerDocumentSerializer = swaggerOptions?.Value?.CustomDocumentSerializer; - var swagger = swaggerProvider.GetSwagger( - namedArgs["swaggerdoc"], - namedArgs.TryGetValue("--host", out var arg) ? arg : null, - namedArgs.TryGetValue("--basepath", out var namedArg) ? namedArg : null); + + var host = namedArgs.TryGetValue("--host", out var arg) ? arg : null; + var basePath = namedArgs.TryGetValue("--basepath", out var namedArg) ? namedArg : null; + var swagger = asyncSwaggerProvider != null + ? await asyncSwaggerProvider.GetSwaggerAsync(namedArgs["swaggerdoc"], host, basePath) + : swaggerProvider.GetSwagger(namedArgs["swaggerdoc"], host, basePath); // 4) Serialize to specified output location or stdout var outputPath = namedArgs.TryGetValue("--output", out var arg1) @@ -139,7 +141,7 @@ public static int Main(string[] args) using var child = Process.Start("dotnet", subProcessCommandLine); child.WaitForExit(); - return child.ExitCode; + return Task.FromResult(child.ExitCode); }); }); @@ -150,7 +152,7 @@ public static int Main(string[] args) c.Option("--output", ""); c.OnRun((namedArgs) => { - SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var swaggerProvider, out var swaggerOptions); + SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out _, out var swaggerProvider, out var swaggerOptions); IList docNames = []; string outputPath = namedArgs.TryGetValue("--output", out var arg1) @@ -172,7 +174,7 @@ public static int Main(string[] args) if (swaggerProvider is not ISwaggerDocumentMetadataProvider docMetaProvider) { writer.WriteLine($"The registered {nameof(ISwaggerProvider)} instance does not implement {nameof(ISwaggerDocumentMetadataProvider)}; unable to list the Swagger document names."); - return -1; + return Task.FromResult(-1); } docNames = docMetaProvider.GetDocumentNames(); @@ -182,14 +184,14 @@ public static int Main(string[] args) writer.WriteLine($"\"{name}\""); } - return 0; + return Task.FromResult(0); }); }); - return runner.Run(args); + return await runner.RunAsync(args); } - private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary namedArgs, out ISwaggerProvider swaggerProvider, out IOptions swaggerOptions) + private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary namedArgs, out IAsyncSwaggerProvider asyncSwaggerProvider, out ISwaggerProvider swaggerProvider, out IOptions swaggerOptions) { // 1) Configure host with provided startupassembly var startupAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath( @@ -199,6 +201,7 @@ private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary(); swaggerProvider = serviceProvider.GetRequiredService(); swaggerOptions = serviceProvider.GetService>(); } diff --git a/test/Swashbuckle.AspNetCore.Cli.Test/CommandRunnerTests.cs b/test/Swashbuckle.AspNetCore.Cli.Test/CommandRunnerTests.cs index 774223f9b4..feaf29ec0d 100644 --- a/test/Swashbuckle.AspNetCore.Cli.Test/CommandRunnerTests.cs +++ b/test/Swashbuckle.AspNetCore.Cli.Test/CommandRunnerTests.cs @@ -5,7 +5,7 @@ namespace Swashbuckle.AspNetCore.Cli.Test; public static class CommandRunnerTests { [Fact] - public static void Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredMetadata() + public static async Task Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredMetadata() { var receivedValues = new List(); var subject = new CommandRunner("test", "a test", new StringWriter()); @@ -18,7 +18,7 @@ public static void Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredM receivedValues.Add(namedArgs["--opt1"]); receivedValues.Add(namedArgs["--opt2"]); receivedValues.Add(namedArgs["arg1"]); - return 2; + return Task.FromResult(2); }); }); subject.SubCommand("cmd2", "", c => { @@ -30,12 +30,12 @@ public static void Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredM receivedValues.Add(namedArgs["--opt1"]); receivedValues.Add(namedArgs["--opt2"]); receivedValues.Add(namedArgs["arg1"]); - return 3; + return Task.FromResult(3); }); }); - var cmd1ExitCode = subject.Run(["cmd1", "--opt1", "foo", "--opt2", "bar"]); - var cmd2ExitCode = subject.Run(["cmd2", "--opt1", "blah", "--opt2", "dblah"]); + var cmd1ExitCode = await subject.RunAsync(["cmd1", "--opt1", "foo", "--opt2", "bar"]); + var cmd2ExitCode = await subject.RunAsync(["cmd2", "--opt1", "blah", "--opt2", "dblah"]); Assert.Equal(2, cmd1ExitCode); Assert.Equal(3, cmd2ExitCode); @@ -43,14 +43,14 @@ public static void Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredM } [Fact] - public static void Run_PrintsAvailableCommands_WhenUnexpectedCommandIsProvided() + public static async Task Run_PrintsAvailableCommands_WhenUnexpectedCommandIsProvided() { var output = new StringWriter(); var subject = new CommandRunner("test", "a test", output); subject.SubCommand("cmd", "does something", c => { }); - var exitCode = subject.Run(["foo"]); + var exitCode = await subject.RunAsync(["foo"]); Assert.StartsWith("a test", output.ToString()); Assert.Contains("Commands:", output.ToString()); @@ -58,14 +58,14 @@ public static void Run_PrintsAvailableCommands_WhenUnexpectedCommandIsProvided() } [Fact] - public static void Run_PrintsAvailableCommands_WhenHelpOptionIsProvided() + public static async Task Run_PrintsAvailableCommands_WhenHelpOptionIsProvided() { var output = new StringWriter(); var subject = new CommandRunner("test", "a test", output); subject.SubCommand("cmd", "does something", c => { }); - var exitCode = subject.Run(["--help"]); + var exitCode = await subject.RunAsync(["--help"]); Assert.StartsWith("a test", output.ToString()); Assert.Contains("Commands:", output.ToString()); @@ -81,7 +81,7 @@ public static void Run_PrintsAvailableCommands_WhenHelpOptionIsProvided() [InlineData(new string[0], new[] { "arg1" }, new[] { "cmd", "--opt1" }, true)] [InlineData(new string[0], new[] { "arg1" }, new[] { "cmd", "foo", "bar" }, true)] [InlineData(new string[0], new[] { "arg1" }, new[] { "cmd", "foo" }, false)] - public static void Run_PrintsCommandUsage_WhenUnexpectedArgumentsAreProvided( + public static async Task Run_PrintsCommandUsage_WhenUnexpectedArgumentsAreProvided( string[] optionNames, string[] argNames, string[] providedArgs, @@ -97,7 +97,7 @@ public static void Run_PrintsCommandUsage_WhenUnexpectedArgumentsAreProvided( c.Argument(name, ""); }); - subject.Run(providedArgs); + await subject.RunAsync(providedArgs); if (shouldPrintUsage) Assert.StartsWith("Usage: test cmd", output.ToString()); diff --git a/test/Swashbuckle.AspNetCore.Cli.Test/Swashbuckle.AspNetCore.Cli.Test.csproj b/test/Swashbuckle.AspNetCore.Cli.Test/Swashbuckle.AspNetCore.Cli.Test.csproj index dc0d77941a..285c4179dd 100644 --- a/test/Swashbuckle.AspNetCore.Cli.Test/Swashbuckle.AspNetCore.Cli.Test.csproj +++ b/test/Swashbuckle.AspNetCore.Cli.Test/Swashbuckle.AspNetCore.Cli.Test.csproj @@ -7,6 +7,7 @@ + diff --git a/test/Swashbuckle.AspNetCore.Cli.Test/ToolTests.cs b/test/Swashbuckle.AspNetCore.Cli.Test/ToolTests.cs index facefaddc3..9c47543f54 100644 --- a/test/Swashbuckle.AspNetCore.Cli.Test/ToolTests.cs +++ b/test/Swashbuckle.AspNetCore.Cli.Test/ToolTests.cs @@ -8,9 +8,9 @@ namespace Swashbuckle.AspNetCore.Cli.Test; public static class ToolTests { [Fact] - public static void Can_Output_Swagger_Document_Names() + public static async Task Can_Output_Swagger_Document_Names() { - var result = RunToStringCommand((outputPath) => + var result = await RunToStringCommandAsync((outputPath) => [ "list", "--output", @@ -22,17 +22,17 @@ public static void Can_Output_Swagger_Document_Names() } [Fact] - public static void Throws_When_Startup_Assembly_Does_Not_Exist() + public static async Task Throws_When_Startup_Assembly_Does_Not_Exist() { string[] args = ["tofile", "--output", "swagger.json", "--openapiversion", "2.0", "./does_not_exist.dll", "v1"]; - Assert.Throws(() => Program.Main(args)); + await Assert.ThrowsAsync(() => Program.Main(args)); } [Theory] [InlineData("a")] [InlineData("1.9")] [InlineData("3.2")] - public static void Error_When_OpenApiVersion_Is_Not_Supported(string version) + public static async Task Error_When_OpenApiVersion_Is_Not_Supported(string version) { string[] args = [ @@ -44,13 +44,13 @@ public static void Error_When_OpenApiVersion_Is_Not_Supported(string version) Path.Combine(Directory.GetCurrentDirectory(), "Basic.dll"), "v1" ]; - Assert.NotEqual(0, Program.Main(args)); + Assert.NotEqual(0, await Program.Main(args)); } [Fact] - public static void Can_Generate_Swagger_Json_v2_OpenApiVersion() + public static async Task Can_Generate_Swagger_Json_v2_OpenApiVersion() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -70,9 +70,9 @@ public static void Can_Generate_Swagger_Json_v2_OpenApiVersion() } [Fact] - public static void Can_Generate_Swagger_Json_v3() + public static async Task Can_Generate_Swagger_Json_v3() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -90,9 +90,9 @@ public static void Can_Generate_Swagger_Json_v3() } [Fact] - public static void Can_Generate_Swagger_Json_v3_1() + public static async Task Can_Generate_Swagger_Json_v3_1() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -112,9 +112,9 @@ public static void Can_Generate_Swagger_Json_v3_1() } [Fact] - public static void Can_Generate_Swagger_Json_v3_OpenApiVersion() + public static async Task Can_Generate_Swagger_Json_v3_OpenApiVersion() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -134,9 +134,35 @@ public static void Can_Generate_Swagger_Json_v3_OpenApiVersion() } [Fact] - public static void Overwrites_Existing_File() + public static async Task Can_Generate_Swagger_Json_With_Async_Operation_Filters() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => + [ + "tofile", + "--output", + outputPath, + Path.Combine(Directory.GetCurrentDirectory(), "Authorization.dll"), + "v1" + ]); + + var paths = document.RootElement.GetProperty("paths"); + var resourcesPath = paths.GetProperty("/resources/{id}"); + var getOp = resourcesPath.GetProperty("get"); + + // verify that the async operation filter resolved authorization policies and added security requirements + Assert.True(getOp.TryGetProperty("security", out var security)); + Assert.True(security.GetArrayLength() > 0); + + // verify that 401/403 responses were added by the async filter + var responses = getOp.GetProperty("responses"); + Assert.True(responses.TryGetProperty("401", out _)); + Assert.True(responses.TryGetProperty("403", out _)); + } + + [Fact] + public static async Task Overwrites_Existing_File() + { + using var document = await RunToJsonCommandAsync((outputPath) => { File.WriteAllText(outputPath, new string('x', 100_000)); @@ -159,9 +185,9 @@ public static void Overwrites_Existing_File() } [Fact] - public static void CustomDocumentSerializer_Writes_Custom_V2_Document() + public static async Task CustomDocumentSerializer_Writes_Custom_V2_Document() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -178,9 +204,9 @@ public static void CustomDocumentSerializer_Writes_Custom_V2_Document() } [Fact] - public static void CustomDocumentSerializer_Writes_Custom_V3_Document() + public static async Task CustomDocumentSerializer_Writes_Custom_V3_Document() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -196,9 +222,9 @@ public static void CustomDocumentSerializer_Writes_Custom_V3_Document() } [Fact] - public static void CustomDocumentSerializer_Writes_Custom_V3_1_Document() + public static async Task CustomDocumentSerializer_Writes_Custom_V3_1_Document() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -216,9 +242,9 @@ public static void CustomDocumentSerializer_Writes_Custom_V3_1_Document() } [Fact] - public static void Can_Generate_Swagger_Json_ForTopLevelApp() + public static async Task Can_Generate_Swagger_Json_ForTopLevelApp() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -236,9 +262,9 @@ public static void Can_Generate_Swagger_Json_ForTopLevelApp() } [Fact] - public static void Does_Not_Run_Crashing_HostedService() + public static async Task Does_Not_Run_Crashing_HostedService() { - using var document = RunToJsonCommand((outputPath) => + using var document = await RunToJsonCommandAsync((outputPath) => [ "tofile", "--output", @@ -254,9 +280,9 @@ public static void Does_Not_Run_Crashing_HostedService() } [Fact] - public static void Creates_New_Folder_Path() + public static async Task Creates_New_Folder_Path() { - using var document = RunToJsonCommand(outputPath => + using var document = await RunToJsonCommandAsync(outputPath => [ "tofile", "--output", @@ -273,7 +299,7 @@ public static void Creates_New_Folder_Path() Assert.True(productsPath.TryGetProperty("post", out _)); } - private static string RunToStringCommand(Func setup, string subOutputPath = default) + private static async Task RunToStringCommandAsync(Func setup, string subOutputPath = default) { using var temporaryDirectory = new TemporaryDirectory(); @@ -283,14 +309,14 @@ private static string RunToStringCommand(Func setup, string su string[] args = setup(outputPath); - Assert.Equal(0, Program.Main(args)); + Assert.Equal(0, await Program.Main(args)); return File.ReadAllText(outputPath); } - private static JsonDocument RunToJsonCommand(Func setup, string subOutputPath = default) + private static async Task RunToJsonCommandAsync(Func setup, string subOutputPath = default) { - string json = RunToStringCommand(setup, subOutputPath); + string json = await RunToStringCommandAsync(setup, subOutputPath); return JsonDocument.Parse(json); } diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerIntegrationTests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerIntegrationTests.cs index 5d01813839..c1575667f0 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerIntegrationTests.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerIntegrationTests.cs @@ -137,6 +137,7 @@ public async Task SwaggerMiddleware_CanBeConfiguredMultipleTimes( [InlineData(typeof(TopLevelSwaggerDoc.Program), "/swagger/v1.json")] [InlineData(typeof(WebApi.Program), "/swagger/v1/swagger.json")] [InlineData(typeof(WebApi.Aot.Program), "/swagger/v1/swagger.json")] + [InlineData(typeof(Authorization.Program), "/swagger/v1/swagger.json")] public async Task SwaggerEndpoint_ReturnsValidSwaggerJson_Without_Startup( Type entryPointType, string swaggerRequestUri) diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/VerifyTests.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/VerifyTests.cs index 862b9ac97a..b2d1042e8e 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/VerifyTests.cs +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/VerifyTests.cs @@ -66,6 +66,7 @@ await Verify(swagger) [InlineData(typeof(WebApi.Program), "/swagger/v1/swagger.json")] [InlineData(typeof(WebApi.Aot.Program), "/swagger/v1/swagger.json")] [InlineData(typeof(WebApi.Map.Program), "/swagger/v1/swagger.json")] + [InlineData(typeof(Authorization.Program), "/swagger/v1/swagger.json")] public async Task Swagger_IsValidJson_No_Startup( Type entryPointType, string swaggerRequestUri) diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/10_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/10_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt new file mode 100644 index 0000000000..1fb3df8769 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/10_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt @@ -0,0 +1,224 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Authorization API", + "version": "v1" + }, + "paths": { + "/resources": { + "get": { + "tags": [ + "Resources" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "Resources" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Administrator" + ] + } + ] + } + }, + "/resources/{id}": { + "get": { + "tags": [ + "Resources" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Reader", + "Administrator" + ] + } + ] + }, + "delete": { + "tags": [ + "Resources" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Administrator" + ] + } + ] + } + } + }, + "components": { + "schemas": { + "Resource": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "bearer": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "tags": [ + { + "name": "Resources" + } + ] +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/8_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/8_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt new file mode 100644 index 0000000000..1fb3df8769 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/8_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt @@ -0,0 +1,224 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Authorization API", + "version": "v1" + }, + "paths": { + "/resources": { + "get": { + "tags": [ + "Resources" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "Resources" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Administrator" + ] + } + ] + } + }, + "/resources/{id}": { + "get": { + "tags": [ + "Resources" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Reader", + "Administrator" + ] + } + ] + }, + "delete": { + "tags": [ + "Resources" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Administrator" + ] + } + ] + } + } + }, + "components": { + "schemas": { + "Resource": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "bearer": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "tags": [ + { + "name": "Resources" + } + ] +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/9_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/9_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt new file mode 100644 index 0000000000..1fb3df8769 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/9_0/VerifyTests.Swagger_IsValidJson_No_Startup_entryPointType=Authorization.Program_swaggerRequestUri=v1.verified.txt @@ -0,0 +1,224 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Authorization API", + "version": "v1" + }, + "paths": { + "/resources": { + "get": { + "tags": [ + "Resources" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Resource" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "Resources" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Administrator" + ] + } + ] + } + }, + "/resources/{id}": { + "get": { + "tags": [ + "Resources" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/Resource" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Reader", + "Administrator" + ] + } + ] + }, + "delete": { + "tags": [ + "Resources" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "bearer": [ + "Administrator" + ] + } + ] + } + } + }, + "components": { + "schemas": { + "Resource": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "bearer": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "tags": [ + { + "name": "Resources" + } + ] +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/KiotaOpenApiClient.verified.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/KiotaOpenApiClient.verified.cs new file mode 100644 index 0000000000..b51b78d957 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/KiotaOpenApiClient.verified.cs @@ -0,0 +1,43 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Serialization.Form; +using Microsoft.Kiota.Serialization.Json; +using Microsoft.Kiota.Serialization.Multipart; +using Microsoft.Kiota.Serialization.Text; +using Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System; +namespace Swashbuckle.AspNetCore.IntegrationTests.KiotaTests +{ + /// + /// The main entry point of the SDK, exposes the configuration and the fluent API. + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class KiotaOpenApiClient : BaseRequestBuilder + { + /// The resources property + public global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.ResourcesRequestBuilder Resources + { + get => new global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.ResourcesRequestBuilder(PathParameters, RequestAdapter); + } + /// + /// Instantiates a new and sets the default values. + /// + /// The request adapter to use to execute the requests. + public KiotaOpenApiClient(IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}", new Dictionary()) + { + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultSerializer(); + ApiClientBuilder.RegisterDefaultDeserializer(); + ApiClientBuilder.RegisterDefaultDeserializer(); + ApiClientBuilder.RegisterDefaultDeserializer(); + } + } +} +#pragma warning restore CS0618 diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Models/Resource.verified.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Models/Resource.verified.cs new file mode 100644 index 0000000000..e71661f59e --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Models/Resource.verified.cs @@ -0,0 +1,59 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using System.Collections.Generic; +using System.IO; +using System; +namespace Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models +{ + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + #pragma warning disable CS1591 + public partial class Resource : IParsable + #pragma warning restore CS1591 + { + /// The id property + public int? Id { get; set; } + /// The name property +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public string? Name { get; set; } +#nullable restore +#else + public string Name { get; set; } +#endif + /// + /// Creates a new instance of the appropriate class based on discriminator value + /// + /// A + /// The parse node to use to read the discriminator value and create the object + public static global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource CreateFromDiscriminatorValue(IParseNode parseNode) + { + if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode)); + return new global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource(); + } + /// + /// The deserialization information for the current model + /// + /// A IDictionary<string, Action<IParseNode>> + public virtual IDictionary> GetFieldDeserializers() + { + return new Dictionary> + { + { "id", n => { Id = n.GetIntValue(); } }, + { "name", n => { Name = n.GetStringValue(); } }, + }; + } + /// + /// Serializes information the current object + /// + /// Serialization writer to use to serialize this model + public virtual void Serialize(ISerializationWriter writer) + { + if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer)); + writer.WriteIntValue("id", Id); + writer.WriteStringValue("name", Name); + } + } +} +#pragma warning restore CS0618 diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Resources/Item/ResourcesItemRequestBuilder.verified.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Resources/Item/ResourcesItemRequestBuilder.verified.cs new file mode 100644 index 0000000000..4724f97fde --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Resources/Item/ResourcesItemRequestBuilder.verified.cs @@ -0,0 +1,124 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item +{ + /// + /// Builds and executes requests for operations under \resources\{id} + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ResourcesItemRequestBuilder : BaseRequestBuilder + { + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public ResourcesItemRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/resources/{id}", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public ResourcesItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/resources/{id}", rawUrl) + { + } + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task DeleteAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task DeleteAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToDeleteRequestInformation(requestConfiguration); + return await RequestAdapter.SendPrimitiveAsync(requestInfo, default, cancellationToken).ConfigureAwait(false); + } + /// A + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + return await RequestAdapter.SendAsync(requestInfo, global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource.CreateFromDiscriminatorValue, default, cancellationToken).ConfigureAwait(false); + } + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToDeleteRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToDeleteRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.DELETE, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + return requestInfo; + } + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9"); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item.ResourcesItemRequestBuilder WithUrl(string rawUrl) + { + return new global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item.ResourcesItemRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ResourcesItemRequestBuilderDeleteRequestConfiguration : RequestConfiguration + { + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ResourcesItemRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Resources/ResourcesRequestBuilder.verified.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Resources/ResourcesRequestBuilder.verified.cs new file mode 100644 index 0000000000..cdd3234b81 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_1ea4601ed911b960/Resources/ResourcesRequestBuilder.verified.cs @@ -0,0 +1,157 @@ +// +#pragma warning disable CS0618 +using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions; +using Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models; +using Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using System.Threading; +using System; +namespace Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources +{ + /// + /// Builds and executes requests for operations under \resources + /// + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ResourcesRequestBuilder : BaseRequestBuilder + { + /// Gets an item from the Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.resources.item collection + /// Unique identifier of the item + /// A + public global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item.ResourcesItemRequestBuilder this[int position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + urlTplParams.Add("id", position); + return new global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item.ResourcesItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// Gets an item from the Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.resources.item collection + /// Unique identifier of the item + /// A + [Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")] + public global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item.ResourcesItemRequestBuilder this[string position] + { + get + { + var urlTplParams = new Dictionary(PathParameters); + if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("id", position); + return new global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.Item.ResourcesItemRequestBuilder(urlTplParams, RequestAdapter); + } + } + /// + /// Instantiates a new and sets the default values. + /// + /// Path parameters for the request + /// The request adapter to use to execute the requests. + public ResourcesRequestBuilder(Dictionary pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/resources", pathParameters) + { + } + /// + /// Instantiates a new and sets the default values. + /// + /// The raw URL to use for the request builder. + /// The request adapter to use to execute the requests. + public ResourcesRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/resources", rawUrl) + { + } + /// A List<global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource> + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task?> GetAsync(Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task> GetAsync(Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + var requestInfo = ToGetRequestInformation(requestConfiguration); + var collectionResult = await RequestAdapter.SendCollectionAsync(requestInfo, global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource.CreateFromDiscriminatorValue, default, cancellationToken).ConfigureAwait(false); + return collectionResult?.AsList(); + } + /// A + /// The request body + /// Cancellation token to use when cancelling requests + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public async Task PostAsync(global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource body, Action>? requestConfiguration = default, CancellationToken cancellationToken = default) + { +#nullable restore +#else + public async Task PostAsync(global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource body, Action> requestConfiguration = default, CancellationToken cancellationToken = default) + { +#endif + if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body)); + var requestInfo = ToPostRequestInformation(body, requestConfiguration); + return await RequestAdapter.SendAsync(requestInfo, global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource.CreateFromDiscriminatorValue, default, cancellationToken).ConfigureAwait(false); + } + /// A + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToGetRequestInformation(Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToGetRequestInformation(Action> requestConfiguration = default) + { +#endif + var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9"); + return requestInfo; + } + /// A + /// The request body + /// Configuration for the request such as headers, query parameters, and middleware options. +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER +#nullable enable + public RequestInformation ToPostRequestInformation(global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource body, Action>? requestConfiguration = default) + { +#nullable restore +#else + public RequestInformation ToPostRequestInformation(global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Models.Resource body, Action> requestConfiguration = default) + { +#endif + if(ReferenceEquals(body, null)) throw new ArgumentNullException(nameof(body)); + var requestInfo = new RequestInformation(Method.POST, UrlTemplate, PathParameters); + requestInfo.Configure(requestConfiguration); + requestInfo.Headers.TryAdd("Accept", "application/json, text/plain;q=0.9"); + requestInfo.SetContentFromParsable(RequestAdapter, "application/json", body); + return requestInfo; + } + /// + /// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. + /// + /// A + /// The raw URL to use for the request builder. + public global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.ResourcesRequestBuilder WithUrl(string rawUrl) + { + return new global::Swashbuckle.AspNetCore.IntegrationTests.KiotaTests.Resources.ResourcesRequestBuilder(rawUrl, RequestAdapter); + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ResourcesRequestBuilderGetRequestConfiguration : RequestConfiguration + { + } + /// + /// Configuration for the request such as headers, query parameters, and middleware options. + /// + [Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")] + [global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")] + public partial class ResourcesRequestBuilderPostRequestConfiguration : RequestConfiguration + { + } + } +} +#pragma warning restore CS0618 diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_b3a89011d8f70304/NSwagOpenApiClient.verified.cs b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_b3a89011d8f70304/NSwagOpenApiClient.verified.cs new file mode 100644 index 0000000000..f4ee7a46d9 --- /dev/null +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/snapshots/code/GeneratesValidClient_b3a89011d8f70304/NSwagOpenApiClient.verified.cs @@ -0,0 +1,626 @@ +//---------------------- +// +// Generated using the NSwag toolchain +// +//---------------------- + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 612 // Disable "CS0612 '...' is obsolete" +#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null" +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" +#pragma warning disable 8600 // Disable "CS8600 Converting null literal or possible null value to non-nullable type" +#pragma warning disable 8602 // Disable "CS8602 Dereference of a possibly null reference" +#pragma warning disable 8603 // Disable "CS8603 Possible null reference return" +#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter" +#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type" +#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)." + +namespace Swashbuckle.AspNetCore.IntegrationTests.NSwagTests +{ + using System = global::System; + + [GeneratedCode] + public partial class NSwagOpenApiClient + { + #pragma warning disable 8618 + private string _baseUrl; + #pragma warning restore 8618 + + private System.Net.Http.HttpClient _httpClient; + private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true); + private Newtonsoft.Json.JsonSerializerSettings _instanceSettings; + + #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public NSwagOpenApiClient(string baseUrl, System.Net.Http.HttpClient httpClient) + #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + BaseUrl = baseUrl; + _httpClient = httpClient; + Initialize(); + } + + private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + public string BaseUrl + { + get { return _baseUrl; } + set + { + _baseUrl = value; + if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/")) + _baseUrl += '/'; + } + } + + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _instanceSettings ?? _settings.Value; } } + + static partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + + partial void Initialize(); + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> ResourcesAllAsync() + { + return ResourcesAllAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> ResourcesAllAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "resources" + urlBuilder_.Append("resources"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task ResourcesPOSTAsync(Resource body) + { + return ResourcesPOSTAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task ResourcesPOSTAsync(Resource body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "resources" + urlBuilder_.Append("resources"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 401) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Unauthorized", status_, responseText_, headers_, null); + } + else + if (status_ == 403) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Forbidden", status_, responseText_, headers_, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task ResourcesGETAsync(int id) + { + return ResourcesGETAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task ResourcesGETAsync(int id, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "resources/{id}" + urlBuilder_.Append("resources/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + if (status_ == 401) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Unauthorized", status_, responseText_, headers_, null); + } + else + if (status_ == 403) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Forbidden", status_, responseText_, headers_, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task ResourcesDELETEAsync(int id) + { + return ResourcesDELETEAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task ResourcesDELETEAsync(int id, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "resources/{id}" + urlBuilder_.Append("resources/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + if (status_ == 401) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Unauthorized", status_, responseText_, headers_, null); + } + else + if (status_ == 403) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Forbidden", status_, responseText_, headers_, null); + } + else + { + var responseData_ = response_.Content == null ? null : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static System.Threading.Tasks.Task ReadAsStringAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) + { + #if NET5_0_OR_GREATER + return content.ReadAsStringAsync(cancellationToken); + #else + return content.ReadAsStringAsync(); + #endif + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static System.Threading.Tasks.Task ReadAsStreamAsync(System.Net.Http.HttpContent content, System.Threading.CancellationToken cancellationToken) + { + #if NET5_0_OR_GREATER + return content.ReadAsStreamAsync(cancellationToken); + #else + return content.ReadAsStreamAsync(); + #endif + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await ReadAsStringAsync(response.Content, cancellationToken).ConfigureAwait(false); + try + { + var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await ReadAsStreamAsync(response.Content, cancellationToken).ConfigureAwait(false)) + using (var streamReader = new System.IO.StreamReader(responseStream)) + using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) + { + var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); + var typedBody = serializer.Deserialize(jsonTextReader); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field_ = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field_ != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field_, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value is string[]) + { + return string.Join(",", (string[])value); + } + else if (value.GetType().IsArray) + { + var valueArray = (System.Array)value; + var valueTextArray = new string[valueArray.Length]; + for (var i = 0; i < valueArray.Length; i++) + { + valueTextArray[i] = ConvertToString(valueArray.GetValue(i), cultureInfo); + } + return string.Join(",", valueTextArray); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + [GeneratedCode] + public partial class Resource + { + + [Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int Id { get; set; } + + [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Name { get; set; } + + } + + + + [GeneratedCode] + public partial class ApiException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [GeneratedCode] + public partial class ApiException : ApiException + { + public TResult Result { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} + +#pragma warning restore 108 +#pragma warning restore 114 +#pragma warning restore 472 +#pragma warning restore 612 +#pragma warning restore 649 +#pragma warning restore 1573 +#pragma warning restore 1591 +#pragma warning restore 8073 +#pragma warning restore 3016 +#pragma warning restore 8600 +#pragma warning restore 8602 +#pragma warning restore 8603 +#pragma warning restore 8604 +#pragma warning restore 8625 +#pragma warning restore 8765 \ No newline at end of file diff --git a/test/WebSites/Authorization/Authorization.csproj b/test/WebSites/Authorization/Authorization.csproj new file mode 100644 index 0000000000..8b44ca1b9d --- /dev/null +++ b/test/WebSites/Authorization/Authorization.csproj @@ -0,0 +1,12 @@ + + + + $(DefaultTargetFrameworks) + + + + + + + + diff --git a/test/WebSites/Authorization/Controllers/ResourcesController.cs b/test/WebSites/Authorization/Controllers/ResourcesController.cs new file mode 100644 index 0000000000..cc6460ae36 --- /dev/null +++ b/test/WebSites/Authorization/Controllers/ResourcesController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Authorization.Controllers; + +[Route("resources")] +public class ResourcesController : Controller +{ + [HttpGet] + public IEnumerable GetAll() + { + return [new Resource { Id = 1, Name = "Public Resource" }]; + } + + [HttpGet("{id}")] + [Authorize(Policy = "readAccess")] + public Resource GetById(int id) + { + return new Resource { Id = id, Name = "Protected Resource" }; + } + + [HttpPost] + [Authorize(Policy = "writeAccess")] + public Resource Create([FromBody] Resource resource) + { + return resource; + } + + [HttpDelete("{id}")] + [Authorize(Policy = "writeAccess")] + public void Delete(int id) + { + } +} + +public class Resource +{ + public int Id { get; set; } + + public string Name { get; set; } +} diff --git a/test/WebSites/Authorization/Program.cs b/test/WebSites/Authorization/Program.cs new file mode 100644 index 0000000000..20168cf387 --- /dev/null +++ b/test/WebSites/Authorization/Program.cs @@ -0,0 +1,50 @@ +using System.Security.Claims; +using Authorization.Swagger; +using Microsoft.OpenApi; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("readAccess", policy => + policy.RequireClaim(ClaimTypes.Role, "Reader", "Administrator")); + + options.AddPolicy("writeAccess", policy => + policy.RequireClaim(ClaimTypes.Role, "Administrator")); +}); + +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Authorization API", + Version = "v1" + }); + + c.AddSecurityDefinition("bearer", new OpenApiSecurityScheme + { + Type = SecuritySchemeType.Http, + Scheme = "bearer", + BearerFormat = "JWT" + }); + + c.OperationAsyncFilter(); +}); + +var app = builder.Build(); + +app.UseAuthorization(); + +app.MapControllers(); +app.MapSwagger("swagger/{documentName}/swagger.json"); + +app.Run(); + +namespace Authorization +{ + public partial class Program + { + } +} diff --git a/test/WebSites/Authorization/Swagger/SecurityRequirementsAsyncOperationFilter.cs b/test/WebSites/Authorization/Swagger/SecurityRequirementsAsyncOperationFilter.cs new file mode 100644 index 0000000000..0605e430c8 --- /dev/null +++ b/test/WebSites/Authorization/Swagger/SecurityRequirementsAsyncOperationFilter.cs @@ -0,0 +1,65 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Infrastructure; +using Microsoft.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Authorization.Swagger; + +public class SecurityRequirementsAsyncOperationFilter(IAuthorizationPolicyProvider authorizationPolicyProvider) : IOperationAsyncFilter +{ + public async Task ApplyAsync(OpenApiOperation operation, OperationFilterContext context, CancellationToken cancellationToken) + { + var authorizeAttributes = context.MethodInfo + .GetCustomAttributes(true) + .Concat(context.MethodInfo.DeclaringType.GetCustomAttributes(true)) + .OfType() + .ToList(); + + if (authorizeAttributes.Count == 0) + { + return; + } + + var requiredRoles = new List(); + + foreach (var attribute in authorizeAttributes) + { + if (attribute.Policy == null) + { + continue; + } + + var policy = await authorizationPolicyProvider.GetPolicyAsync(attribute.Policy); + if (policy == null) + { + continue; + } + + var roleRequirements = policy.Requirements + .OfType() + .Where(r => r.ClaimType == ClaimTypes.Role) + .SelectMany(r => r.AllowedValues); + + requiredRoles.AddRange(roleRequirements); + } + + if (requiredRoles.Count == 0) + { + return; + } + + operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" }); + operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" }); + + var scheme = new OpenApiSecuritySchemeReference("bearer", context.Document); + + operation.Security = + [ + new OpenApiSecurityRequirement + { + [scheme] = requiredRoles.Distinct().ToList() + } + ]; + } +}