Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
coronabytes committed Oct 19, 2024
1 parent 040467d commit 1ac26e3
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 80 deletions.
4 changes: 2 additions & 2 deletions Core.ServiceMesh.Abstractions/ServiceMeshAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
namespace Core.ServiceMesh.Abstractions;

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public class ServiceMeshAttribute(string name) : Attribute
public class ServiceMeshAttribute(string? name = null) : Attribute
{
/// <summary>
/// Unique service name.
/// May also include versioning information
/// e.g. SampleServiceV2
/// </summary>
public string Name => name;
public string Name => name ?? string.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using Core.ServiceMesh.Abstractions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Core.ServiceMesh.SourceGen.Tests;

public class UnitTest1
public class SourceGenTest
{
[Fact]
public Task ProxyInterface()
Expand All @@ -29,7 +25,7 @@ public interface ISomeService
IAsyncEnumerable<T> E<T>(T e) where T : INumber<T>;
}

[ServiceMesh("someservice")]
[ServiceMesh]
public class SomeService : ISomeService
{
public async ValueTask A(string a)
Expand Down Expand Up @@ -69,47 +65,4 @@ public async IAsyncEnumerable<T> E<T>(T e) where T : INumber<T>

return TestHelper.VerifySourceGen(source);
}
}

public static class TestHelper
{
public static async Task VerifySourceGen(string source)
{
var syntaxTree = CSharpSyntaxTree.ParseText(source);

// force reference
typeof(ServiceMeshAttribute).ToString();

var references = AppDomain
.CurrentDomain.GetAssemblies()
.Where(assembly => !assembly.IsDynamic)
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
.Cast<MetadataReference>();

var compilation = CSharpCompilation.Create(
"SourceGeneratorTests",
[syntaxTree],
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);

var sourceDiagnostics = compilation.GetDiagnostics();
var errors = sourceDiagnostics
.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();

Assert.Empty(errors);

var generator = new ServiceMeshGenerator();

var driver = CSharpGeneratorDriver.Create(generator)
.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _);

foreach (var tree in outputCompilation.SyntaxTrees.Skip(1))
{
var genSource = tree.ToString();
var genErrors = tree.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error);

Assert.Empty(genErrors);
}
}
}
48 changes: 48 additions & 0 deletions Core.ServiceMesh.SourceGen.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Core.ServiceMesh.Abstractions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Core.ServiceMesh.SourceGen.Tests;

public static class TestHelper
{
public static async Task VerifySourceGen(string source)
{
var syntaxTree = CSharpSyntaxTree.ParseText(source);

// force reference
typeof(ServiceMeshAttribute).ToString();

var references = AppDomain
.CurrentDomain.GetAssemblies()
.Where(assembly => !assembly.IsDynamic)
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
.Cast<MetadataReference>();

var compilation = CSharpCompilation.Create(
"SourceGeneratorTests",
[syntaxTree],
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);

var sourceDiagnostics = compilation.GetDiagnostics();
var errors = sourceDiagnostics
.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();

Assert.Empty(errors);

var generator = new ServiceMeshGenerator();

var driver = CSharpGeneratorDriver.Create(generator)
.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _);

foreach (var tree in outputCompilation.SyntaxTrees.Skip(1))
{
var genSource = tree.ToString();
var genErrors = tree.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error);

Assert.Empty(genErrors);
}
}
}
5 changes: 2 additions & 3 deletions Core.ServiceMesh.SourceGen/Core.ServiceMesh.SourceGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true"
PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>

