Skip to content

Commit 2271ae2

Browse files
committed
Use IAsyncSwaggerProvider in CLI to support async filters
Make the CLI's tofile command fully async, preferring IAsyncSwaggerProvider.GetSwaggerAsync() when available to ensure async operation filters (IOperationAsyncFilter) are executed. Falls back to ISwaggerProvider.GetSwagger() for backward compatibility. - Make CommandRunner and Program.Main fully async (no sync-over-async) - Add Authorization test WebSite demonstrating real-world async filter use case: resolving authorization policies via IAuthorizationPolicyProvider - SecurityRequirementsAsyncOperationFilter uses GetPolicyAsync() to resolve claim requirements and adds security to operations - Integration test snapshots and CLI test for the new WebSite
1 parent d22870e commit 2271ae2

16 files changed

+947
-62
lines changed

Swashbuckle.AspNetCore.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<Project Path="test/Swashbuckle.AspNetCore.TestSupport/Swashbuckle.AspNetCore.TestSupport.csproj" />
5959
</Folder>
6060
<Folder Name="/test/WebSites/">
61+
<Project Path="test/WebSites/Authorization/Authorization.csproj" />
6162
<Project Path="test/WebSites/Basic/Basic.csproj" />
6263
<Project Path="test/WebSites/CliExample/CliExample.csproj" />
6364
<Project Path="test/WebSites/CliExampleWithFactory/CliExampleWithFactory.csproj" />

src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ internal class CommandRunner(string commandName, string commandDescription, Text
44
{
55
private readonly Dictionary<string, string> _argumentDescriptors = [];
66
private readonly Dictionary<string, OptionDescriptor> _optionDescriptors = [];
7-
private Func<IDictionary<string, string>, int> _runFunc = (_) => 1;
7+
private Func<IDictionary<string, string>, Task<int>> _runFunc = (_) => Task.FromResult(1);
88
private readonly List<CommandRunner> _subRunners = [];
99
private readonly TextWriter _output = output;
1010

@@ -27,7 +27,7 @@ public void Option(string name, string description, bool isFlag = false)
2727
_optionDescriptors.Add(name, new OptionDescriptor { Description = description, IsFlag = isFlag });
2828
}
2929

30-
public void OnRun(Func<IDictionary<string, string>, int> runFunc)
30+
public void OnRun(Func<IDictionary<string, string>, Task<int>> runFunc)
3131
{
3232
_runFunc = runFunc;
3333
}
@@ -39,12 +39,12 @@ public void SubCommand(string name, string description, Action<CommandRunner> co
3939
_subRunners.Add(runner);
4040
}
4141

42-
public int Run(IEnumerable<string> args)
42+
public async Task<int> RunAsync(IEnumerable<string> args)
4343
{
4444
if (args.Any())
4545
{
4646
var subRunner = _subRunners.FirstOrDefault(r => r.CommandName.Split(' ').Last() == args.First());
47-
if (subRunner != null) return subRunner.Run(args.Skip(1));
47+
if (subRunner != null) return await subRunner.RunAsync(args.Skip(1));
4848
}
4949

5050
if (_subRunners.Count != 0 || !TryParseArgs(args, out IDictionary<string, string> namedArgs))
@@ -53,7 +53,7 @@ public int Run(IEnumerable<string> args)
5353
return 1;
5454
}
5555

56-
return _runFunc(namedArgs);
56+
return await _runFunc(namedArgs);
5757
}
5858

5959
private bool TryParseArgs(IEnumerable<string> args, out IDictionary<string, string> namedArgs)

src/Swashbuckle.AspNetCore.Cli/Program.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal class Program
1616
{
1717
private const string OpenApiVersionOption = "--openapiversion";
1818

19-
public static int Main(string[] args)
19+
public static async Task<int> Main(string[] args)
2020
{
2121
// Helper to simplify command line parsing etc.
2222
var runner = new CommandRunner("dotnet swagger", "Swashbuckle (Swagger) Command Line Tools", Console.Out);
@@ -45,7 +45,7 @@ public static int Main(string[] args)
4545
using var child = Process.Start("dotnet", subProcessCommandLine);
4646

4747
child.WaitForExit();
48-
return child.ExitCode;
48+
return Task.FromResult(child.ExitCode);
4949
});
5050
});
5151

@@ -60,14 +60,16 @@ public static int Main(string[] args)
6060
c.Option(OpenApiVersionOption, "");
6161
c.Option("--yaml", "", true);
6262

