Skip to content

Commit

Permalink
asyncapi#207 generate code from spec: yml support + generate extensio…
Browse files Browse the repository at this point in the history
…n for AsyncApi document info
  • Loading branch information
Senn Geerts authored and Senn Geerts committed Jul 20, 2024
1 parent 23b8e4c commit 8f6dd34
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 58 deletions.
26 changes: 2 additions & 24 deletions examples/StreetlightsAPI.AsyncApiSpecFirst/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using NLog;
using NLog.Web;
using Saunter;
using Saunter.AsyncApiSchema.v2;
using Saunter.Streetlights.Api;

Check failure on line 11 in examples/StreetlightsAPI.AsyncApiSpecFirst/Program.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Streetlights' does not exist in the namespace 'Saunter' (are you missing an assembly reference?)

Check failure on line 11 in examples/StreetlightsAPI.AsyncApiSpecFirst/Program.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'Streetlights' does not exist in the namespace 'Saunter' (are you missing an assembly reference?)
using StreetlightsAPI;

LogManager.Setup().LoadConfigurationFromAppSettings();
Expand All @@ -18,29 +18,7 @@
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.AddAsyncApiStreetlights();

builder.Services.AddScoped<IStreetlightMessageBus, StreetlightMessageBus>();
builder.Services.AddControllers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<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.json" OutputPath="generated2" Namespace="SaunterX" />-->
<AsyncAPISpecs Include="specs/streetlights.yml" OutputPath="generated2" Namespace="SaunterX" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down Expand Up @@ -52,6 +52,8 @@
<None Include="../StreetlightsAPI/nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

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

<ItemGroup>
Expand All @@ -65,5 +67,9 @@
<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 @@ -52,6 +52,9 @@
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

<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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal interface IAsyncApiGenerator
{
string GenerateAsyncApiInterfaces(GeneratorOptions options, string text, AsyncApiState state);

string GenerateAsyncApiInterfaces(GeneratorOptions options, AsyncApiDocument asyncApi, AsyncApiState state);
string GenerateAsyncApiInterfaces(GeneratorOptions options, AsyncApiDocument asyncApiDocument, AsyncApiState state);
}

internal class AsyncApiGenerator : IAsyncApiGenerator
Expand All @@ -30,24 +30,84 @@ public AsyncApiGenerator()

public string GenerateAsyncApiInterfaces(GeneratorOptions options, string text, AsyncApiState state)
{
var asyncApi = new AsyncApiStringReader().Read(text, out var diagnostic);
return this.GenerateAsyncApiInterfaces(options, asyncApi, state);
var asyncApiDocument = new AsyncApiStringReader().Read(text, out var diagnostic);
return this.GenerateAsyncApiInterfaces(options, asyncApiDocument, state);
}

public string GenerateAsyncApiInterfaces(GeneratorOptions options, AsyncApiDocument asyncApi, AsyncApiState state)
public string GenerateAsyncApiInterfaces(GeneratorOptions options, AsyncApiDocument asyncApiDocument, AsyncApiState state)
{
var sb = CreateHeader(options);

this.AddDocumentExtension(sb, options, asyncApiDocument);

// Commands
this.AddInterface(sb, options, options.ClassName, $"{options.ClassName}Commands", new("SubscribeOperation", "command", x => x.Subscribe), asyncApiDocument.Channels.Where(x => x.Value.Subscribe != null));
sb.AppendLine();

// Events
this.AddInterface(sb, options, options.ClassName, $"{options.ClassName}Events", new("PublishOperation", "evt", x => x.Publish), asyncApiDocument.Channels.Where(x => x.Value.Publish != null));

sb.AppendLine("}"); // close namespace
sb.AppendLine();

state.Documents.Add(options.ClassName);
var contents = sb.ToString();
return contents;
}

