diff --git a/src/Benchmark/Bench.cs b/src/Benchmark/Bench.cs index 8e6e9b2..079c1d2 100644 --- a/src/Benchmark/Bench.cs +++ b/src/Benchmark/Bench.cs @@ -20,7 +20,7 @@ public void Setup() { var sc = new ServiceCollection(); sc.AddEventSourcing(); - sc.AddPoliciesFrom(typeof(Bench).Assembly); + sc.AddPolicy(); _sp = sc.BuildServiceProvider(); } @@ -56,7 +56,7 @@ public void SetupHeavyRead() { var sc = new ServiceCollection(); sc.AddEventSourcing(); - sc.AddPoliciesFrom(typeof(Bench).Assembly); + sc.AddPolicy(); _sp = sc.BuildServiceProvider(); diff --git a/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs b/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs index d84dc0f..1ba96b0 100644 --- a/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs +++ b/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs @@ -10,17 +10,10 @@ namespace Fluss.PostgreSQL; public static class ServiceCollectionExtensions { public static IServiceCollection AddPostgresEventSourcingRepository(this IServiceCollection services, - string connectionString, Assembly? upcasterSourceAssembly = null) + string connectionString) { ArgumentNullException.ThrowIfNull(services); - if (upcasterSourceAssembly is not null) - { - services - .AddUpcasters(upcasterSourceAssembly) - .AddHostedService(); - } - return services .AddBaseEventRepository() .AddFluentMigratorCore() @@ -31,7 +24,8 @@ public static IServiceCollection AddPostgresEventSourcingRepository(this IServic .AddLogging(lb => lb.AddFluentMigratorConsole()) .AddSingleton(new PostgreSQLConfig(connectionString)) .AddSingleton() - .AddHostedService(sp => sp.GetRequiredService()); + .AddHostedService(sp => sp.GetRequiredService()) + .AddHostedService(); } } diff --git a/src/Fluss.Regen/Generators/RegistrationSyntaxGenerator.cs b/src/Fluss.Regen/Generators/RegistrationSyntaxGenerator.cs new file mode 100644 index 0000000..066ca73 --- /dev/null +++ b/src/Fluss.Regen/Generators/RegistrationSyntaxGenerator.cs @@ -0,0 +1,112 @@ +using System; +using System.Text; +using Fluss.Regen.Helpers; +using Microsoft.CodeAnalysis.Text; + +namespace Fluss.Regen.Generators; + +public sealed class RegistrationSyntaxGenerator : IDisposable +{ + private readonly string _moduleName; + private readonly string _ns; + private StringBuilder _sb; + private CodeWriter _writer; + private bool _disposed; + + public RegistrationSyntaxGenerator(string moduleName, string ns) + { + _moduleName = moduleName; + _ns = ns; + _sb = StringBuilderPool.Get(); + _writer = new CodeWriter(_sb); + } + + public void WriteHeader() + { + _writer.WriteFileHeader(); + _writer.WriteLine(); + } + + public void WriteBeginNamespace() + { + _writer.WriteIndentedLine("namespace {0} {{", _ns); + _writer.IncreaseIndent(); + } + + public void WriteEndNamespace() + { + _writer.DecreaseIndent(); + _writer.WriteIndentedLine("}"); + } + + public void WriteBeginClass() + { + _writer.WriteIndentedLine("public static partial class {0}ServiceCollectionExtensions {{", _moduleName); + _writer.IncreaseIndent(); + } + + public void WriteEndClass() + { + _writer.DecreaseIndent(); + _writer.WriteIndentedLine("}"); + } + + public void WriteBeginRegistrationMethod() + { + _writer.WriteIndentedLine( + "public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Add{0}(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection sc) {{", + _moduleName); + _writer.IncreaseIndent(); + } + + public void WriteEndRegistrationMethod() + { + _writer.WriteIndentedLine("return sc;"); + _writer.DecreaseIndent(); + _writer.WriteIndentedLine("}"); + } + + public void WriteAggregateValidatorRegistration(string aggregateValidatorType) + { + _writer.WriteIndentedLine("global::Fluss.Validation.ValidationServiceCollectionExtension.AddAggregateValidator<{0}>(sc);", aggregateValidatorType); + } + + public void WriteEventValidatorRegistration(string eventValidatorType) + { + _writer.WriteIndentedLine("global::Fluss.Validation.ValidationServiceCollectionExtension.AddEventValidator<{0}>(sc);", eventValidatorType); + } + + public void WritePolicyRegistration(string policyType) + { + _writer.WriteIndentedLine("global::Fluss.Authentication.ServiceCollectionExtensions.AddPolicy<{0}>(sc);", policyType); + } + + public void WriteSideEffectRegistration(string sideEffectType) + { + _writer.WriteIndentedLine("global::Fluss.SideEffects.SideEffectsServiceCollectionExtension.AddSideEffect<{0}>(sc);", sideEffectType); + } + + public void WriteUpcasterRegistration(string upcasterType) + { + _writer.WriteIndentedLine("global::Fluss.ServiceCollectionExtensions.AddUpcaster<{0}>(sc);", upcasterType); + } + + public override string ToString() + => _sb.ToString(); + + public SourceText ToSourceText() + => SourceText.From(ToString(), Encoding.UTF8); + + public void Dispose() + { + if (_disposed) + { + return; + } + + StringBuilderPool.Return(_sb); + _sb = default!; + _writer = default!; + _disposed = true; + } +} diff --git a/src/Fluss.Regen/Generators/SelectorSyntaxGenerator.cs b/src/Fluss.Regen/Generators/SelectorSyntaxGenerator.cs index 965fa74..1b1d087 100644 --- a/src/Fluss.Regen/Generators/SelectorSyntaxGenerator.cs +++ b/src/Fluss.Regen/Generators/SelectorSyntaxGenerator.cs @@ -38,18 +38,23 @@ public void WriteClassHeader() public void WriteEndNamespace() { - _writer.WriteIndentedLine(""" - private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); - private static async ValueTask MatchesEventListenerState(IUnitOfWork unitOfWork, CacheEntryValue value) { - foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { - if (!(await eventListenerData.IsStillUpToDate(unitOfWork))) { - return false; - } - } - - return true; - } - """); + _writer.WriteIndentedLine("private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners);"); + _writer.WriteLine(); + _writer.WriteIndented("private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) "); + using (_writer.WriteBraces()) + { + _writer.WriteIndented("foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) "); + using (_writer.WriteBraces()) + { + _writer.WriteIndented("if (!await eventListenerData.IsStillUpToDate(unitOfWork)) "); + using (_writer.WriteBraces()) + { + _writer.WriteIndentedLine("return false;"); + } + } + + _writer.WriteIndentedLine("return true;"); + } _writer.DecreaseIndent(); _writer.WriteIndentedLine("}"); diff --git a/src/Fluss.Regen/Inspectors/AggregateValidatorInfo.cs b/src/Fluss.Regen/Inspectors/AggregateValidatorInfo.cs new file mode 100644 index 0000000..b642812 --- /dev/null +++ b/src/Fluss.Regen/Inspectors/AggregateValidatorInfo.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class AggregateValidatorInfo( + INamedTypeSymbol classSymbol, + ClassDeclarationSyntax classSyntax) + : ISyntaxInfo +{ + public INamedTypeSymbol Type { get; } = classSymbol; + private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax; + + public bool Equals(AggregateValidatorInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ClassSyntax.Equals(other.ClassSyntax); + } + + public bool Equals(ISyntaxInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other is AggregateValidatorInfo info && Equals(info); + } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || (obj is AggregateValidatorInfo other && Equals(other)); + + public override int GetHashCode() => ClassSyntax.GetHashCode(); +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/AggregateValidatorInspector.cs b/src/Fluss.Regen/Inspectors/AggregateValidatorInspector.cs new file mode 100644 index 0000000..de66f9c --- /dev/null +++ b/src/Fluss.Regen/Inspectors/AggregateValidatorInspector.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class AggregateValidatorInspector : ISyntaxInspector +{ + public bool TryHandle( + GeneratorSyntaxContext context, + [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) + { + if (context.Node is ClassDeclarationSyntax classSyntax) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax); + if (symbol is INamedTypeSymbol classSymbol && + classSymbol.AllInterfaces.Any(i => i.ToDisplayString().StartsWith("Fluss.Validation.AggregateValidator"))) + { + syntaxInfo = new AggregateValidatorInfo(classSymbol, classSyntax); + return true; + } + } + + syntaxInfo = null; + return false; + } +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/EventValidatorInfo.cs b/src/Fluss.Regen/Inspectors/EventValidatorInfo.cs new file mode 100644 index 0000000..1a753db --- /dev/null +++ b/src/Fluss.Regen/Inspectors/EventValidatorInfo.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class EventValidatorInfo( + INamedTypeSymbol classSymbol, + ClassDeclarationSyntax classSyntax) + : ISyntaxInfo +{ + public INamedTypeSymbol Type { get; } = classSymbol; + private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax; + + public bool Equals(EventValidatorInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ClassSyntax.Equals(other.ClassSyntax); + } + + public bool Equals(ISyntaxInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other is EventValidatorInfo info && Equals(info); + } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || (obj is EventValidatorInfo other && Equals(other)); + + public override int GetHashCode() => ClassSyntax.GetHashCode(); +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/EventValidatorInspector.cs b/src/Fluss.Regen/Inspectors/EventValidatorInspector.cs new file mode 100644 index 0000000..d92d4be --- /dev/null +++ b/src/Fluss.Regen/Inspectors/EventValidatorInspector.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class EventValidatorInspector : ISyntaxInspector +{ + public bool TryHandle( + GeneratorSyntaxContext context, + [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) + { + if (context.Node is ClassDeclarationSyntax classSyntax) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax); + if (symbol is INamedTypeSymbol classSymbol && + classSymbol.AllInterfaces.Any(i => i.ToDisplayString().StartsWith("Fluss.Validation.EventValidator<"))) + { + syntaxInfo = new EventValidatorInfo(classSymbol, classSyntax); + return true; + } + } + + syntaxInfo = null; + return false; + } +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/PolicyInfo.cs b/src/Fluss.Regen/Inspectors/PolicyInfo.cs new file mode 100644 index 0000000..53476ab --- /dev/null +++ b/src/Fluss.Regen/Inspectors/PolicyInfo.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class PolicyInfo( + INamedTypeSymbol classSymbol, + ClassDeclarationSyntax classSyntax) + : ISyntaxInfo +{ + public INamedTypeSymbol Type { get; } = classSymbol; + private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax; + + public bool Equals(PolicyInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ClassSyntax.Equals(other.ClassSyntax); + } + + public bool Equals(ISyntaxInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other is PolicyInfo info && Equals(info); + } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || (obj is PolicyInfo other && Equals(other)); + + public override int GetHashCode() => ClassSyntax.GetHashCode(); +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/PolicyInspector.cs b/src/Fluss.Regen/Inspectors/PolicyInspector.cs new file mode 100644 index 0000000..da10f9a --- /dev/null +++ b/src/Fluss.Regen/Inspectors/PolicyInspector.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class PolicyInspector : ISyntaxInspector +{ + public bool TryHandle( + GeneratorSyntaxContext context, + [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) + { + if (context.Node is ClassDeclarationSyntax classSyntax) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax); + if (symbol is INamedTypeSymbol classSymbol && + classSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "Fluss.Authentication.Policy")) + { + syntaxInfo = new PolicyInfo(classSymbol, classSyntax); + return true; + } + } + + syntaxInfo = null; + return false; + } +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/SelectorInfo.cs b/src/Fluss.Regen/Inspectors/SelectorInfo.cs index 2fd5938..21c58e0 100644 --- a/src/Fluss.Regen/Inspectors/SelectorInfo.cs +++ b/src/Fluss.Regen/Inspectors/SelectorInfo.cs @@ -62,5 +62,4 @@ public override int GetHashCode() return hashCode; } } - } \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/SideEffectInfo.cs b/src/Fluss.Regen/Inspectors/SideEffectInfo.cs new file mode 100644 index 0000000..99b71d5 --- /dev/null +++ b/src/Fluss.Regen/Inspectors/SideEffectInfo.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class SideEffectInfo( + INamedTypeSymbol classSymbol, + ClassDeclarationSyntax classSyntax) + : ISyntaxInfo +{ + public INamedTypeSymbol Type { get; } = classSymbol; + private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax; + + public bool Equals(SideEffectInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ClassSyntax.Equals(other.ClassSyntax); + } + + public bool Equals(ISyntaxInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other is SideEffectInfo info && Equals(info); + } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || (obj is SideEffectInfo other && Equals(other)); + + public override int GetHashCode() => ClassSyntax.GetHashCode(); +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/SideEffectInspector.cs b/src/Fluss.Regen/Inspectors/SideEffectInspector.cs new file mode 100644 index 0000000..1f6d025 --- /dev/null +++ b/src/Fluss.Regen/Inspectors/SideEffectInspector.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class SideEffectInspector : ISyntaxInspector +{ + public bool TryHandle( + GeneratorSyntaxContext context, + [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) + { + if (context.Node is ClassDeclarationSyntax classSyntax) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax); + if (symbol is INamedTypeSymbol classSymbol && + classSymbol.AllInterfaces.Any(i => i.ToDisplayString().StartsWith("Fluss.SideEffects.SideEffect<"))) + { + syntaxInfo = new SideEffectInfo(classSymbol, classSyntax); + return true; + } + } + + syntaxInfo = null; + return false; + } +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/UpcasterInfo.cs b/src/Fluss.Regen/Inspectors/UpcasterInfo.cs new file mode 100644 index 0000000..0e09183 --- /dev/null +++ b/src/Fluss.Regen/Inspectors/UpcasterInfo.cs @@ -0,0 +1,32 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class UpcasterInfo( + INamedTypeSymbol classSymbol, + ClassDeclarationSyntax classSyntax) + : ISyntaxInfo +{ + public INamedTypeSymbol Type { get; } = classSymbol; + private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax; + + public bool Equals(UpcasterInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ClassSyntax.Equals(other.ClassSyntax); + } + + public bool Equals(ISyntaxInfo? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other is UpcasterInfo info && Equals(info); + } + + public override bool Equals(object? obj) => + ReferenceEquals(this, obj) || (obj is UpcasterInfo other && Equals(other)); + + public override int GetHashCode() => ClassSyntax.GetHashCode(); +} \ No newline at end of file diff --git a/src/Fluss.Regen/Inspectors/UpcasterInspector.cs b/src/Fluss.Regen/Inspectors/UpcasterInspector.cs new file mode 100644 index 0000000..925e4a0 --- /dev/null +++ b/src/Fluss.Regen/Inspectors/UpcasterInspector.cs @@ -0,0 +1,29 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Fluss.Regen.Inspectors; + +public sealed class UpcasterInspector : ISyntaxInspector +{ + public bool TryHandle( + GeneratorSyntaxContext context, + [NotNullWhen(true)] out ISyntaxInfo? syntaxInfo) + { + if (context.Node is ClassDeclarationSyntax classSyntax) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax); + if (symbol is INamedTypeSymbol classSymbol && + classSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "Fluss.Upcasting.IUpcaster")) + { + syntaxInfo = new UpcasterInfo(classSymbol, classSyntax); + return true; + } + } + + syntaxInfo = null; + return false; + } +} \ No newline at end of file diff --git a/src/Fluss.Regen/SelectorGenerator.cs b/src/Fluss.Regen/SelectorGenerator.cs index 4ad6529..e8a9e04 100644 --- a/src/Fluss.Regen/SelectorGenerator.cs +++ b/src/Fluss.Regen/SelectorGenerator.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Fluss.Regen.Attributes; using Fluss.Regen.Generators; +using Fluss.Regen.Helpers; using Fluss.Regen.Inspectors; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -19,7 +20,12 @@ public class SelectorGenerator : IIncrementalGenerator { private static readonly ISyntaxInspector[] Inspectors = [ + new AggregateValidatorInspector(), + new EventValidatorInspector(), + new PolicyInspector(), new SelectorInspector(), + new SideEffectInspector(), + new UpcasterInspector(), ]; public void Initialize(IncrementalGeneratorInitializationContext context) @@ -39,7 +45,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput( valueProvider, - static (context, source) => Execute(context, source.Right)); + static (context, source) => Execute(context, source.Left, source.Right)); } private static bool IsRelevant(SyntaxNode node) @@ -71,8 +77,8 @@ private static bool IsMethodWithAttribute(SyntaxNode node) return null; } - private static void Execute( - SourceProductionContext context, + private static void Execute(SourceProductionContext context, + Compilation compilation, ImmutableArray syntaxInfos) { if (syntaxInfos.IsEmpty) @@ -82,6 +88,7 @@ private static void Execute( var syntaxInfoList = syntaxInfos.ToList(); WriteSelectorMethods(context, syntaxInfoList); + WriteRegistration(context, compilation, syntaxInfoList); } private static void WriteSelectorMethods(SourceProductionContext context, List syntaxInfos) @@ -173,6 +180,64 @@ private static void WriteSelectorMethods(SourceProductionContext context, List syntaxInfos) + { + if (syntaxInfos.Count == 0) + { + return; + } + + var moduleName = (compilation.AssemblyName ?? "Assembly").Split(".").Last() + "ESComponents"; + + using var generator = new RegistrationSyntaxGenerator(moduleName, "Microsoft.Extensions.DependencyInjection"); + + generator.WriteHeader(); + generator.WriteBeginNamespace(); + generator.WriteBeginClass(); + generator.WriteBeginRegistrationMethod(); + + var foundInfo = false; + + foreach (var syntaxInfo in syntaxInfos) + { + switch (syntaxInfo) + { + case AggregateValidatorInfo aggregateValidatorInfo: + generator.WriteAggregateValidatorRegistration(aggregateValidatorInfo.Type.ToFullyQualified()); + foundInfo = true; + break; + case EventValidatorInfo eventValidatorInfo: + generator.WriteEventValidatorRegistration(eventValidatorInfo.Type.ToFullyQualified()); + foundInfo = true; + break; + case PolicyInfo policyInfo: + generator.WritePolicyRegistration(policyInfo.Type.ToFullyQualified()); + foundInfo = true; + break; + case SideEffectInfo sideEffectInfo: + generator.WriteSideEffectRegistration(sideEffectInfo.Type.ToFullyQualified()); + foundInfo = true; + break; + case UpcasterInfo upcasterInfo: + generator.WriteUpcasterRegistration(upcasterInfo.Type.ToFullyQualified()); + foundInfo = true; + break; + } + } + + generator.WriteEndRegistrationMethod(); + generator.WriteEndClass(); + generator.WriteEndNamespace(); + + if (foundInfo) + { + context.AddSource("Registration.g.cs", generator.ToSourceText()); + } + } + private static ITypeSymbol ExtractValueType(ITypeSymbol returnType) { if (returnType is INamedTypeSymbol namedTypeSymbol && (ToTypeNameNoGenerics(returnType) == typeof(ValueTask).FullName || diff --git a/src/Fluss.UnitTest/Fluss.UnitTest.csproj b/src/Fluss.UnitTest/Fluss.UnitTest.csproj index 59b188d..db6337d 100644 --- a/src/Fluss.UnitTest/Fluss.UnitTest.csproj +++ b/src/Fluss.UnitTest/Fluss.UnitTest.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0 + net9.0 enable enable @@ -33,8 +33,4 @@ - - - - diff --git a/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs b/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs index 83834ac..4688c32 100644 --- a/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs +++ b/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs @@ -1,6 +1,8 @@ +using Fluss.Events; using Fluss.Regen; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Newtonsoft.Json.Linq; namespace Fluss.UnitTest.Regen; @@ -9,79 +11,192 @@ public class SelectorGeneratorTests [Fact] public Task GeneratesForAsyncSelector() { - var generator = new SelectorGenerator(); + var runResult = GenerateFor( + """ + + using Fluss.Regen; + using System.Threading.Tasks; + + namespace TestNamespace; + + public class Test + { + [Selector] + public static async ValueTask Add(int a, int b) { + return a + b; + } + + [Selector] + public static async ValueTask Add2(int a, int b) { + return a + b; + } + } + """); - var driver = CSharpGeneratorDriver.Create(generator); + return Verify(runResult); + } - var compilation = CSharpCompilation.Create(nameof(SelectorGeneratorTests), - [ - CSharpSyntaxTree.ParseText( - """ - - using Fluss.Regen; - using System.Threading.Tasks; - - namespace TestNamespace; - - public class Test - { - [Selector] - public static async ValueTask Add(int a, int b) { - return a + b; - } - - [Selector] - public static async ValueTask Add2(int a, int b) { - return a + b; - } - } - """) - ], - [ - MetadataReference.CreateFromFile(typeof(object).Assembly.Location) - ]); + [Fact] + public Task GeneratesForNonAsyncSelector() + { + var runResult = GenerateFor( + """ - var runResult = driver.RunGenerators(compilation).GetRunResult(); + using Fluss.Regen; + + namespace TestNamespace; + + public class Test + { + [Selector] + public static int Add(int a, int b) { + return a + b; + } + } + """); return Verify(runResult); } [Fact] - public Task GeneratesForNonAsyncSelector() + public Task GeneratesForUnitOfWorkSelector() { - var generator = new SelectorGenerator(); + var runResult = GenerateFor( + """ - var driver = CSharpGeneratorDriver.Create(generator); + using Fluss; + using Fluss.Regen; - var compilation = CSharpCompilation.Create(nameof(SelectorGeneratorTests), - [ - CSharpSyntaxTree.ParseText( - """ + namespace TestNamespace; - using Fluss.Regen; + public class Test + { + [Selector] + public static int Add(IUnitOfWork unitOfWork, int a, int b) { + return a + b; + } + } + """); - namespace TestNamespace; + return Verify(runResult); + } - public class Test - { - [Selector] - public static int Add(int a, int b) { - return a + b; - } - } - """) - ], - [ - MetadataReference.CreateFromFile(typeof(object).Assembly.Location) - ]); + [Fact] + public Task GeneratesForAggregateValidator() + { + var runResult = GenerateFor( + """ + using Fluss; + using Fluss.Validation; + + namespace TestNamespace; + + public record TestAggregate : AggregateRoot + { + public TestAggregate When(EventEnvelope envelope) { + return this; + } + } + + public class TestAggregateValidator : AggregateValidator + { + public ValueTask ValidateAsync(TestAggregate aggregateAfterEvent, IUnitOfWork unitOfWorkBeforeEvent) { + return ValueTask.CompletedTask; + } + } + """ + ); - var runResult = driver.RunGenerators(compilation).GetRunResult(); + return Verify(runResult); + } + + [Fact] + public Task GenerateForEventValidator() + { + var runResult = GenerateFor( + """ + using Fluss; + using Fluss.Validation; + + namespace TestNamespace; + + public record TestEvent : Event; + + public class TestEventValidator : EventValidator + { + public ValueTask Validate(TestEvent @event, IUnitOfWork unitOfWorkBeforeEvent) { + return ValueTask.CompletedTask; + } + } + """ + ); return Verify(runResult); } [Fact] - public Task GeneratesForUnitOfWorkSelector() + public Task GenerateForPolicy() + { + var runResult = GenerateFor( + """ + using Fluss; + using Fluss.Authentication; + + namespace TestNamespace; + + public class TestPolicy : Policy; + """ + ); + + return Verify(runResult); + } + + [Fact] + public Task GenerateForSideEffect() + { + var runResult = GenerateFor( + """ + using Fluss; + using Fluss.SideEffects; + + namespace TestNamespace; + + public record TestEvent : Event; + + public class TestSideEffect : SideEffect { + public Task> HandleAsync(T @event, UnitOfWork unitOfWork) { + return Task.FromResult>(new List()); + } + } + """ + ); + + return Verify(runResult); + } + + [Fact] + public Task GenerateForUpcaster() + { + var runResult = GenerateFor( + """ + using Fluss; + using Fluss.Upcasting; + using Newtonsoft.Json.Linq; + + namespace TestNamespace; + + public class TestUpcaster : IUpcaster { + public IEnumerable? Upcast(JObject eventJson) { + return null; + } + } + """ + ); + + return Verify(runResult); + } + + private GeneratorDriverRunResult GenerateFor(string source) { var generator = new SelectorGenerator(); @@ -89,30 +204,16 @@ public Task GeneratesForUnitOfWorkSelector() var compilation = CSharpCompilation.Create(nameof(SelectorGeneratorTests), [ - CSharpSyntaxTree.ParseText( - """ - - using Fluss; - using Fluss.Regen; - - namespace TestNamespace; - - public class Test - { - [Selector] - public static int Add(IUnitOfWork unitOfWork, int a, int b) { - return a + b; - } - } - """) + CSharpSyntaxTree.ParseText(source) ], [ MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(UnitOfWork).Assembly.Location) + MetadataReference.CreateFromFile(typeof(UnitOfWork).Assembly.Location), + MetadataReference.CreateFromFile(typeof(JObject).Assembly.Location), ]); var runResult = driver.RunGenerators(compilation).GetRunResult(); - return Verify(runResult); + return runResult; } } \ No newline at end of file diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#Registration.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#Registration.g.verified.cs new file mode 100644 index 0000000..8146b72 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#Registration.g.verified.cs @@ -0,0 +1,16 @@ +//HintName: Registration.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.DependencyInjection { + public static partial class SelectorGeneratorTestsESComponentsServiceCollectionExtensions { + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddSelectorGeneratorTestsESComponents(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection sc) { + global::Fluss.Validation.ValidationServiceCollectionExtension.AddEventValidator(sc); + return sc; + } + } +} diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#SelectorAttribute.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#SelectorAttribute.g.verified.cs new file mode 100644 index 0000000..6b9ec56 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#SelectorAttribute.g.verified.cs @@ -0,0 +1,10 @@ +//HintName: SelectorAttribute.g.cs +// + +namespace Fluss.Regen +{ + [System.AttributeUsage(System.AttributeTargets.Method)] + public class SelectorAttribute : System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#Selectors.g.verified.cs new file mode 100644 index 0000000..6ec1378 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForEventValidator#Selectors.g.verified.cs @@ -0,0 +1,26 @@ +//HintName: Selectors.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Fluss +{ + public static class UnitOfWorkSelectors + { + private static global::Microsoft.Extensions.Caching.Memory.MemoryCache _cache = new (new global::Microsoft.Extensions.Caching.Memory.MemoryCacheOptions { SizeLimit = 1024 }); + private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; + } + } +} + diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#Registration.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#Registration.g.verified.cs new file mode 100644 index 0000000..4459032 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#Registration.g.verified.cs @@ -0,0 +1,16 @@ +//HintName: Registration.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.DependencyInjection { + public static partial class SelectorGeneratorTestsESComponentsServiceCollectionExtensions { + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddSelectorGeneratorTestsESComponents(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection sc) { + global::Fluss.Authentication.ServiceCollectionExtensions.AddPolicy(sc); + return sc; + } + } +} diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#SelectorAttribute.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#SelectorAttribute.g.verified.cs new file mode 100644 index 0000000..6b9ec56 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#SelectorAttribute.g.verified.cs @@ -0,0 +1,10 @@ +//HintName: SelectorAttribute.g.cs +// + +namespace Fluss.Regen +{ + [System.AttributeUsage(System.AttributeTargets.Method)] + public class SelectorAttribute : System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#Selectors.g.verified.cs new file mode 100644 index 0000000..6ec1378 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForPolicy#Selectors.g.verified.cs @@ -0,0 +1,26 @@ +//HintName: Selectors.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Fluss +{ + public static class UnitOfWorkSelectors + { + private static global::Microsoft.Extensions.Caching.Memory.MemoryCache _cache = new (new global::Microsoft.Extensions.Caching.Memory.MemoryCacheOptions { SizeLimit = 1024 }); + private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; + } + } +} + diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#Registration.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#Registration.g.verified.cs new file mode 100644 index 0000000..1047a25 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#Registration.g.verified.cs @@ -0,0 +1,16 @@ +//HintName: Registration.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.DependencyInjection { + public static partial class SelectorGeneratorTestsESComponentsServiceCollectionExtensions { + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddSelectorGeneratorTestsESComponents(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection sc) { + global::Fluss.SideEffects.SideEffectsServiceCollectionExtension.AddSideEffect(sc); + return sc; + } + } +} diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#SelectorAttribute.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#SelectorAttribute.g.verified.cs new file mode 100644 index 0000000..6b9ec56 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#SelectorAttribute.g.verified.cs @@ -0,0 +1,10 @@ +//HintName: SelectorAttribute.g.cs +// + +namespace Fluss.Regen +{ + [System.AttributeUsage(System.AttributeTargets.Method)] + public class SelectorAttribute : System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#Selectors.g.verified.cs new file mode 100644 index 0000000..6ec1378 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForSideEffect#Selectors.g.verified.cs @@ -0,0 +1,26 @@ +//HintName: Selectors.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Fluss +{ + public static class UnitOfWorkSelectors + { + private static global::Microsoft.Extensions.Caching.Memory.MemoryCache _cache = new (new global::Microsoft.Extensions.Caching.Memory.MemoryCacheOptions { SizeLimit = 1024 }); + private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; + } + } +} + diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#Registration.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#Registration.g.verified.cs new file mode 100644 index 0000000..eb21806 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#Registration.g.verified.cs @@ -0,0 +1,16 @@ +//HintName: Registration.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.DependencyInjection { + public static partial class SelectorGeneratorTestsESComponentsServiceCollectionExtensions { + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddSelectorGeneratorTestsESComponents(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection sc) { + global::Fluss.ServiceCollectionExtensions.AddUpcaster(sc); + return sc; + } + } +} diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#SelectorAttribute.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#SelectorAttribute.g.verified.cs new file mode 100644 index 0000000..6b9ec56 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#SelectorAttribute.g.verified.cs @@ -0,0 +1,10 @@ +//HintName: SelectorAttribute.g.cs +// + +namespace Fluss.Regen +{ + [System.AttributeUsage(System.AttributeTargets.Method)] + public class SelectorAttribute : System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#Selectors.g.verified.cs new file mode 100644 index 0000000..6ec1378 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GenerateForUpcaster#Selectors.g.verified.cs @@ -0,0 +1,26 @@ +//HintName: Selectors.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Fluss +{ + public static class UnitOfWorkSelectors + { + private static global::Microsoft.Extensions.Caching.Memory.MemoryCache _cache = new (new global::Microsoft.Extensions.Caching.Memory.MemoryCacheOptions { SizeLimit = 1024 }); + private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; + } + } +} + diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#Registration.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#Registration.g.verified.cs new file mode 100644 index 0000000..cb12f1a --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#Registration.g.verified.cs @@ -0,0 +1,16 @@ +//HintName: Registration.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.DependencyInjection { + public static partial class SelectorGeneratorTestsESComponentsServiceCollectionExtensions { + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddSelectorGeneratorTestsESComponents(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection sc) { + global::Fluss.Validation.ValidationServiceCollectionExtension.AddAggregateValidator(sc); + return sc; + } + } +} diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#SelectorAttribute.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#SelectorAttribute.g.verified.cs new file mode 100644 index 0000000..6b9ec56 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#SelectorAttribute.g.verified.cs @@ -0,0 +1,10 @@ +//HintName: SelectorAttribute.g.cs +// + +namespace Fluss.Regen +{ + [System.AttributeUsage(System.AttributeTargets.Method)] + public class SelectorAttribute : System.Attribute + { + } +} \ No newline at end of file diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#Selectors.g.verified.cs new file mode 100644 index 0000000..6ec1378 --- /dev/null +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAggregateValidator#Selectors.g.verified.cs @@ -0,0 +1,26 @@ +//HintName: Selectors.g.cs +// + +#nullable enable + +using System; +using System.Runtime.CompilerServices; + +namespace Fluss +{ + public static class UnitOfWorkSelectors + { + private static global::Microsoft.Extensions.Caching.Memory.MemoryCache _cache = new (new global::Microsoft.Extensions.Caching.Memory.MemoryCacheOptions { SizeLimit = 1024 }); + private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; + } + } +} + diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAsyncSelector#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAsyncSelector#Selectors.g.verified.cs index e7f842b..8c55f72 100644 --- a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAsyncSelector#Selectors.g.verified.cs +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForAsyncSelector#Selectors.g.verified.cs @@ -68,15 +68,15 @@ int b return (int)result; } private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); -private static async ValueTask MatchesEventListenerState(IUnitOfWork unitOfWork, CacheEntryValue value) { - foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { - if (!(await eventListenerData.IsStillUpToDate(unitOfWork))) { - return false; + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; } } - - return true; -} - } } diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForNonAsyncSelector#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForNonAsyncSelector#Selectors.g.verified.cs index 8496d3d..f0a615e 100644 --- a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForNonAsyncSelector#Selectors.g.verified.cs +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForNonAsyncSelector#Selectors.g.verified.cs @@ -40,15 +40,15 @@ int b return (int)result; } private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); -private static async ValueTask MatchesEventListenerState(IUnitOfWork unitOfWork, CacheEntryValue value) { - foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { - if (!(await eventListenerData.IsStillUpToDate(unitOfWork))) { - return false; + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; } } - - return true; -} - } } diff --git a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForUnitOfWorkSelector#Selectors.g.verified.cs b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForUnitOfWorkSelector#Selectors.g.verified.cs index 37b31d9..1a21ae5 100644 --- a/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForUnitOfWorkSelector#Selectors.g.verified.cs +++ b/src/Fluss.UnitTest/Regen/Snapshots/SelectorGeneratorTests.GeneratesForUnitOfWorkSelector#Selectors.g.verified.cs @@ -42,15 +42,15 @@ int b return (int)result; } private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList? EventListeners); -private static async ValueTask MatchesEventListenerState(IUnitOfWork unitOfWork, CacheEntryValue value) { - foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { - if (!(await eventListenerData.IsStillUpToDate(unitOfWork))) { - return false; + + private static async global::System.Threading.Tasks.ValueTask MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) { + foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty()) { + if (!await eventListenerData.IsStillUpToDate(unitOfWork)) { + return false; + } + } + return true; } } - - return true; -} - } } diff --git a/src/Fluss/Authentication/ServiceCollectionExtensions.cs b/src/Fluss/Authentication/ServiceCollectionExtensions.cs index 32dd6ad..2cf6425 100644 --- a/src/Fluss/Authentication/ServiceCollectionExtensions.cs +++ b/src/Fluss/Authentication/ServiceCollectionExtensions.cs @@ -1,22 +1,13 @@ -using System.Reflection; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; namespace Fluss.Authentication; public static class ServiceCollectionExtensions { - public static IServiceCollection AddPoliciesFrom(this IServiceCollection services, Assembly assembly) + public static IServiceCollection AddPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TPolicy>(this IServiceCollection services) where TPolicy : class, Policy { - var policyTypes = assembly.GetTypes().Where(x => x.IsAssignableTo(typeof(Policy))); - - foreach (var policyType in policyTypes) - { - services - .AddSingleton(policyType) - .AddSingleton(sp => (Policy)sp.GetRequiredService(policyType)); - } - - return services; + return services.AddSingleton(); } public static IServiceCollection ProvideUserIdFrom(this IServiceCollection services, diff --git a/src/Fluss/Events/EventListener.cs b/src/Fluss/Events/EventListener.cs index 343c24d..8392fde 100644 --- a/src/Fluss/Events/EventListener.cs +++ b/src/Fluss/Events/EventListener.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Fluss.Events.TransientEvents; namespace Fluss.Events; @@ -73,11 +74,13 @@ public bool HasTaint() public interface IRootEventListener; +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public interface IEventListenerWithKey { public object Id { get; } } +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public interface IEventListenerWithKey : IEventListenerWithKey { public new TKey Id { get; init; } diff --git a/src/Fluss/ServiceCollectionExtensions.cs b/src/Fluss/ServiceCollectionExtensions.cs index f8f2222..c7197be 100644 --- a/src/Fluss/ServiceCollectionExtensions.cs +++ b/src/Fluss/ServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using Fluss.Authentication; @@ -63,7 +64,7 @@ public static IServiceCollection AddEventSourcing(this IServiceCollection servic return services; } - public static IServiceCollection AddEventRepositoryPipeline(this IServiceCollection services) + public static IServiceCollection AddEventRepositoryPipeline<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TEventRepository>(this IServiceCollection services) where TEventRepository : EventRepositoryPipeline { return services @@ -71,7 +72,7 @@ public static IServiceCollection AddEventRepositoryPipeline(th .AddSingleton(sp => sp.GetRequiredService()); } - public static IServiceCollection AddBaseEventRepository(this IServiceCollection services) + public static IServiceCollection AddBaseEventRepository<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TBaseEventRepository>(this IServiceCollection services) where TBaseEventRepository : class, IBaseEventRepository { ArgumentNullException.ThrowIfNull(services); @@ -93,16 +94,8 @@ public static IServiceCollection AddBaseEventRepository(th }); } - public static IServiceCollection AddUpcasters(this IServiceCollection services, Assembly sourceAssembly) + public static IServiceCollection AddUpcaster<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TUpcaster>(this IServiceCollection services) where TUpcaster : class, IUpcaster { - var upcasterType = typeof(IUpcaster); - var upcasters = sourceAssembly.GetTypes().Where(t => t.IsAssignableTo(upcasterType)); - - foreach (var upcaster in upcasters) - { - services.AddSingleton(upcasterType, upcaster); - } - - return services; + return services.AddSingleton(); } } diff --git a/src/Fluss/SideEffects/SideEffect.cs b/src/Fluss/SideEffects/SideEffect.cs index 57f749a..e489f00 100644 --- a/src/Fluss/SideEffects/SideEffect.cs +++ b/src/Fluss/SideEffects/SideEffect.cs @@ -1,7 +1,9 @@ +using System.Diagnostics.CodeAnalysis; using Fluss.Events; namespace Fluss.SideEffects; +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods)] public interface SideEffect; public interface SideEffect : SideEffect where T : Event diff --git a/src/Fluss/SideEffects/SideEffectsServiceCollectionExtension.cs b/src/Fluss/SideEffects/SideEffectsServiceCollectionExtension.cs index 6070c1c..4a849f6 100644 --- a/src/Fluss/SideEffects/SideEffectsServiceCollectionExtension.cs +++ b/src/Fluss/SideEffects/SideEffectsServiceCollectionExtension.cs @@ -1,19 +1,12 @@ -using System.Reflection; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; namespace Fluss.SideEffects; public static class SideEffectsServiceCollectionExtension { - public static IServiceCollection RegisterSideEffects(this IServiceCollection services, Assembly assembly) + public static IServiceCollection AddSideEffect<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TSideEffect>(this IServiceCollection services) where TSideEffect : class, SideEffect { - var implementingClasses = assembly.GetTypes().Where(t => t.IsAssignableTo(typeof(SideEffect))).ToList(); - - foreach (var @class in implementingClasses) - { - services.AddSingleton(typeof(SideEffect), @class); - } - - return services; + return services.AddSingleton(); } } diff --git a/src/Fluss/UnitOfWork/IUnitOfWork.cs b/src/Fluss/UnitOfWork/IUnitOfWork.cs index 9e14389..4a11411 100644 --- a/src/Fluss/UnitOfWork/IUnitOfWork.cs +++ b/src/Fluss/UnitOfWork/IUnitOfWork.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using Fluss.Events; using Fluss.ReadModel; @@ -9,7 +10,7 @@ public interface IUnitOfWork : IDisposable ValueTask ConsistentVersion(); IReadOnlyCollection ReadModels { get; } - ValueTask GetReadModel(Type tReadModel, object? key, long? at = null); + ValueTask GetReadModel([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type tReadModel, object? key, long? at = null); ValueTask GetReadModel(long? at = null) where TReadModel : EventListener, IRootEventListener, IReadModel, new(); diff --git a/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs b/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs index 464ca68..b7d8cce 100644 --- a/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs +++ b/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using Collections.Pooled; using Fluss.Events; using Fluss.ReadModel; @@ -10,7 +11,7 @@ public partial class UnitOfWork private readonly PooledList _readModels = new(); public IReadOnlyCollection ReadModels => _readModels; - public async ValueTask GetReadModel(Type tReadModel, object? key, long? at = null) + public async ValueTask GetReadModel([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type tReadModel, object? key, long? at = null) { using var activity = FlussActivitySource.Source.StartActivity(); activity?.SetTag("EventSourcing.ReadModel", tReadModel.FullName); @@ -22,7 +23,7 @@ public async ValueTask GetReadModel(Type tReadModel, object? key, lo if (eventListener is IEventListenerWithKey eventListenerWithKey) { - eventListenerWithKey.GetType().GetProperty("Id")?.SetValue(eventListenerWithKey, key); + typeof(IEventListenerWithKey).GetProperty("Id")?.SetValue(eventListenerWithKey, key); } eventListener = await UpdateAndApplyPublished(eventListener, at); diff --git a/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs b/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs index aad4a3b..d3c5223 100644 --- a/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs +++ b/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using Fluss.Events; using Fluss.ReadModel; @@ -15,7 +16,7 @@ public ValueTask ConsistentVersion() public List RecordedListeners { get; } = []; - public ValueTask GetReadModel(Type tReadModel, object? key, long? at = null) + public ValueTask GetReadModel([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type tReadModel, object? key, long? at = null) { return impl.GetReadModel(tReadModel, key, at); } @@ -85,7 +86,7 @@ public IReadOnlyList GetRecordedListeners() return eventListenerTypeWithKeyAndVersions; } - public record EventListenerTypeWithKeyAndVersion(Type Type, object? Key, long Version) + public record EventListenerTypeWithKeyAndVersion([property: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type Type, object? Key, long Version) { public async ValueTask IsStillUpToDate(IUnitOfWork unitOfWork, long? at = null) { diff --git a/src/Fluss/Validation/AggregateValidator.cs b/src/Fluss/Validation/AggregateValidator.cs index 0274166..ad80293 100644 --- a/src/Fluss/Validation/AggregateValidator.cs +++ b/src/Fluss/Validation/AggregateValidator.cs @@ -1,7 +1,9 @@ +using System.Diagnostics.CodeAnalysis; using Fluss.Aggregates; namespace Fluss.Validation; +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods)] public interface AggregateValidator; public interface AggregateValidator : AggregateValidator where T : AggregateRoot diff --git a/src/Fluss/Validation/EventValidator.cs b/src/Fluss/Validation/EventValidator.cs index 469a6f5..ba15e5d 100644 --- a/src/Fluss/Validation/EventValidator.cs +++ b/src/Fluss/Validation/EventValidator.cs @@ -1,7 +1,9 @@ +using System.Diagnostics.CodeAnalysis; using Fluss.Events; namespace Fluss.Validation; +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicMethods)] public interface EventValidator; public interface EventValidator : EventValidator where T : Event diff --git a/src/Fluss/Validation/ValidationServiceCollectionExtension.cs b/src/Fluss/Validation/ValidationServiceCollectionExtension.cs index 84a0216..79240dc 100644 --- a/src/Fluss/Validation/ValidationServiceCollectionExtension.cs +++ b/src/Fluss/Validation/ValidationServiceCollectionExtension.cs @@ -1,28 +1,17 @@ -using System.Reflection; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; namespace Fluss.Validation; public static class ValidationServiceCollectionExtension { - public static IServiceCollection AddValidationFrom(this IServiceCollection services, Assembly sourceAssembly) + public static IServiceCollection AddAggregateValidator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TAggregateValidator>(this IServiceCollection services) where TAggregateValidator : class, AggregateValidator { - var aggregateValidatorType = typeof(AggregateValidator); - var aggregateValidators = - sourceAssembly.GetTypes().Where(t => t.IsAssignableTo(aggregateValidatorType)).ToList(); - foreach (var aggregateValidator in aggregateValidators) - { - services.AddSingleton(aggregateValidatorType, aggregateValidator); - } - - var eventValidatorType = typeof(EventValidator); - var eventValidators = - sourceAssembly.GetTypes().Where(t => t.IsAssignableTo(eventValidatorType)).ToList(); - foreach (var eventValidator in eventValidators) - { - services.AddSingleton(eventValidatorType, eventValidator); - } + return services.AddSingleton(); + } - return services; + public static IServiceCollection AddEventValidator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TEventValidator>(this IServiceCollection services) where TEventValidator : class, EventValidator + { + return services.AddSingleton(); } }