63-
c.OnRun((namedArgs) =>
63+
c.OnRun(async (namedArgs) =>
6464
{
65-
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var swaggerProvider, out var swaggerOptions);
65+
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var asyncSwaggerProvider, out var swaggerProvider, out var swaggerOptions);
6666
var swaggerDocumentSerializer = swaggerOptions?.Value?.CustomDocumentSerializer;
67-
var swagger = swaggerProvider.GetSwagger(
68-
namedArgs["swaggerdoc"],
69-
namedArgs.TryGetValue("--host", out var arg) ? arg : null,
70-
namedArgs.TryGetValue("--basepath", out var namedArg) ? namedArg : null);
67+
68+
var host = namedArgs.TryGetValue("--host", out var arg) ? arg : null;
69+
var basePath = namedArgs.TryGetValue("--basepath", out var namedArg) ? namedArg : null;
70+
var swagger = asyncSwaggerProvider != null
71+
? await asyncSwaggerProvider.GetSwaggerAsync(namedArgs["swaggerdoc"], host, basePath)
72+
: swaggerProvider.GetSwagger(namedArgs["swaggerdoc"], host, basePath);
7173

7274
// 4) Serialize to specified output location or stdout
7375
var outputPath = namedArgs.TryGetValue("--output", out var arg1)
@@ -139,7 +141,7 @@ public static int Main(string[] args)
139141
using var child = Process.Start("dotnet", subProcessCommandLine);
140142

141143
child.WaitForExit();
142-
return child.ExitCode;
144+
return Task.FromResult(child.ExitCode);
143145
});
144146
});
145147

@@ -150,7 +152,7 @@ public static int Main(string[] args)
150152
c.Option("--output", "");
151153
c.OnRun((namedArgs) =>
152154
{
153-
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out var swaggerProvider, out var swaggerOptions);
155+
SetupAndRetrieveSwaggerProviderAndOptions(namedArgs, out _, out var swaggerProvider, out var swaggerOptions);
154156
IList<string> docNames = [];
155157

156158
string outputPath = namedArgs.TryGetValue("--output", out var arg1)
@@ -172,7 +174,7 @@ public static int Main(string[] args)
172174
if (swaggerProvider is not ISwaggerDocumentMetadataProvider docMetaProvider)
173175
{
174176
writer.WriteLine($"The registered {nameof(ISwaggerProvider)} instance does not implement {nameof(ISwaggerDocumentMetadataProvider)}; unable to list the Swagger document names.");
175-
return -1;
177+
return Task.FromResult(-1);
176178
}
177179

178180
docNames = docMetaProvider.GetDocumentNames();
@@ -182,14 +184,14 @@ public static int Main(string[] args)
182184
writer.WriteLine($"\"{name}\"");
183185
}
184186

185-
return 0;
187+
return Task.FromResult(0);
186188
});
187189
});
188190

189-
return runner.Run(args);
191+
return await runner.RunAsync(args);
190192
}
191193

192-
private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary<string, string> namedArgs, out ISwaggerProvider swaggerProvider, out IOptions<SwaggerOptions> swaggerOptions)
194+
private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary<string, string> namedArgs, out IAsyncSwaggerProvider asyncSwaggerProvider, out ISwaggerProvider swaggerProvider, out IOptions<SwaggerOptions> swaggerOptions)
193195
{
194196
// 1) Configure host with provided startupassembly
195197
var startupAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
@@ -199,6 +201,7 @@ private static void SetupAndRetrieveSwaggerProviderAndOptions(IDictionary<string
199201
var serviceProvider = GetServiceProvider(startupAssembly);
200202

201203
// 3) Retrieve Swagger via configured provider
204+
asyncSwaggerProvider = serviceProvider.GetService<IAsyncSwaggerProvider>();
202205
swaggerProvider = serviceProvider.GetRequiredService<ISwaggerProvider>();
203206
swaggerOptions = serviceProvider.GetService<IOptions<SwaggerOptions>>();
204207
}

test/Swashbuckle.AspNetCore.Cli.Test/CommandRunnerTests.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Swashbuckle.AspNetCore.Cli.Test;
55
public static class CommandRunnerTests
66
{
77
[Fact]
8-
public static void Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredMetadata()
8+
public static async Task Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredMetadata()
99
{
1010
var receivedValues = new List<string>();
1111
var subject = new CommandRunner("test", "a test", new StringWriter());
@@ -18,7 +18,7 @@ public static void Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredM
1818
receivedValues.Add(namedArgs["--opt1"]);
1919
receivedValues.Add(namedArgs["--opt2"]);
2020
receivedValues.Add(namedArgs["arg1"]);
21-
return 2;
21+
return Task.FromResult(2);
2222
});
2323
});
2424
subject.SubCommand("cmd2", "", c => {
@@ -30,42 +30,42 @@ public static void Run_ParsesArgumentsAndExecutesCommands_AccordingToConfiguredM
3030
receivedValues.Add(namedArgs["--opt1"]);
3131
receivedValues.Add(namedArgs["--opt2"]);
3232
receivedValues.Add(namedArgs["arg1"]);
33-
return 3;
33+
return Task.FromResult(3);
3434
});
3535
});
3636

