Skip to content

Commit a379a3e

Browse files
Senn GeertsSenn Geerts
Senn Geerts
authored and
Senn Geerts
committed
#197 Produce AsyncAPI documents at build time
1 parent f0af6d1 commit a379a3e

11 files changed

+149
-13
lines changed

Saunter.sln

+16-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saunter.Tests.MarkerTypeTes
3131
EndProject
3232
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.Cli", "src\AsyncAPI.Saunter.Generator.Cli\AsyncAPI.Saunter.Generator.Cli.csproj", "{6C102D4D-3DA4-4763-B75E-C15E33E7E94A}"
3333
EndProject
34-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncAPI.Saunter.Generator.Cli.Tests", "test\AsyncAPI.Saunter.Generator.Cli.Tests\AsyncAPI.Saunter.Generator.Cli.Tests.csproj", "{18AD0249-0436-4A26-9972-B97BA6905A54}"
34+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.Cli.Tests", "test\AsyncAPI.Saunter.Generator.Cli.Tests\AsyncAPI.Saunter.Generator.Cli.Tests.csproj", "{18AD0249-0436-4A26-9972-B97BA6905A54}"
35+
EndProject
36+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.Build", "src\AsyncAPI.Saunter.Generator.Build\AsyncAPI.Saunter.Generator.Build.csproj", "{A320E670-5CB0-4815-AF67-D8D09FC92A2A}"
3537
EndProject
3638
Global
3739
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -127,6 +129,18 @@ Global
127129
{18AD0249-0436-4A26-9972-B97BA6905A54}.Release|x64.Build.0 = Release|Any CPU
128130
{18AD0249-0436-4A26-9972-B97BA6905A54}.Release|x86.ActiveCfg = Release|Any CPU
129131
{18AD0249-0436-4A26-9972-B97BA6905A54}.Release|x86.Build.0 = Release|Any CPU
132+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
133+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
134+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Debug|x64.ActiveCfg = Debug|Any CPU
135+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Debug|x64.Build.0 = Debug|Any CPU
136+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Debug|x86.ActiveCfg = Debug|Any CPU
137+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Debug|x86.Build.0 = Debug|Any CPU
138+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
139+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Release|Any CPU.Build.0 = Release|Any CPU
140+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Release|x64.ActiveCfg = Release|Any CPU
141+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Release|x64.Build.0 = Release|Any CPU
142+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Release|x86.ActiveCfg = Release|Any CPU
143+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A}.Release|x86.Build.0 = Release|Any CPU
130144
EndGlobalSection
131145
GlobalSection(SolutionProperties) = preSolution
132146
HideSolutionNode = FALSE
@@ -139,6 +153,7 @@ Global
139153
{02284473-6DE7-4EE0-8433-2AC295045549} = {6491E321-2D02-44AB-9116-D722FE169595}
140154
{6C102D4D-3DA4-4763-B75E-C15E33E7E94A} = {28D4C365-FDED-49AE-A97D-36202E24A55A}
141155
{18AD0249-0436-4A26-9972-B97BA6905A54} = {6491E321-2D02-44AB-9116-D722FE169595}
156+
{A320E670-5CB0-4815-AF67-D8D09FC92A2A} = {28D4C365-FDED-49AE-A97D-36202E24A55A}
142157
EndGlobalSection
143158
GlobalSection(ExtensibilityGlobals) = postSolution
144159
SolutionGuid = {2F85D9DA-DBCF-4F13-8C42-5719F1469B2E}

examples/StreetlightsAPI/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
specs/

examples/StreetlightsAPI/StreetlightsAPI.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
<PropertyGroup>
44
<TargetFramework>net6.0</TargetFramework>
55
<IsPackable>false</IsPackable>
6+
<AsyncAPIGenerateDocumentsOnBuild>true</AsyncAPIGenerateDocumentsOnBuild>
7+
<AsyncAPIDocumentFormats>json,yml</AsyncAPIDocumentFormats>
8+
<AsyncAPIDocumentFilename>streetlights.{extension}</AsyncAPIDocumentFilename>
9+
<AsyncAPIDocumentOutputPath>specs</AsyncAPIDocumentOutputPath>
610
</PropertyGroup>
711