public void AddDocumentExtension(StringBuilder sb, GeneratorOptions options, AsyncApiDocument asyncApiDocument)
{
var extensionsClassName = $"{options.ClassName}AsyncApiDocumentExtensions";
sb.AppendLine(this.GetGeneratedCodeAttributeLine());

// TODO: AsyncApiDocument will just become the LEGO AsyncApiDocument at some point.
sb.AppendLine(
$$"""
public static class {{extensionsClassName}}
{
public static IServiceCollection AddAsyncApi{{options.ClassName}}(this IServiceCollection services)
{
services.AddAsyncApiSchemaGeneration(options =>
{
options.AssemblyMarkerTypes = [typeof({{extensionsClassName}})];
options.Middleware.UiTitle = "{{asyncApiDocument.Info.Title}}";
options.AsyncApi = new AsyncApiDocument
{
Info = new Info("{{asyncApiDocument.Info.Title}}", "{{asyncApiDocument.Info.Version}}")
{
Description = "{{asyncApiDocument.Info.Description}}",
License = new License("{{asyncApiDocument.Info.License.Name}}")
{
Url = "{{asyncApiDocument.Info.License.Url}}"
}
},
};
});
return services;
}
}

""");
}

private StringBuilder CreateHeader(GeneratorOptions options)
{
var sb = new StringBuilder(
$$"""
//----------------------
// <auto-generated>
// Generated using {{this._name}} v{{this._version}}
// At: {{DateTime.Now:U}}
// At: {{DateTime.Now.ToLocalTime():U}}
// </auto-generated>
//----------------------

using Saunter.Attributes;
using Saunter.AsyncApiSchema.v2;
using System.CodeDom.Compiler;

using Microsoft.Extensions.DependencyInjection;

namespace {{options.Namespace}}
{
{{this.GetGeneratedCodeAttributeLine()}}
Expand All @@ -59,20 +119,7 @@ public static partial class {{options.TopicsClassName}}
sb.AppendLine($" public const string {MakeGlobalDocumentTopic(options.ClassName)} = \"{options.ClassName.ToPascalCase()}\";");
sb.AppendLine(" }");
sb.AppendLine();

// Commands
this.AddInterface(sb, options, options.ClassName, $"{options.ClassName}Commands", new("SubscribeOperation", "command", x => x.Subscribe), asyncApi.Channels.Where(x => x.Value.Subscribe != null));
sb.AppendLine();

// Events
this.AddInterface(sb, options, options.ClassName, $"{options.ClassName}Events", new("PublishOperation", "evt", x => x.Publish), asyncApi.Channels.Where(x => x.Value.Publish != null));

sb.AppendLine("}"); // close namespace
sb.AppendLine();

state.Documents.Add(options.ClassName);
var contents = sb.ToString();
return contents;
return sb;
}

private string GetGeneratedCodeAttributeLine() => $" [GeneratedCodeAttribute(\"{this._name}\", \"{this._version}\")]";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
using YamlDotNet.RepresentationModel;
using Yaml2JsonNode;

namespace AsyncAPI.Saunter.Generator.Cli.FromSpec.DataTypes;

internal static class OpenApiCompatibility
{
internal static string PrepareSpecFile(string spec)
{
var json = (JObject)JsonConvert.DeserializeObject(spec);
// the type is important for NSwag
if (!json.ContainsKey("openapi"))
{
json.Add("openapi", "3.0.1");
}
// NSwag doesn't understand the servers format of AsyncApi, and it is not needed anyway.
if (json.ContainsKey("servers"))
Debugger.Launch();
var reader = new StringReader(spec);
var yamlStream = new YamlStream();
yamlStream.Load(reader);

if (yamlStream.Documents[0].ToJsonNode() is JsonObject json)
{
json.Remove("servers");
// the type is important for NSwag
if (!json.ContainsKey("openapi"))
{
json.Add("openapi", "3.0.1");
}

//// NSwag doesn't understand the servers format of AsyncApi, not needed.
if (json.ContainsKey("servers"))
{
json.Remove("servers");
}
return JsonSerializer.Serialize(json);
}
return JsonConvert.SerializeObject(json);

return spec;
}
}

0 comments on commit 8f6dd34

Please sign in to comment.