37-
var cmd1ExitCode = subject.Run(["cmd1", "--opt1", "foo", "--opt2", "bar"]);
38-
var cmd2ExitCode = subject.Run(["cmd2", "--opt1", "blah", "--opt2", "dblah"]);
37+
var cmd1ExitCode = await subject.RunAsync(["cmd1", "--opt1", "foo", "--opt2", "bar"]);
38+
var cmd2ExitCode = await subject.RunAsync(["cmd2", "--opt1", "blah", "--opt2", "dblah"]);
3939

4040
Assert.Equal(2, cmd1ExitCode);
4141
Assert.Equal(3, cmd2ExitCode);
4242
Assert.Equal(["foo", null, "bar", "blah", null, "dblah"], [.. receivedValues]);
4343
}
4444

4545
[Fact]
46-
public static void Run_PrintsAvailableCommands_WhenUnexpectedCommandIsProvided()
46+
public static async Task Run_PrintsAvailableCommands_WhenUnexpectedCommandIsProvided()
4747
{
4848
var output = new StringWriter();
4949
var subject = new CommandRunner("test", "a test", output);
5050
subject.SubCommand("cmd", "does something", c => {
5151
});
5252

53-
var exitCode = subject.Run(["foo"]);
53+
var exitCode = await subject.RunAsync(["foo"]);
5454

5555
Assert.StartsWith("a test", output.ToString());
5656
Assert.Contains("Commands:", output.ToString());
5757
Assert.Contains("cmd: does something", output.ToString());
5858
}
5959

6060
[Fact]
61-
public static void Run_PrintsAvailableCommands_WhenHelpOptionIsProvided()
61+
public static async Task Run_PrintsAvailableCommands_WhenHelpOptionIsProvided()
6262
{
6363
var output = new StringWriter();
6464
var subject = new CommandRunner("test", "a test", output);
6565
subject.SubCommand("cmd", "does something", c => {
6666
});
6767

68-
var exitCode = subject.Run(["--help"]);
68+
var exitCode = await subject.RunAsync(["--help"]);
6969

7070
Assert.StartsWith("a test", output.ToString());
7171
Assert.Contains("Commands:", output.ToString());
@@ -81,7 +81,7 @@ public static void Run_PrintsAvailableCommands_WhenHelpOptionIsProvided()
8181
[InlineData(new string[0], new[] { "arg1" }, new[] { "cmd", "--opt1" }, true)]
8282
[InlineData(new string[0], new[] { "arg1" }, new[] { "cmd", "foo", "bar" }, true)]
8383
[InlineData(new string[0], new[] { "arg1" }, new[] { "cmd", "foo" }, false)]
84-
public static void Run_PrintsCommandUsage_WhenUnexpectedArgumentsAreProvided(
84+
public static async Task Run_PrintsCommandUsage_WhenUnexpectedArgumentsAreProvided(
8585
string[] optionNames,
8686
string[] argNames,
8787
string[] providedArgs,
@@ -97,7 +97,7 @@ public static void Run_PrintsCommandUsage_WhenUnexpectedArgumentsAreProvided(
9797
c.Argument(name, "");
9898
});
9999

100-
subject.Run(providedArgs);
100+
await subject.RunAsync(providedArgs);
101101

102102
if (shouldPrintUsage)
103103
Assert.StartsWith("Usage: test cmd", output.ToString());

test/Swashbuckle.AspNetCore.Cli.Test/Swashbuckle.AspNetCore.Cli.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
<ItemGroup>
99
<ProjectReference Include="..\Swashbuckle.AspNetCore.TestSupport\Swashbuckle.AspNetCore.TestSupport.csproj" />
10+
<ProjectReference Include="..\WebSites\Authorization\Authorization.csproj" />
1011
<ProjectReference Include="..\WebSites\Basic\Basic.csproj" />
1112
<ProjectReference Include="..\WebSites\CustomDocumentSerializer\CustomDocumentSerializer.csproj" />
1213
<ProjectReference Include="..\WebSites\MinimalAppWithHostedServices\MinimalAppWithHostedServices.csproj" />

0 commit comments

Comments
 (0)