812
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<LangVersion>12</LangVersion>
7+
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
8+
9+
<Description>AsyncAPI Build Tools</Description>
10+
<Authors>AsyncAPI Initiative</Authors>
11+
<PackageId>AsyncAPI.Saunter.Generator.Build</PackageId>
12+
<PackageTags>asyncapi;aspnetcore;openapi;documentation;amqp;generator;build;tool</PackageTags>
13+
<PackageReadmeFile>readme.md</PackageReadmeFile>
14+
<PackageIcon>logo.png</PackageIcon>
15+
<RepositoryUrl>https://github.com/asyncapi/saunter</RepositoryUrl>
16+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
17+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
18+
<IncludeBuildOutput>false</IncludeBuildOutput>
19+
<IncludeSymbols>false</IncludeSymbols>
20+
<PackageProjectUrl>https://github.com/asyncapi/saunter</PackageProjectUrl>
21+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
22+
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
23+
<Version>0.0.24</Version>
24+
<OutputPath>tools/$(Configuration)</OutputPath>
25+
</PropertyGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\AsyncAPI.Saunter.Generator.Cli\AsyncAPI.Saunter.Generator.Cli.csproj" />
29+
<ProjectReference Include="..\Saunter\Saunter.csproj" />
30+
<ProjectReference Update="@(ProjectReference)" PrivateAssets="All" />
31+
</ItemGroup>
32+
33+
<ItemGroup Condition=" '$(TargetFramework)' != 'netstandard2.0' ">
34+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
35+
</ItemGroup>
36+
37+
<ItemGroup>
38+
<PackageReference Include="AsyncAPI.NET.Readers" Version="5.2.1" />
39+
40+
<!-- Development Dependencies -->
41+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
42+
<PackageReference Update="@(PackageReference)" PrivateAssets="All" />
43+
</ItemGroup>
44+
45+
<ItemGroup>
46+
<None Include="readme.md" Pack="true" PackagePath="/" />
47+
<None Include="../../assets/logo.png" Pack="true" PackagePath="/" />
48+
<None Include="build/*" Pack="true" PackagePath="/build" />
49+
<None Include="$(OutputPath)/net6.0/*" Pack="true" PackagePath="/tools/net6.0" />
50+
</ItemGroup>
51+
52+
<!--ItemGroup>
53+
<Compile Include="../AsyncAPI.Saunter.Generator.Cli/Commands/**/*.cs" />
54+
<Compile Include="../AsyncAPI.Saunter.Generator.Cli/Internal/**/*.cs" />
55+
<Compile Include="../AsyncAPI.Saunter.Generator.Cli/SwashbuckleImport/**/*.cs" />
56+
<Compile Include="../AsyncAPI.Saunter.Generator.Cli/*.cs" />
57+
</ItemGroup>-->
58+
59+
<ItemGroup>
60+
<Folder Include="tools\" />
61+
</ItemGroup>
62+
63+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8" standalone="no"?>
2+
<Project>
3+
<PropertyGroup>
4+
<AsyncAPIGenerateDocumentsOnBuild Condition=" '$(AsyncAPIGenerateDocumentsOnBuild)' == '' ">true</AsyncAPIGenerateDocumentsOnBuild>
5+
<AsyncAPIDocumentFormats Condition=" '$(AsyncAPIDocumentFormats)' == '' ">json</AsyncAPIDocumentFormats>
6+
<AsyncAPIDocumentOutputPath Condition=" '$(AsyncAPIDocumentOutputPath)' == '' ">./</AsyncAPIDocumentOutputPath>
7+
<AsyncAPIDocumentFilename Condition=" '$(AsyncAPIDocumentFilename)' == '' "></AsyncAPIDocumentFilename>
8+
<AsyncAPIDocumentNames Condition=" '$(AsyncAPIDocumentNames)' == '' "></AsyncAPIDocumentNames>
9+
<AsyncAPIDocumentEnvVars Condition=" '$(AsyncAPIDocumentEnvVars)' == '' "></AsyncAPIDocumentEnvVars>
10+
</PropertyGroup>
11+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8" standalone="no"?>
2+
<Project>
3+
4+
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition=" '$(AsyncAPIGenerateDocumentsOnBuild)' == 'true' ">
5+
<Message Text="AsyncAPI.Build; AsyncAPIDocumentFormats: $(AsyncAPIDocumentFormats)" />
6+
<Message Text="AsyncAPI.Build; AsyncAPIDocumentOutputPath: $(AsyncAPIDocumentOutputPath)" />
7+
<Message Text="AsyncAPI.Build; AsyncAPIDocumentNames: $(AsyncAPIDocumentNames)" />
8+
<Message Text="AsyncAPI.Build; AsyncAPIDocumentFilename: $(AsyncAPIDocumentFilename)" />
9+
<Message Text="AsyncAPI.Build; AsyncAPIDocumentEnvVars: $(AsyncAPIDocumentEnvVars)" />
10+
<Message Text="AsyncAPI.Build; MSBuildThisFileDirectory: $(MSBuildThisFileDirectory)" />
11+
<Message Text="AsyncAPI.Build; MSBuildProjectFullPath: $(MSBuildProjectFullPath)" />
12+
<Message Text="AsyncAPI.Build; MSBuildProjectDirectory: $(MSBuildProjectDirectory)" />
13+
14+
<Exec Command="dotnet &quot;$(MSBuildThisFileDirectory)/../tools/net6.0/AsyncAPI.Saunter.Generator.Cli.dll&quot; tofile --output &quot;$(MSBuildProjectDirectory)/$(AsyncAPIDocumentOutputPath)&quot; --format &quot;$(AsyncAPIDocumentFormats)&quot; --doc &quot;$(AsyncAPIDocumentNames)&quot; --filename &quot;$(AsyncAPIDocumentFilename)&quot; --env &quot;$(AsyncAPIDocumentEnvVars)&quot; &quot;$(MSBuildProjectDirectory)/$(OutputPath)/$(AssemblyTitle).dll&quot;"/>
15+
</Target>
16+
17+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# AsyncApi Generator.Build Nuget Package
2+
A nuget package to generate AsyncAPI specification files at build time, based on code-first attributes.
3+
4+
# Customizations
5+
The AsyncAPI spec generation can be configured through properties in the csproj-file (or .props files):
6+
```
7+
<PropertyGroup>
8+
<AsyncAPIGenerateDocumentsOnBuild></AsyncAPIGenerateDocumentsOnBuild>
9+
<AsyncAPIDocumentFormats></AsyncAPIDocumentFormats>
10+
<AsyncAPIDocumentOutputPath></AsyncAPIDocumentOutputPath>
11+
<AsyncAPIDocumentNames></AsyncAPIDocumentNames>
12+
<AsyncAPIDocumentFilename></AsyncAPIDocumentFilename>
13+
<AsyncAPIDocumentEnvVars></AsyncAPIDocumentEnvVars>
14+
</PropertyGroup>
15+
```
16+
17+
Defaults are the same as the underlying [Generator.Cli tool](https://www.nuget.org/packages/AsyncAPI.Saunter.Generator.Cli).
18+
19+
If the ```AsyncAPI.Saunter.Generator.Build``` Nuget package is referenced, the default is to generate AsyncAPI spec files at build time.
20+
21+
- AsyncAPIGenerateDocumentsOnBuild: Whether to actually generate AsyncAPI spec files on build (true or false, default: true)
22+
- AsyncAPIDocumentFormats: Format of the expected AsyncAPI spec files (json, yml or yaml, default: json)
23+
- AsyncAPIDocumentOutputPath: Output path for the AsyncAPI spec files, relative to the csproj location. (default is the csproj root path: ./)
24+
- AsyncAPIDocumentNames: The AsyncAPI documents to generate. (default: generate all known documents)
25+
- AsyncAPIDocumentFilename: Template of the AsyncAPI spec files (default: "{document}_asyncapi.{extension}")
26+
- AsyncAPIDocumentEnvVars: Environment variable(s) to set during generation of the AsyncAPI spec files (default: none, Example: "ASPNETCORE_ENVIRONMENT=Development")
27+
None of these properties are mandatory. By only referencing the [AsyncAPI.Saunter.Generator.Build](https://www.nuget.org/packages/AsyncAPI.Saunter.Generator.Build) Nuget package a json AsyncAPI spec file will be generated for all AsyncAPI documents.

src/AsyncAPI.Saunter.Generator.Cli/AsyncAPI.Saunter.Generator.Cli.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
5+
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<LangVersion>12</LangVersion>
88
<RootNamespace>AsyncAPI.Saunter.Generator.Cli</RootNamespace>

src/AsyncAPI.Saunter.Generator.Cli/Commands/Tofile.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ internal static Func<IDictionary<string, string>, int> Run(string[] args) => nam
4141

4242
private static string EscapePath(string path)
4343
{
44-
return path.Contains(' ') ? "\"" + path + "\"" : path;
44+
return (path.Contains(' ') || string.IsNullOrWhiteSpace(path)) ? "\"" + path + "\"" : path;
4545
}
4646
}

src/AsyncAPI.Saunter.Generator.Cli/Commands/TofileInternal.cs

+7-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Diagnostics;
56
using LEGO.AsyncAPI.Readers;
67
using Microsoft.Extensions.Options;
78
using Saunter.Serialization;
@@ -31,7 +32,7 @@ internal static int Run(IDictionary<string, string> namedArgs)
3132
var startupAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Directory.GetCurrentDirectory(), namedArgs[StartupAssemblyArgument]));
3233

