diff --git a/internalUsages/Pure.Domain/Class1.cs b/internalUsages/Pure.Domain/Class1.cs index f577d2f2..310b6d65 100644 --- a/internalUsages/Pure.Domain/Class1.cs +++ b/internalUsages/Pure.Domain/Class1.cs @@ -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; @@ -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")) +// }; +// } diff --git a/src/Sekiban.Pure.SourceGenerator/Class1.cs b/src/Sekiban.Pure.SourceGenerator/CommandExecutionExtensionGenerator.cs similarity index 99% rename from src/Sekiban.Pure.SourceGenerator/Class1.cs rename to src/Sekiban.Pure.SourceGenerator/CommandExecutionExtensionGenerator.cs index 5120b6b9..aa12a442 100644 --- a/src/Sekiban.Pure.SourceGenerator/Class1.cs +++ b/src/Sekiban.Pure.SourceGenerator/CommandExecutionExtensionGenerator.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using System.Collections.Immutable; @@ -6,9 +6,6 @@ using System.Text; namespace Sekiban.Pure.SourceGenerator; -public class Class1 -{ -} [Generator] public class CommandExecutionExtensionGenerator : IIncrementalGenerator { diff --git a/src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs b/src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs new file mode 100644 index 00000000..fed85079 --- /dev/null +++ b/src/Sekiban.Pure.SourceGenerator/EventTypesGenerator.cs @@ -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); + 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(); + 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; } + public string RecordName { get; set; } + public int TypeCount { get; set; } + } +} \ No newline at end of file diff --git a/tests/Pure.Domain.Test/UnitTest1.cs b/tests/Pure.Domain.Test/UnitTest1.cs index 67cc9520..effc0c9f 100644 --- a/tests/Pure.Domain.Test/UnitTest1.cs +++ b/tests/Pure.Domain.Test/UnitTest1.cs @@ -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")); @@ -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"); @@ -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")); @@ -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")); @@ -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(