Skip to content

Commit

Permalink
Add EventTypes Generator
Browse files Browse the repository at this point in the history
  • Loading branch information
tomohisa committed Dec 8, 2024
1 parent 6948c1c commit ca54344
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 47 deletions.
75 changes: 37 additions & 38 deletions internalUsages/Pure.Domain/Class1.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Pure.Domain.Generated;
using ResultBoxes;
using Sekiban.Pure;
using Sekiban.Pure.Exception;
namespace Pure.Domain;

public record UnconfirmedUser(string Name, string Email) : IAggregatePayload;
Expand Down Expand Up @@ -136,40 +135,40 @@ public void Test1()
// command.Handle);
// }
// now writing manually, but it will be generated by the source generator
public class DomainEventTypes : IEventTypes
{
public ResultBox<IEvent> GenerateTypedEvent(
IEventPayload payload,
PartitionKeys partitionKeys,
string sortableUniqueId,
int version) => payload switch
{
UserRegistered userRegistered => new Event<UserRegistered>(
userRegistered,
partitionKeys,
sortableUniqueId,
version),
UserConfirmed userConfirmed => new Event<UserConfirmed>(
userConfirmed,
partitionKeys,
sortableUniqueId,
version),
UserUnconfirmed userUnconfirmed => new Event<UserUnconfirmed>(
userUnconfirmed,
partitionKeys,
sortableUniqueId,
version),
BranchCreated branchCreated => new Event<BranchCreated>(
branchCreated,
partitionKeys,
sortableUniqueId,
version),
BranchNameChanged branchNameChanged => new Event<BranchNameChanged>(
branchNameChanged,
partitionKeys,
sortableUniqueId,
version),
_ => ResultBox<IEvent>.FromException(
new SekibanEventTypeNotFoundException($"Event Type {payload.GetType().Name} Not Found"))
};
}
// public class DomainEventTypes : IEventTypes
// {
// public ResultBox<IEvent> GenerateTypedEvent(
// IEventPayload payload,
// PartitionKeys partitionKeys,
// string sortableUniqueId,
// int version) => payload switch
// {
// UserRegistered userRegistered => new Event<UserRegistered>(
// userRegistered,
// partitionKeys,
// sortableUniqueId,
// version),
// UserConfirmed userConfirmed => new Event<UserConfirmed>(
// userConfirmed,
// partitionKeys,
// sortableUniqueId,
// version),
// UserUnconfirmed userUnconfirmed => new Event<UserUnconfirmed>(
// userUnconfirmed,
// partitionKeys,
// sortableUniqueId,
// version),
// BranchCreated branchCreated => new Event<BranchCreated>(
// branchCreated,
// partitionKeys,
// sortableUniqueId,
// version),
// BranchNameChanged branchNameChanged => new Event<BranchNameChanged>(
// branchNameChanged,
// partitionKeys,
// sortableUniqueId,
// version),
// _ => ResultBox<IEvent>.FromException(
// new SekibanEventTypeNotFoundException($"Event Type {payload.GetType().Name} Not Found"))
// };
// }
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Sekiban.Pure.SourceGenerator;

public class Class1
{
}
[Generator]
public class CommandExecutionExtensionGenerator : IIncrementalGenerator
{
Expand Down
121 changes: 121 additions & 0 deletions src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Sekiban.Pure.SourceGenerator;

[Generator]
public class EventTypesGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Collect all class and record declarations
var typeDeclarations = context
.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is ClassDeclarationSyntax || node is RecordDeclarationSyntax,
static (ctx, _) => ctx.Node)
.Where(static typeDecl => typeDecl is ClassDeclarationSyntax || typeDecl is RecordDeclarationSyntax);

// Combine with compilation information
var compilationAndTypes = context.CompilationProvider.Combine(typeDeclarations.Collect());


// Generate source code
context.RegisterSourceOutput(
compilationAndTypes,
(ctx, source) =>
{
var (compilation, types) = source;
var commandTypes = ImmutableArray.CreateBuilder<CommandWithHandlerValues>();

commandTypes.AddRange(GetEventValues(compilation, types));

// Generate source code
var rootNamespace = compilation.AssemblyName;
var sourceCode = GenerateSourceCode(commandTypes.ToImmutable(), rootNamespace);

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Mixed

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Mixed

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularAggregateTest

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularMultitenant

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularPostgres

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularDynamo

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Cosmos

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Cosmos

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.

Check warning on line 38 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / flaky90

Possible null reference argument for parameter 'rootNamespace' in 'string EventTypesGenerator.GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)'.
ctx.AddSource("EventTypes.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
});

}
public ImmutableArray<CommandWithHandlerValues> GetEventValues(
Compilation compilation,
ImmutableArray<SyntaxNode> types)
{
var iEventPayloadSymbol = compilation.GetTypeByMetadataName("Sekiban.Pure.IEventPayload");
if (iEventPayloadSymbol == null)
return new ImmutableArray<CommandWithHandlerValues>();
var eventTypes = ImmutableArray.CreateBuilder<CommandWithHandlerValues>();
foreach (var typeSyntax in types)
{
var model = compilation.GetSemanticModel(typeSyntax.SyntaxTree);
var typeSymbol = model.GetDeclaredSymbol(typeSyntax) as INamedTypeSymbol;
var allInterfaces = typeSymbol.AllInterfaces.ToList();

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Mixed

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Mixed

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularAggregateTest

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularMultitenant

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularPostgres

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularDynamo

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Cosmos

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Cosmos

Dereference of a possibly null reference.

Check warning on line 55 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / flaky90

Dereference of a possibly null reference.
if (typeSymbol != null && typeSymbol.AllInterfaces.Any(m => m == iEventPayloadSymbol))
{
var interfaceImplementation = typeSymbol.AllInterfaces.First(m => m == iEventPayloadSymbol);
eventTypes.Add(
new CommandWithHandlerValues
{
InterfaceName = interfaceImplementation.Name,
RecordName = typeSymbol.ToDisplayString()
});
}
}
return eventTypes.ToImmutable();
}

