Skip to content

Commit

Permalink
asyncapi#207 generate code from spec: SourceGenerator implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Senn Geerts authored and Senn Geerts committed Jul 20, 2024
1 parent 8f6dd34 commit aed2466
Show file tree
Hide file tree
Showing 28 changed files with 381 additions and 140 deletions.
30 changes: 30 additions & 0 deletions Saunter.sln
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreetlightsAPI.AsyncApiSpe
{A320E670-5CB0-4815-AF67-D8D09FC92A2A} = {A320E670-5CB0-4815-AF67-D8D09FC92A2A}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator.SourceGenerator", "src\AsyncAPI.Saunter.Generator.SourceGenerator\AsyncAPI.Saunter.Generator.SourceGenerator.csproj", "{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncAPI.Saunter.Generator", "src\AsyncAPI.Saunter.Generator\AsyncAPI.Saunter.Generator.csproj", "{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -216,6 +220,30 @@ Global
{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
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Debug|x64.ActiveCfg = Debug|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Debug|x64.Build.0 = Debug|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Debug|x86.ActiveCfg = Debug|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Debug|x86.Build.0 = Debug|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Release|Any CPU.Build.0 = Release|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Release|x64.ActiveCfg = Release|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Release|x64.Build.0 = Release|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Release|x86.ActiveCfg = Release|Any CPU
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0}.Release|x86.Build.0 = Release|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Debug|x64.ActiveCfg = Debug|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Debug|x64.Build.0 = Debug|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Debug|x86.ActiveCfg = Debug|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Debug|x86.Build.0 = Debug|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Release|Any CPU.Build.0 = Release|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Release|x64.ActiveCfg = Release|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Release|x64.Build.0 = Release|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Release|x86.ActiveCfg = Release|Any CPU
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -234,6 +262,8 @@ Global
{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}
{6ADD07FE-B6D1-42A9-A618-E7AA102E38A0} = {28D4C365-FDED-49AE-A97D-36202E24A55A}
{47763E73-0B9B-4B11-BB03-5FDB0B8A0C5E} = {28D4C365-FDED-49AE-A97D-36202E24A55A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2F85D9DA-DBCF-4F13-8C42-5719F1469B2E}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>

<!-- Disable code first specifiction generation -->
<AsyncAPIGenerateDocumentsOnBuild>false</AsyncAPIGenerateDocumentsOnBuild>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

<ItemGroup>
<!-- Instruct "AsyncAPI.Saunter.Generator.Build" to generate classes for this AsyncAPI specifiction -->
<AsyncAPISpecs Include="specs/streetlights.json" OutputPath="generated" Namespace="Saunter" />
<AsyncAPISpecs Include="specs/streetlights.yml" OutputPath="generated2" Namespace="SaunterX" />
<!-- Instruct "AsyncAPI.Saunter.Generator.SourceGenerator" to generate classes for this AsyncAPI specifiction -->
<AdditionalFiles Include="specs/streetlights.json" Namespace="Saunter" />
<AdditionalFiles Include="specs/streetlights.yml" Namespace="SaunterX" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand All @@ -27,15 +26,20 @@
<!-- 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.*">
<!-- <ProjectReference Include="..\..\src\AsyncAPI.Saunter.Generator.SourceGenerator\AsyncAPI.Saunter.Generator.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
-->
</ItemGroup>

<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="AsyncAPI.Saunter.Generator.SourceGenerator" 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="*-*">
<PackageReference Include="AsyncAPI.Saunter.Generator.SourceGenerator" Version="*-*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand All @@ -52,24 +56,11 @@
<None Include="../StreetlightsAPI/nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

<None Include="specs\streetlights.json" />
</ItemGroup>

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

<ItemGroup>
<Folder Include="generated/" />
<Folder Include="generated2/" />
<Compile Remove="generated2/*" />
<None Include="generated2/*" />
</ItemGroup>

<ItemGroup>
<Content Remove="specs\streetlights.json" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,4 @@
<AsyncAPIDocumentEnvVars Condition=" '$(AsyncAPIDocumentEnvVars)' == '' "></AsyncAPIDocumentEnvVars>
</PropertyGroup>

<ItemGroup>
<!-- Example how to specify .json or .yml AsyncApi spec files -->
<!-- <AsyncAPISpecs Include="asyncapi.json" OutputPath="generated" Namespace="Saunter.Generated" /> -->
</ItemGroup>

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

<Target Name="AsyncAPIInitial">
<Message Text="===== AsyncAPI 999.$([System.DateTime]::Now.ToString(&quot;yy&quot;))$([System.DateTime]::Now.DayOfYear).$([System.DateTime]::Now.ToString(&quot;HHmm.ss&quot;)) ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI ===== AsyncAPI =====" Importance="High" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPISpecs : @(AsyncAPISpecs->'%(DefiningProjectDirectory)%(Identity)')" Importance="High" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPISpecs.Outputs : @(AsyncAPISpecs->'%(DefiningProjectDirectory)%(OutputPath)/%(Filename).g.cs')" Importance="High" />
</Target>
<Project>

<Target Name="AsyncAPICommonBuildProperties">
<!-- Calculate some common paths -->
Expand All @@ -15,14 +9,14 @@
<AsyncAPICliToolPath>$([System.IO.Path]::Combine($(AsyncAPIBuildToolRoot), tools, net8.0, AsyncAPI.Saunter.Generator.Cli.dll))</AsyncAPICliToolPath>
</PropertyGroup>

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

<Message Text="AsyncAPI.Generator.Build; MSBuildThisFile : $(MSBuildThisFile)" Importance="High" />
<Message Text="AsyncAPI.Generator.Build; MSBuildThisFileDirectory : $(MSBuildThisFileDirectory)" Importance="High" />
<Message Text="AsyncAPI.Generator.Build; MSBuildProjectFullPath : $(MSBuildProjectFullPath)" Importance="High" />
<Message Text="AsyncAPI.Generator.Build; MSBuildProjectDirectory : $(MSBuildProjectDirectory)" Importance="High" />
<Message Text="AsyncAPI.Generator.Build; MSBuildThisFile : $(MSBuildThisFile)" />
<Message Text="AsyncAPI.Generator.Build; MSBuildThisFileDirectory : $(MSBuildThisFileDirectory)" />
<Message Text="AsyncAPI.Generator.Build; MSBuildProjectFullPath : $(MSBuildProjectFullPath)" />
<Message Text="AsyncAPI.Generator.Build; MSBuildProjectDirectory : $(MSBuildProjectDirectory)" />
</Target>

<!-- AsyncAPIGenerateDocumentsOnBuild: Generate .json/.yml spec at build time from code-first attributes -->
Expand All @@ -47,19 +41,4 @@
WorkingDirectory="$(AsyncAPIBuildToolRoot)" />
</Target>

<!-- AsyncAPISpecs: Generate dataclasses from .json/.yml AsyncAPI spec files -->
<Target Name="GenerateCodeForAsyncAPI" BeforeTargets="BeforeBuild" DependsOnTargets="AsyncAPICommonBuildProperties"
Condition=" '@(AsyncAPISpecs)' != '' "
Inputs="@(AsyncAPISpecs)" Outputs="@(AsyncAPISpecs->'%(DefiningProjectDirectory)%(OutputPath)/%(Filename).g.cs')">
<!-- https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-well-known-item-metadata -->
<Message Text="AsyncAPI.Generator.Build; AsyncAPISpecs 2 : @(AsyncAPISpecs->'%(DefiningProjectDirectory)%(Identity)')" Importance="High" />
<Message Text="AsyncAPI.Generator.Build; AsyncAPISpecs.specs 2 : @(AsyncAPISpecs->'&quot;%(Namespace),%(DefiningProjectDirectory)%(OutputPath),%(DefiningProjectDirectory)%(Identity)&quot;', ' ')" Importance="High" />

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

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

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@

<ItemGroup>
<PackageReference Include="AsyncAPI.NET.Readers" Version="5.2.1" />
<PackageReference Include="Yaml2JsonNode " Version="2.1.0" />
<PackageReference Include="CaseConverter" Version="2.0.1" />
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="14.0.7" />
<PackageReference Include="ConsoleAppFramework" Version="5.2.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -52,6 +49,7 @@
</ItemGroup>

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

Expand Down
58 changes: 10 additions & 48 deletions src/AsyncAPI.Saunter.Generator.Cli/FromSpec/FromSpecCommand.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System.Diagnostics;
using System.Text;
using AsyncAPI.Saunter.Generator.Cli.FromSpec.AsyncApiInterface;
using AsyncAPI.Saunter.Generator.Cli.FromSpec.DataTypes;
using CaseConverter;
using System.Text;
using AsyncAPI.Saunter.Generator.FromSpec;
using ConsoleAppFramework;
using Microsoft.Extensions.Logging;

namespace AsyncAPI.Saunter.Generator.Cli.FromSpec;

internal class FromSpecCommand(ILogger<FromSpecCommand> logger, IAsyncApiGenerator asyncApiGenerator, IDataTypesGenerator dataTypesGenerator)
internal class FromSpecCommand(ILogger<FromSpecCommand> logger, IAsyncApiCodeGenerator codeGenerator)
{
/// <summary>
/// Retrieves AsyncAPI spec from a startup assembly and writes to file.
Expand All @@ -20,58 +17,23 @@ public async Task<int> FromSpec(params string[] specs)
logger.LogInformation($"FromSpec(#{specs.Length}): --specs {string.Join(';', specs)}");

var specsToGenerate = Split(specs);
var output = specsToGenerate.ToDictionary(x => x, _ => new StringBuilder());

// Common
var topicsClassName = "Topics";
foreach (var (spec, _) in output)
var output = await codeGenerator.FromSpecs(specsToGenerate).ConfigureAwait(false);

// Write to file
foreach (var (spec, contents) in output)
{
Directory.CreateDirectory(spec.OutputDirectory);

if (!File.Exists(spec.SpecFile))
{
throw new ArgumentException($"Provided spec does not exist: {Path.GetFullPath(spec.SpecFile)}.");
}
}

// AsyncAPI Interface
var aaState = new AsyncApiState();
foreach (var (spec, sb) in output)
{
var options = new GeneratorOptions($"{spec.NamespaceName}.{spec.SpecName}.Api", spec.SpecName, $"{topicsClassName}");
var contents = asyncApiGenerator.GenerateAsyncApiInterfaces(options, spec.Contents, aaState);
sb.Append(contents);
}

// DataTypes
var nsState = new DataTypesGeneratorState();
foreach (var (spec, sb) in output)
{
var options = new GeneratorOptions($"{spec.NamespaceName}.{spec.SpecName}.Api", spec.SpecName, $"{topicsClassName}");
var contents = await dataTypesGenerator.GenerateDataTypesAsync(options, spec.Contents, nsState).ConfigureAwait(false);
sb.Append(contents);
}

// Write to file
foreach (var (spec, sb) in output)
{
var contents = sb.ToString();
var outputFile = spec.OutputFileName;
await File.WriteAllTextAsync(outputFile, contents, Encoding.UTF8);
await File.WriteAllTextAsync(outputFile, contents, Encoding.UTF8).ConfigureAwait(false);
logger.LogInformation($"Created {outputFile} (size: {contents.Length:N0} chars)");
}

return 0;
}

private record SpecToGenerate(string NamespaceName, string OutputDirectory, string SpecFile)
private record SpecToGenerate(string NamespaceName, string OutputDirectory, string SpecFilePath) : Generator.FromSpec.SpecToGenerate(NamespaceName, SpecFilePath)
{
private string _contents;

public string Contents => this._contents ??= File.ReadAllText(this.SpecFile);

public string SpecName => Path.GetFileNameWithoutExtension(this.SpecFile).ToPascalCase();

public string OutputFileName => Path.GetFullPath(Path.Combine(this.OutputDirectory, $"{this.SpecName}.g.cs"));
}

Expand All @@ -82,7 +44,7 @@ private static IEnumerable<SpecToGenerate> Split(IEnumerable<string> input)
var split = spec.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList();
if (split.Count == 3 && !split.Any(string.IsNullOrWhiteSpace))
{
yield return new SpecToGenerate(NamespaceName: split[0], OutputDirectory: split[1], SpecFile: split[2]);
yield return new SpecToGenerate(NamespaceName: split[0], OutputDirectory: split[1], SpecFilePath: split[2]);
}
}
}
Expand Down
15 changes: 0 additions & 15 deletions src/AsyncAPI.Saunter.Generator.Cli/FromSpec/ServiceExtensions.cs

This file was deleted.

9 changes: 5 additions & 4 deletions src/AsyncAPI.Saunter.Generator.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using AsyncAPI.Saunter.Generator.Cli.FromSpec;
using AsyncAPI.Saunter.Generator;
using AsyncAPI.Saunter.Generator.Cli.FromSpec;
using AsyncAPI.Saunter.Generator.Cli.ToFile;
using AsyncAPI.Saunter.Generator.FromSpec;
using ConsoleAppFramework;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

var services = new ServiceCollection();
services.AddLogging(builder => builder.AddSimpleConsole(x => x.SingleLine = true).SetMinimumLevel(LogLevel.Trace));
var services = GeneratorServiceCollection.Create();
services.AddToFileCommand();
services.AddFromSpecCommand();
services.AddFromSpecCodeGenerator();

using var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Release 1.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
AA0001 | SourceGenerator | Error | AA001
Loading

0 comments on commit aed2466

Please sign in to comment.