3334
// 2) Build a service container that's based on the startup assembly
34-
var envVars = namedArgs.TryGetValue(EnvOption, out var x) ? x.Split(',').Select(x => x.Trim()) : Array.Empty<string>();
35+
var envVars = (namedArgs.TryGetValue(EnvOption, out var x) && !string.IsNullOrWhiteSpace(x)) ? x.Split(',').Select(x => x.Trim()) : Array.Empty<string>();
3536
foreach (var envVar in envVars.Select(x => x.Split('=').Select(x => x.Trim()).ToList()))
3637
{
3738
if (envVar.Count == 2)
@@ -50,8 +51,8 @@ internal static int Run(IDictionary<string, string> namedArgs)
5051
var asyncapiOptions = serviceProvider.GetService<IOptions<AsyncApiOptions>>().Value;
5152
var documentSerializer = serviceProvider.GetRequiredService<IAsyncApiDocumentSerializer>();
5253

53-
var documentNames = namedArgs.TryGetValue(DocOption, out var doc) ? [doc] : asyncapiOptions.NamedApis.Keys;
54-
var fileTemplate = namedArgs.TryGetValue(FileNameOption, out var template) ? template : "{document}_asyncapi.{extension}";
54+
var documentNames = (namedArgs.TryGetValue(DocOption, out var doc) && !string.IsNullOrWhiteSpace(doc)) ? [doc] : asyncapiOptions.NamedApis.Keys;
55+
var fileTemplate = (namedArgs.TryGetValue(FileNameOption, out var template) && !string.IsNullOrWhiteSpace(template)) ? template : "{document}_asyncapi.{extension}";
5556
if (documentNames.Count == 0)
5657
{
5758
if (asyncapiOptions.AssemblyMarkerTypes.Any())
@@ -86,20 +87,16 @@ internal static int Run(IDictionary<string, string> namedArgs)
8687
}
8788

8889
// 4) Serialize to specified output location or stdout
89-
var outputPath = namedArgs.TryGetValue(OutputOption, out var arg1) ? Path.Combine(Directory.GetCurrentDirectory(), arg1) : null;
90+
var outputPath = (namedArgs.TryGetValue(OutputOption, out var path) && !string.IsNullOrWhiteSpace(path)) ? Path.Combine(Directory.GetCurrentDirectory(), path) : null;
9091
if (!string.IsNullOrEmpty(outputPath))
9192
{
92-
var directoryPath = Path.GetDirectoryName(outputPath);
93-
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
94-
{
95-
Directory.CreateDirectory(directoryPath);
96-
}
93+
Directory.CreateDirectory(outputPath);
9794
}
9895

9996
var exportJson = true;
10097
var exportYml = false;
10198
var exportYaml = false;
102-
if (namedArgs.TryGetValue(FormatOption, out var format))
99+
if (namedArgs.TryGetValue(FormatOption, out var format) && !string.IsNullOrWhiteSpace(format))
103100
{
104101
var splitted = format.Split(',').Select(x => x.Trim()).ToList();
105102
exportJson = splitted.Any(x => x.Equals("json", StringComparison.OrdinalIgnoreCase));

src/AsyncAPI.Saunter.Generator.Cli/SwashbuckleImport/CommandRunner.cs

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public int Run(IEnumerable<string> args)
5656

5757
if (_subRunners.Any() || !TryParseArgs(args, out IDictionary<string, string> namedArgs))
5858
{
59+
_output.WriteLine($"Input: {string.Join(' ', args)}");
5960
PrintUsage();
6061
return 1;
6162
}

0 commit comments

Comments
 (0)