private string GenerateSourceCode(ImmutableArray<CommandWithHandlerValues> eventTypes, string rootNamespace)
{
var sb = new StringBuilder();
sb.AppendLine("// Auto-generated by IncrementalGenerator");
sb.AppendLine("using System.Threading.Tasks;");
sb.AppendLine("using ResultBoxes;");
sb.AppendLine("using Sekiban.Pure;");
sb.AppendLine("using Sekiban.Pure.Exception;");

sb.AppendLine();
sb.AppendLine($"namespace {rootNamespace}.Generated");
sb.AppendLine("{");
sb.AppendLine($" public class {rootNamespace.Replace(".", "")}EventTypes : IEventTypes");
sb.AppendLine(" {");
sb.AppendLine(" public ResultBox<IEvent> GenerateTypedEvent(");
sb.AppendLine(" IEventPayload payload,");
sb.AppendLine(" PartitionKeys partitionKeys,");
sb.AppendLine(" string sortableUniqueId,");
sb.AppendLine(" int version) => payload switch");
sb.AppendLine(" {");

foreach (var type in eventTypes)
{
switch (type.InterfaceName, type.TypeCount)
{
case ("IEventPayload", 0):
sb.AppendLine(
$" {type.RecordName} {type.RecordName.Split('.').Last().ToLower()} => new Event<{type.RecordName}>(");
sb.AppendLine($" {type.RecordName.Split('.').Last().ToLower()},");
sb.AppendLine(" partitionKeys,");
sb.AppendLine(" sortableUniqueId,");
sb.AppendLine(" version),");
break;
}
}

sb.AppendLine(" _ => ResultBox<IEvent>.FromException(");
sb.AppendLine(
" new SekibanEventTypeNotFoundException($\"Event Type {payload.GetType().Name} Not Found\"))");
sb.AppendLine(" };");
sb.AppendLine(" };");
sb.AppendLine("}");

return sb.ToString();
}
public class CommandWithHandlerValues
{
public string InterfaceName { get; set; }

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Mixed

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Mixed

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularAggregateTest

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularMultitenant

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularPostgres

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularDynamo

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Cosmos

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Cosmos

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 117 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / flaky90

Non-nullable property 'InterfaceName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string RecordName { get; set; }

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Mixed

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Mixed

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularAggregateTest

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularMultitenant

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularPostgres

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regularDynamo

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular80Cosmos

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / regular90Cosmos

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 118 in src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs

View workflow job for this annotation

GitHub Actions / flaky90

Non-nullable property 'RecordName' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public int TypeCount { get; set; }
}
}
10 changes: 5 additions & 5 deletions tests/Pure.Domain.Test/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void TenantPartitionKeysTest()
public async Task SimpleEventSourcing()
{
Repository.Events.Clear();
var executor = new CommandExecutor { EventTypes = new DomainEventTypes() };
var executor = new CommandExecutor { EventTypes = new PureDomainEventTypes() };

Assert.Empty(Repository.Events);
await executor.Execute(new RegisterBranch("branch1"));
Expand Down Expand Up @@ -83,7 +83,7 @@ public async Task SimpleEventSourcing()
public async Task SimpleEventSourcingFunction()
{
Repository.Events.Clear();
var executor = new CommandExecutor { EventTypes = new DomainEventTypes() };
var executor = new CommandExecutor { EventTypes = new PureDomainEventTypes() };

Assert.Empty(Repository.Events);
var registerBranch = new RegisterBranch("branch1");
Expand Down Expand Up @@ -168,7 +168,7 @@ await executor.ExecuteFunction(
public async Task ChangeBranchNameSpec()
{
Repository.Events.Clear();
var executor = new CommandExecutor { EventTypes = new DomainEventTypes() };
var executor = new CommandExecutor { EventTypes = new PureDomainEventTypes() };

Assert.Empty(Repository.Events);
var executed = await executor.Execute(new RegisterBranch("branch1"));
Expand Down Expand Up @@ -204,7 +204,7 @@ public void CanUseDelegateSpec()
public async Task MultipleBranchesSpec()
{
Repository.Events.Clear();
var executor = new CommandExecutor { EventTypes = new DomainEventTypes() };
var executor = new CommandExecutor { EventTypes = new PureDomainEventTypes() };

Assert.Empty(Repository.Events);
var executed = await executor.Execute(new RegisterBranch("branch 0"));
Expand Down Expand Up @@ -234,7 +234,7 @@ public async Task MultipleBranchesSpec()
public async Task ICommandAndICommandWithAggregateRestrictionShouldWorkWithFunctionTest()
{
Repository.Events.Clear();
var executor = new CommandExecutor { EventTypes = new DomainEventTypes() };
var executor = new CommandExecutor { EventTypes = new PureDomainEventTypes() };

var command1 = new RegisterBranch2("aaa");
var result = await executor.ExecuteFunction(
Expand Down

0 comments on commit ca54344

Please sign in to comment.