</Project>
4 changes: 3 additions & 1 deletion Core.ServiceMesh.SourceGen/Model/ServiceDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ namespace Core.ServiceMesh.SourceGen.Model;
internal readonly record struct ServiceDescription
{
public readonly string ClassName;
public readonly string InterFaceName;
public readonly bool IsInterface;
public readonly ImmutableEquatableArray<MethodDescription> Methods;
public readonly string Namespace;
public readonly string ServiceName;
public readonly ImmutableEquatableArray<string> Usings;

public ServiceDescription(bool isInterface, string className, string ns, string service,
public ServiceDescription(bool isInterface, string className, string interfaceName, string ns, string service,
List<MethodDescription> methods, List<string> usings)
{
IsInterface = isInterface;
ClassName = className;
InterFaceName = interfaceName;
Namespace = ns;
ServiceName = service;
Methods = new ImmutableEquatableArray<MethodDescription>(methods);
Expand Down
40 changes: 29 additions & 11 deletions Core.ServiceMesh.SourceGen/ServiceMeshGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Core.ServiceMesh.SourceGen.Core;
using Core.ServiceMesh.SourceGen.Model;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -41,7 +38,26 @@ private ServiceDescription Transform(GeneratorAttributeSyntaxContext ctx, Cancel

var className = typeSymbol.Name;
var attr = typeSymbol.GetAttributes().Single(x => x.AttributeClass!.Name == AttributeName);
var serviceName = (string)attr.ConstructorArguments[0].Value;
var serviceName = string.Empty;
var interfaceName = string.Empty;

if (!isInterface)
{
var interFace = typeSymbol.AllInterfaces.SingleOrDefault(x => x.GetAttributes()
.Any(y => y.AttributeClass!.Name == AttributeName));

if (interFace != null)
{
var attr2 = interFace.GetAttributes().Single(x => x.AttributeClass!.Name == AttributeName);
serviceName = (string)attr2.ConstructorArguments[0].Value;

interfaceName = interFace.Name;
}
}
else
{
serviceName = (string)attr.ConstructorArguments[0].Value;
}

var methods = typeSymbol.GetMembers()
.Where(x => x.Kind == SymbolKind.Method)
Expand All @@ -55,7 +71,7 @@ private ServiceDescription Transform(GeneratorAttributeSyntaxContext ctx, Cancel
? string.Empty
: $"{typeSymbol.ContainingNamespace}";

return new ServiceDescription(isInterface, className, ns, serviceName, methods.Select(x =>
return new ServiceDescription(isInterface, className, interfaceName, ns, serviceName, methods.Select(x =>
{
var methodName = x.Name;
var generics = x
Expand Down Expand Up @@ -114,22 +130,24 @@ private static void BuildRemoteProxy(SourceProductionContext context, ServiceDes
builder.AppendLine();
var parameterEx = $"[{string.Join(", ", m.ParameterNames)}]";
var genericsEx = $"[{string.Join(", ", m.Generics.Select(x => $"typeof({x})"))}]";
var invoke = $"await mesh.RequestAsync(subject, {parameterEx}, {genericsEx});";

var subject = $"\"{service.ServiceName}.{m.Name}.G{m.Generics.Count}P{m.Parameters.Count}\"";

var invoke = $"await mesh.RequestAsync({subject}, {parameterEx}, {genericsEx});";

if (m.Return.StartsWith("ValueTask<") || m.Return.StartsWith("Task<"))
invoke =
$"return await mesh.RequestAsync<{m.ReturnArguments[0]}>(subject, {parameterEx}, {genericsEx});";
$"return await mesh.RequestAsync<{m.ReturnArguments[0]}>({subject}, {parameterEx}, {genericsEx});";

if (m.Return.StartsWith("IAsyncEnumerable<"))
invoke = $"""
await foreach (var msg in mesh.StreamAsync<{m.ReturnArguments[0]}>(subject, {parameterEx}, {genericsEx}))
await foreach (var msg in mesh.StreamAsync<{m.ReturnArguments[0]}>({subject}, {parameterEx}, {genericsEx}))
yield return msg;
""";

var code = $$"""
public async {{m.Return}} {{m.Name}}{{(m.Generics.Any() ? "<" + string.Join(",", m.Generics.Select(x => x)) + ">" : string.Empty)}}({{string.Join(", ", m.Parameters.Select(x => x))}}) {{(m.Constraints.Any() ? string.Join(", ", m.Constraints) : string.Empty)}}
{
var subject = "{{service.ServiceName}}.{{m.Name}}.G{{m.Generics.Count}}P{{m.Parameters.Count}}";
{{invoke}}
}
""";
Expand Down Expand Up @@ -158,7 +176,7 @@ private static void BuildTraceProxy(SourceProductionContext context, ServiceDesc

builder.AppendLine("[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]");
builder.AppendLine(
$"public sealed class {service.ClassName}TraceProxy({service.ClassName} svc) : I{service.ClassName}");
$"public sealed class {service.ClassName}TraceProxy({service.ClassName} svc) : {service.InterFaceName}");
builder.Append("{");

foreach (var m in service.Methods)
Expand Down
6 changes: 3 additions & 3 deletions Core.ServiceMesh/Core.ServiceMesh.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />

<PackageReference Include="NATS.Client.Hosting" Version="2.3.3" />
<PackageReference Include="NATS.Client.JetStream" Version="2.3.3" />
<PackageReference Include="NATS.Net" Version="2.3.3" />
<PackageReference Include="NATS.Client.Hosting" Version="2.5.1" />
<PackageReference Include="NATS.Client.JetStream" Version="2.5.1" />
<PackageReference Include="NATS.Net" Version="2.5.1" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageReference Include="OpenTelemetry.Api" Version="1.9.0" />
</ItemGroup>
Expand Down
5 changes: 3 additions & 2 deletions Core.ServiceMesh/ServiceMeshExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Core.ServiceMesh.Internal;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using NATS.Client.Hosting;
using OpenTelemetry.Trace;
Expand Down Expand Up @@ -68,7 +67,9 @@ public static IServiceCollection AddServiceMesh(this IServiceCollection services

foreach (var method in methods)
{
var subject = applyPrefix(options.ResolveService(attr, method));
var subject =
applyPrefix(
$"{attr.Name}.{method.Name}.G{method.GetGenericArguments().Length}P{method.GetParameters().Length}");

if (subject == null)
continue;
Expand Down
7 changes: 0 additions & 7 deletions Core.ServiceMesh/ServiceMeshOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Reflection;
using System.Text.Json;
using Core.ServiceMesh.Abstractions;
using K4os.Compression.LZ4;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -67,12 +66,6 @@ public class ServiceMeshOptions
public Func<byte[], Type, bool, object?> Deserialize { get; set; } =
(body, type, compress) => JsonSerializer.Deserialize(compress ? LZ4Pickler.Unpickle(body) : body, type);

/// <summary>
/// Nats subject name from service mesh attribute + method info
/// </summary>
public Func<ServiceMeshAttribute, MethodInfo, string> ResolveService { get; set; } = (attr, info) =>
$"{attr.Name}.{info.Name}.G{info.GetGenericArguments().Length}P{info.GetParameters().Length}";

/// <summary>
/// Nats subject for message
/// </summary>
Expand Down

0 comments on commit 1ac26e3

Please sign in to comment.