Skip to content

Commit

Permalink
asyncapi#207 [FEATURE] Generate classes based on AsyncAPI document
Browse files Browse the repository at this point in the history
  • Loading branch information
SennG committed Jul 14, 2024
1 parent 68e06ca commit 5ed4c20
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 10 deletions.
15 changes: 15 additions & 0 deletions Saunter.sln
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.Build.Tests", "test\AsyncAPI.Saunter.Generator.Build.Tests\AsyncAPI.Saunter.Generator.Build.Tests.csproj", "{61142B10-7B49-436E-AE32-2737658BD1E5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreetlightsAPI.AsyncApiSpecFirst", "examples\StreetlightsAPI.AsyncApiSpecFirst\StreetlightsAPI.AsyncApiSpecFirst.csproj", "{19A30A6D-1E91-44FD-BB5D-428D12D0160D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -196,6 +198,18 @@ Global
{61142B10-7B49-436E-AE32-2737658BD1E5}.Release|x64.Build.0 = Release|Any CPU
{61142B10-7B49-436E-AE32-2737658BD1E5}.Release|x86.ActiveCfg = Release|Any CPU
{61142B10-7B49-436E-AE32-2737658BD1E5}.Release|x86.Build.0 = Release|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Debug|x64.ActiveCfg = Debug|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Debug|x64.Build.0 = Debug|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Debug|x86.ActiveCfg = Debug|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Debug|x86.Build.0 = Debug|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Release|Any CPU.Build.0 = Release|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Release|x64.ActiveCfg = Release|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Release|x64.Build.0 = Release|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Release|x86.ActiveCfg = Release|Any CPU
{19A30A6D-1E91-44FD-BB5D-428D12D0160D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -213,6 +227,7 @@ Global
{6F6B8B03-9045-46EC-AE12-E7ADA492F9FA} = {6ABD4842-47AF-49A5-B057-0EBA64416789}
{A320E670-5CB0-4815-AF67-D8D09FC92A2A} = {28D4C365-FDED-49AE-A97D-36202E24A55A}
{61142B10-7B49-436E-AE32-2737658BD1E5} = {6491E321-2D02-44AB-9116-D722FE169595}
{19A30A6D-1E91-44FD-BB5D-428D12D0160D} = {6ABD4842-47AF-49A5-B057-0EBA64416789}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2F85D9DA-DBCF-4F13-8C42-5719F1469B2E}
Expand Down
79 changes: 79 additions & 0 deletions examples/StreetlightsAPI.AsyncApiSpecFirst/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NLog;
using NLog.Web;
using Saunter;
using Saunter.AsyncApiSchema.v2;
using StreetlightsAPI;

LogManager.Setup().LoadConfigurationFromAppSettings();

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddSimpleConsole(console => console.SingleLine = true);
builder.Host.UseNLog();

// Add Saunter to the application services.
builder.Services.AddAsyncApiSchemaGeneration(options =>
{
options.AssemblyMarkerTypes = [typeof(StreetlightMessageBus)];
options.Middleware.UiTitle = "Streetlights API";
options.AsyncApi = new AsyncApiDocument
{
Info = new Info("Streetlights API", "1.0.0")
{
Description = "The Smartylighting Streetlights API allows you to remotely manage the city lights.",
License = new License("Apache 2.0")
{
Url = "https://www.apache.org/licenses/LICENSE-2.0"
}
},
Servers =
{
["mosquitto"] = new Server("test.mosquitto.org", "mqtt"),
["webapi"] = new Server("localhost:5000", "http"),
},
};
});

builder.Services.AddScoped<IStreetlightMessageBus, StreetlightMessageBus>();
builder.Services.AddControllers();

var app = builder.Build();

app.UseDeveloperExceptionPage();

app.UseRouting();
app.UseCors(configure => configure.AllowAnyOrigin().AllowAnyMethod());

// to be fixed with issue #173
#pragma warning disable ASP0014 // Suggest using top level route registrations instead of UseEndpoints
app.UseEndpoints(endpoints =>
{
endpoints.MapAsyncApiDocuments();
endpoints.MapAsyncApiUi();
endpoints.MapControllers();
});
#pragma warning restore ASP0014 // Suggest using top level route registrations instead of UseEndpoints

await app.StartAsync();

// Print the AsyncAPI doc location
var logger = app.Services.GetService<ILoggerFactory>().CreateLogger<Program>();
var options = app.Services.GetService<IOptions<AsyncApiOptions>>();
var addresses = app.Urls;
logger.LogInformation("AsyncAPI doc available at: {URL}", $"{addresses.FirstOrDefault()}{options.Value.Middleware.Route}");
logger.LogInformation("AsyncAPI UI available at: {URL}", $"{addresses.FirstOrDefault()}{options.Value.Middleware.UiBaseRoute}");

// Redirect base url to AsyncAPI UI
app.Map("/", () => Results.Redirect("index.html"));
app.Map("/index.html", () => Results.Redirect(options.Value.Middleware.UiBaseRoute));

await app.WaitForShutdownAsync();
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<!-- This project is targeting .NET8 intentionally (The 'old school' Startup-class project is .NET6), to prove that
the AsyncAPI.Saunter.Generator.Cli tool can generate specs for projects targetting .NET6 and .NET8. -->
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>

<!-- Example settings for "AsyncAPI.Saunter.Generator.Build", they are all option though -->
<AsyncAPIGenerateDocumentsOnBuild>false</AsyncAPIGenerateDocumentsOnBuild>
</PropertyGroup>

<ItemGroup>
<AsyncAPISpecs Include="specs/streetlights.yml" Namespace="Saunter" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DocumentationFile>bin\Debug\StreetlightsAPI.TopLevelStatement.xml</DocumentationFile>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\Release\StreetlightsAPI.TopLevelStatement.xml</DocumentationFile>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>

<!-- Get the latest local build nuget package from source code, from the local nuget packages source as configured in nuget.config
999.* are the 'special' version number for local builds -->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="AsyncAPI.Saunter.Generator.Build" Version="999.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- For release: use a real published nuget package -->
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
<PackageReference Include="AsyncAPI.Saunter.Generator.Build" Version="*-*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Saunter\Saunter.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="specs\" />

<Compile Include="../StreetlightsAPI/API.cs" />
<Compile Include="../StreetlightsAPI/Messaging.cs" />

<None Include="../StreetlightsAPI/nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="NLog" Version="5.3.2" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.11" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions examples/StreetlightsAPI.AsyncApiSpecFirst/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},

"AllowedHosts": "*",

"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://localhost:5001"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
<AsyncAPIDocumentEnvVars Condition=" '$(AsyncAPIDocumentEnvVars)' == '' "></AsyncAPIDocumentEnvVars>
</PropertyGroup>

<ItemGroup>
<AsyncAPISpecs Include="asyncapi.json" />
<AsyncAPIOutput Include="generated/*" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition=" '$(AsyncAPIGenerateDocumentsOnBuild)' == 'true' ">
<!-- Calculate some paths -->
<Target Name="AsyncAPICommonBuildProperties">
<!-- Calculate some common paths -->
<PropertyGroup>
<AsyncAPIBuildToolBuildDir>$([System.IO.Path]::GetDirectoryName($(MSBuildThisFileDirectory)))</AsyncAPIBuildToolBuildDir>
<AsyncAPIBuildToolRoot>$([System.IO.Path]::GetDirectoryName($(AsyncAPIBuildToolBuildDir)))</AsyncAPIBuildToolRoot>
<AsyncAPICliToolPath>$([System.IO.Path]::Combine($(AsyncAPIBuildToolRoot), tools, net8.0, AsyncAPI.Saunter.Generator.Cli.dll))</AsyncAPICliToolPath>
</PropertyGroup>

<Message Text="AsyncAPI.Generator.Build; AsyncAPICliToolPath: $(AsyncAPICliToolPath)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPIBuildToolBuildDir: $(AsyncAPIBuildToolBuildDir)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPIBuildToolRoot: $(AsyncAPIBuildToolRoot)" />
</Target>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" DependsOnTargets="AsyncAPICommonBuildProperties"
Condition=" '$(AsyncAPIGenerateDocumentsOnBuild)' == 'true' ">
<PropertyGroup>
<AsyncAPICliToolStartupAssembly>$([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(OutputPath), $(AssemblyTitle).dll))</AsyncAPICliToolStartupAssembly>
<AsyncAPICliToolOutputPath>$([System.IO.Path]::Combine($(MSBuildProjectDirectory), $(AsyncAPIDocumentOutputPath)))</AsyncAPICliToolOutputPath>
</PropertyGroup>

<!-- Debug output: print some paths, set -v flag (verbosity) at least to [n]ormal to show in build output -->
<Message Text="AsyncAPI.Generator.Build; AsyncAPICliToolPath: $(AsyncAPICliToolPath)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPICliToolStartupAssembly: $(AsyncAPICliToolStartupAssembly)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPICliToolOutputPath: $(AsyncAPICliToolOutputPath)" />

Expand All @@ -21,8 +30,6 @@
<Message Text="AsyncAPI.Generator.Build; AsyncAPIDocumentNames: $(AsyncAPIDocumentNames)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPIDocumentFilename: $(AsyncAPIDocumentFilename)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPIDocumentEnvVars: $(AsyncAPIDocumentEnvVars)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPIBuildToolBuildDir: $(AsyncAPIBuildToolBuildDir)" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPIBuildToolRoot: $(AsyncAPIBuildToolRoot)" />
<Message Text="AsyncAPI.Generator.Build; MSBuildThisFile: $(MSBuildThisFile)" />
<Message Text="AsyncAPI.Generator.Build; MSBuildThisFileDirectory: $(MSBuildThisFileDirectory)" />
<Message Text="AsyncAPI.Generator.Build; MSBuildProjectFullPath: $(MSBuildProjectFullPath)" />
Expand All @@ -32,4 +39,20 @@
WorkingDirectory="$(AsyncAPIBuildToolRoot)" />
</Target>

<ItemGroup Condition=" '$(AsyncAPIOutput)' == '' ">
<AsyncAPIOutput Include="g" />
</ItemGroup>

<Target Name="GenerateCodeForAsyncAPI" BeforeTargets="BeforeBuild" DependsOnTargets="AsyncAPICommonBuildProperties"
Inputs="@(AsyncAPISpecs)" Outputs="@(AsyncAPIOutput)">
<!-- https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata -->
<Message Text="Generate: --specs @(AsyncAPISpecs->'&quot;%(Namespace),%(Identity)&quot;', ' ')" />

<Exec Command="dotnet &quot;$(AsyncAPICliToolPath)&quot; fromspec --specs @(AsyncAPISpecs->'&quot;%(Namespace),%(Identity)&quot;', ' ')"
WorkingDirectory="$(AsyncAPIBuildToolRoot)" />
</Target>

<Target Name="BeforeBuild" DependsOnTargets="GenerateCodeForAsyncAPI">
</Target>

</Project>
20 changes: 20 additions & 0 deletions src/AsyncAPI.Saunter.Generator.Cli/FromSpec/FromSpecCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using ConsoleAppFramework;
using LEGO.AsyncAPI;
using LEGO.AsyncAPI.Models;
using Microsoft.Extensions.Logging;

namespace AsyncAPI.Saunter.Generator.Cli.FromSpecCommand;

internal class FromSpecCommand(ILogger<FromSpecCommand> logger)
{
/// <summary>
/// Retrieves AsyncAPI spec from a startup assembly and writes to file.
/// </summary>
/// <param name="specs">The AsyncAPI specification to generate code for. Parameter should include the namespace: namespace,asyncapi.json</param>
[Command("fromspec")]
public int FromSpec(params string[] specs)
{
logger.LogInformation($"FromSpec(#{specs.Length}): {string.Join(';', specs)}");
return 0;
}
}
5 changes: 4 additions & 1 deletion src/AsyncAPI.Saunter.Generator.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AsyncAPI.Saunter.Generator.Cli.ToFile;
using AsyncAPI.Saunter.Generator.Cli.FromSpecCommand;
using AsyncAPI.Saunter.Generator.Cli.ToFile;
using ConsoleAppFramework;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -11,9 +12,11 @@
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
ConsoleApp.LogError = msg => logger.LogError(msg);
ConsoleApp.ServiceProvider = serviceProvider;
logger.LogDebug($"Generator.Cli args: {string.Join(' ', args)}");

var app = ConsoleApp.Create();
app.Add<ToFileCommand>();
app.Add<FromSpecCommand>();
app.Run(args);

Environment.ExitCode = 0;
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ namespace AsyncAPI.Saunter.Generator.Cli.Tests.ToFile;

public class ToFileCommandTests
{
private readonly ToFileCommand _command;
private readonly FromSpecCommand _command;
private readonly IEnvironmentBuilder _environment;
private readonly IServiceProviderBuilder _builder;
private readonly IAsyncApiDocumentExtractor _docExtractor;
private readonly IFileWriter _fileWriter;
private readonly ILogger<ToFileCommand> _logger;
private readonly ILogger<FromSpecCommand> _logger;
private readonly ITestOutputHelper _output;

public ToFileCommandTests(ITestOutputHelper output)
{
this._output = output;
this._logger = Substitute.For<ILogger<ToFileCommand>>();
this._logger = Substitute.For<ILogger<FromSpecCommand>>();
this._environment = Substitute.For<IEnvironmentBuilder>();
this._builder = Substitute.For<IServiceProviderBuilder>();
this._docExtractor = Substitute.For<IAsyncApiDocumentExtractor>();
this._fileWriter = Substitute.For<IFileWriter>();
this._command = new ToFileCommand(this._logger, _environment, _builder, _docExtractor, _fileWriter);
this._command = new FromSpecCommand(this._logger, _environment, _builder, _docExtractor, _fileWriter);
}

[Fact]
Expand Down

0 comments on commit 5ed4c20

Please sign in to comment.