From 2a716be2e65588b1ee13a7ae392183f6a8f02950 Mon Sep 17 00:00:00 2001 From: eneshoxha Date: Tue, 24 Feb 2026 15:34:56 +0100 Subject: [PATCH] v3/feature/214: Source Generator for Compile-Time Dispatch Add Cortex.Mediator.SourceGenerator with compile-time dispatch Introduce Cortex.Mediator.SourceGenerator, a Roslyn incremental source generator that emits a compile-time dispatcher (GeneratedMediator) and explicit DI registrations, eliminating runtime reflection and assembly scanning. Includes full implementation, packaging, and documentation. Integrates with the solution and test suite, adding comprehensive tests for generator output, diagnostics, and integration. No breaking changes to existing runtime code; the source generator is opt-in and NativeAOT-friendly. --- Cortex.sln | 308 +++++++++++- README.md | 3 + .../Assets/andyX.png | Bin 0 -> 63537 bytes .../Assets/license.md | 20 + .../Cortex.Mediator.SourceGenerator.csproj | 77 +++ .../CortexMediatorGenerator.cs | 247 ++++++++++ .../Diagnostics/DiagnosticDescriptors.cs | 55 +++ .../Discovery/HandlerDiscovery.cs | 61 +++ .../Discovery/MessageDiscovery.cs | 77 +++ .../Discovery/SymbolExtensions.cs | 34 ++ .../Emitters/GeneratedDIEmitter.cs | 104 ++++ .../Emitters/GeneratedMediatorEmitter.cs | 459 ++++++++++++++++++ .../Emitters/SourceTextBuilder.cs | 52 ++ .../Models/HandlerRegistration.cs | 54 +++ .../Models/InterfaceKind.cs | 14 + .../Models/MessageRegistration.cs | 47 ++ src/Cortex.Mediator.SourceGenerator/README.md | 182 +++++++ src/Cortex.Mediator/Cortex.Mediator.csproj | 1 + .../DependencyInjection/MediatorOptions.cs | 2 +- .../ServiceCollectionExtensions.cs | 2 +- src/Cortex.Tests/Cortex.Tests.csproj | 2 + .../SourceGenerator/DiagnosticTests.cs | 254 ++++++++++ .../GeneratedMediatorIntegrationTests.cs | 326 +++++++++++++ .../SourceGenerator/GeneratorDriverTests.cs | 420 ++++++++++++++++ 24 files changed, 2787 insertions(+), 14 deletions(-) create mode 100644 src/Cortex.Mediator.SourceGenerator/Assets/andyX.png create mode 100644 src/Cortex.Mediator.SourceGenerator/Assets/license.md create mode 100644 src/Cortex.Mediator.SourceGenerator/Cortex.Mediator.SourceGenerator.csproj create mode 100644 src/Cortex.Mediator.SourceGenerator/CortexMediatorGenerator.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Discovery/HandlerDiscovery.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Discovery/MessageDiscovery.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Discovery/SymbolExtensions.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedDIEmitter.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedMediatorEmitter.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Emitters/SourceTextBuilder.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Models/HandlerRegistration.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Models/InterfaceKind.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/Models/MessageRegistration.cs create mode 100644 src/Cortex.Mediator.SourceGenerator/README.md create mode 100644 src/Cortex.Tests/Mediator/SourceGenerator/DiagnosticTests.cs create mode 100644 src/Cortex.Tests/Mediator/SourceGenerator/GeneratedMediatorIntegrationTests.cs create mode 100644 src/Cortex.Tests/Mediator/SourceGenerator/GeneratorDriverTests.cs diff --git a/Cortex.sln b/Cortex.sln index 6665027..9640f38 100644 --- a/Cortex.sln +++ b/Cortex.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.0.11111.16 @@ -76,144 +77,426 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cortex.States.DuckDb", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cortex.Mediator.Behaviors.Transactional", "src\Cortex.Mediator.Behaviors.Transactional\Cortex.Mediator.Behaviors.Transactional.csproj", "{F7C9F778-EFDB-4F02-8F19-43A9F4A86003}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cortex.Mediator.SourceGenerator", "src\Cortex.Mediator.SourceGenerator\Cortex.Mediator.SourceGenerator.csproj", "{67B76C68-F28F-4A01-AB02-DB661785C690}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Debug|x64.ActiveCfg = Debug|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Debug|x64.Build.0 = Debug|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Debug|x86.ActiveCfg = Debug|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Debug|x86.Build.0 = Debug|Any CPU {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Release|Any CPU.ActiveCfg = Release|Any CPU {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Release|Any CPU.Build.0 = Release|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Release|x64.ActiveCfg = Release|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Release|x64.Build.0 = Release|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Release|x86.ActiveCfg = Release|Any CPU + {F1CC775A-95DA-4A5A-879F-66BFCB0FDCC9}.Release|x86.Build.0 = Release|Any CPU {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Debug|x64.Build.0 = Debug|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Debug|x86.Build.0 = Debug|Any CPU {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Release|Any CPU.ActiveCfg = Release|Any CPU {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Release|Any CPU.Build.0 = Release|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Release|x64.ActiveCfg = Release|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Release|x64.Build.0 = Release|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Release|x86.ActiveCfg = Release|Any CPU + {1C8605F4-91CB-49A4-A080-0A6DFE1FB010}.Release|x86.Build.0 = Release|Any CPU {96701658-663E-41C5-9EF9-843C79CE727A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96701658-663E-41C5-9EF9-843C79CE727A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Debug|x64.ActiveCfg = Debug|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Debug|x64.Build.0 = Debug|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Debug|x86.ActiveCfg = Debug|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Debug|x86.Build.0 = Debug|Any CPU {96701658-663E-41C5-9EF9-843C79CE727A}.Release|Any CPU.ActiveCfg = Release|Any CPU {96701658-663E-41C5-9EF9-843C79CE727A}.Release|Any CPU.Build.0 = Release|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Release|x64.ActiveCfg = Release|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Release|x64.Build.0 = Release|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Release|x86.ActiveCfg = Release|Any CPU + {96701658-663E-41C5-9EF9-843C79CE727A}.Release|x86.Build.0 = Release|Any CPU {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Debug|x64.ActiveCfg = Debug|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Debug|x64.Build.0 = Debug|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Debug|x86.ActiveCfg = Debug|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Debug|x86.Build.0 = Debug|Any CPU {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Release|Any CPU.Build.0 = Release|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Release|x64.ActiveCfg = Release|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Release|x64.Build.0 = Release|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Release|x86.ActiveCfg = Release|Any CPU + {EE799E10-7469-428E-AF8C-F2807F6CE7E5}.Release|x86.Build.0 = Release|Any CPU {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Debug|x64.ActiveCfg = Debug|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Debug|x64.Build.0 = Debug|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Debug|x86.ActiveCfg = Debug|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Debug|x86.Build.0 = Debug|Any CPU {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Release|Any CPU.ActiveCfg = Release|Any CPU {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Release|Any CPU.Build.0 = Release|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Release|x64.ActiveCfg = Release|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Release|x64.Build.0 = Release|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Release|x86.ActiveCfg = Release|Any CPU + {34CA231A-1E3A-4CA4-820C-946DC8E2737F}.Release|x86.Build.0 = Release|Any CPU {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Debug|x64.ActiveCfg = Debug|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Debug|x64.Build.0 = Debug|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Debug|x86.ActiveCfg = Debug|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Debug|x86.Build.0 = Debug|Any CPU {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Release|Any CPU.ActiveCfg = Release|Any CPU {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Release|Any CPU.Build.0 = Release|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Release|x64.ActiveCfg = Release|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Release|x64.Build.0 = Release|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Release|x86.ActiveCfg = Release|Any CPU + {16FA00B1-973B-443C-BF84-9B76DCAF341B}.Release|x86.Build.0 = Release|Any CPU {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Debug|x64.Build.0 = Debug|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Debug|x86.Build.0 = Debug|Any CPU {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Release|Any CPU.Build.0 = Release|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Release|x64.ActiveCfg = Release|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Release|x64.Build.0 = Release|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Release|x86.ActiveCfg = Release|Any CPU + {1F60F66A-5DC0-4C1D-A7B7-66F568A26911}.Release|x86.Build.0 = Release|Any CPU {2B949637-FC31-4F51-A391-19F76B57CC28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B949637-FC31-4F51-A391-19F76B57CC28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Debug|x64.ActiveCfg = Debug|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Debug|x64.Build.0 = Debug|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Debug|x86.Build.0 = Debug|Any CPU {2B949637-FC31-4F51-A391-19F76B57CC28}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B949637-FC31-4F51-A391-19F76B57CC28}.Release|Any CPU.Build.0 = Release|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Release|x64.ActiveCfg = Release|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Release|x64.Build.0 = Release|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Release|x86.ActiveCfg = Release|Any CPU + {2B949637-FC31-4F51-A391-19F76B57CC28}.Release|x86.Build.0 = Release|Any CPU {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Debug|x64.ActiveCfg = Debug|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Debug|x64.Build.0 = Debug|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Debug|x86.Build.0 = Debug|Any CPU {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Release|Any CPU.Build.0 = Release|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Release|x64.ActiveCfg = Release|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Release|x64.Build.0 = Release|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Release|x86.ActiveCfg = Release|Any CPU + {6E5AC0AC-A364-4DB3-9E9A-C2FB54BD6D1E}.Release|x86.Build.0 = Release|Any CPU {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Debug|x64.ActiveCfg = Debug|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Debug|x64.Build.0 = Debug|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Debug|x86.ActiveCfg = Debug|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Debug|x86.Build.0 = Debug|Any CPU {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Release|Any CPU.ActiveCfg = Release|Any CPU {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Release|Any CPU.Build.0 = Release|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Release|x64.ActiveCfg = Release|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Release|x64.Build.0 = Release|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Release|x86.ActiveCfg = Release|Any CPU + {6019F9E6-C377-416D-9E3B-3D7104FAEB63}.Release|x86.Build.0 = Release|Any CPU {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Debug|x64.ActiveCfg = Debug|Any CPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Debug|x64.Build.0 = Debug|Any CPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Debug|x86.ActiveCfg = Debug|Any CPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Debug|x86.Build.0 = Debug|Any CPU {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Release|Any CPU.ActiveCfg = Release|Any CPU {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Release|Any CPU.Build.0 = Release|Any CPU - {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|Any CPU.ActiveCfg = Debug|AnyCPU - {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|Any CPU.Build.0 = Debug|AnyCPU - {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|Any CPU.ActiveCfg = Release|AnyCPU - {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|Any CPU.Build.0 = Release|AnyCPU - {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|Any CPU.ActiveCfg = Debug|AnyCPU - {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|Any CPU.Build.0 = Debug|AnyCPU - {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|Any CPU.ActiveCfg = Release|AnyCPU - {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|Any CPU.Build.0 = Release|AnyCPU - {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|Any CPU.ActiveCfg = Debug|AnyCPU - {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|Any CPU.Build.0 = Debug|AnyCPU - {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|Any CPU.ActiveCfg = Release|AnyCPU - {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|Any CPU.Build.0 = Release|AnyCPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Release|x64.ActiveCfg = Release|Any CPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Release|x64.Build.0 = Release|Any CPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Release|x86.ActiveCfg = Release|Any CPU + {20FAE1F1-D677-4E0E-B3A7-E2B1485C874C}.Release|x86.Build.0 = Release|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|x64.ActiveCfg = Debug|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|x64.Build.0 = Debug|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|x86.ActiveCfg = Debug|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Debug|x86.Build.0 = Debug|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|Any CPU.Build.0 = Release|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|x64.ActiveCfg = Release|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|x64.Build.0 = Release|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|x86.ActiveCfg = Release|Any CPU + {CFEB3F3A-8B3E-4286-8596-EF888552C07F}.Release|x86.Build.0 = Release|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|x64.ActiveCfg = Debug|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|x64.Build.0 = Debug|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|x86.ActiveCfg = Debug|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Debug|x86.Build.0 = Debug|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|Any CPU.Build.0 = Release|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|x64.ActiveCfg = Release|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|x64.Build.0 = Release|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|x86.ActiveCfg = Release|Any CPU + {D51C6B82-ABD9-4C43-820E-237EDBD706A1}.Release|x86.Build.0 = Release|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|x64.ActiveCfg = Debug|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|x64.Build.0 = Debug|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|x86.ActiveCfg = Debug|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Debug|x86.Build.0 = Debug|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|Any CPU.Build.0 = Release|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|x64.ActiveCfg = Release|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|x64.Build.0 = Release|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|x86.ActiveCfg = Release|Any CPU + {C9A7699A-9BCF-4B51-B29F-ABF78DCEA553}.Release|x86.Build.0 = Release|Any CPU {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Debug|x64.Build.0 = Debug|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Debug|x86.Build.0 = Debug|Any CPU {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Release|Any CPU.Build.0 = Release|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Release|x64.ActiveCfg = Release|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Release|x64.Build.0 = Release|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Release|x86.ActiveCfg = Release|Any CPU + {D376D6CA-3192-4EDC-B840-31F58B6457DD}.Release|x86.Build.0 = Release|Any CPU {447970B9-C5AA-41D9-A07F-330A251597D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {447970B9-C5AA-41D9-A07F-330A251597D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Debug|x64.ActiveCfg = Debug|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Debug|x64.Build.0 = Debug|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Debug|x86.ActiveCfg = Debug|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Debug|x86.Build.0 = Debug|Any CPU {447970B9-C5AA-41D9-A07F-330A251597D0}.Release|Any CPU.ActiveCfg = Release|Any CPU {447970B9-C5AA-41D9-A07F-330A251597D0}.Release|Any CPU.Build.0 = Release|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Release|x64.ActiveCfg = Release|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Release|x64.Build.0 = Release|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Release|x86.ActiveCfg = Release|Any CPU + {447970B9-C5AA-41D9-A07F-330A251597D0}.Release|x86.Build.0 = Release|Any CPU {00358701-D117-4953-A673-D60625D38466}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {00358701-D117-4953-A673-D60625D38466}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Debug|x64.ActiveCfg = Debug|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Debug|x64.Build.0 = Debug|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Debug|x86.ActiveCfg = Debug|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Debug|x86.Build.0 = Debug|Any CPU {00358701-D117-4953-A673-D60625D38466}.Release|Any CPU.ActiveCfg = Release|Any CPU {00358701-D117-4953-A673-D60625D38466}.Release|Any CPU.Build.0 = Release|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Release|x64.ActiveCfg = Release|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Release|x64.Build.0 = Release|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Release|x86.ActiveCfg = Release|Any CPU + {00358701-D117-4953-A673-D60625D38466}.Release|x86.Build.0 = Release|Any CPU {77AD462F-A248-43AF-9212-43031F22F23D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {77AD462F-A248-43AF-9212-43031F22F23D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Debug|x64.ActiveCfg = Debug|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Debug|x64.Build.0 = Debug|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Debug|x86.ActiveCfg = Debug|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Debug|x86.Build.0 = Debug|Any CPU {77AD462F-A248-43AF-9212-43031F22F23D}.Release|Any CPU.ActiveCfg = Release|Any CPU {77AD462F-A248-43AF-9212-43031F22F23D}.Release|Any CPU.Build.0 = Release|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Release|x64.ActiveCfg = Release|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Release|x64.Build.0 = Release|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Release|x86.ActiveCfg = Release|Any CPU + {77AD462F-A248-43AF-9212-43031F22F23D}.Release|x86.Build.0 = Release|Any CPU {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Debug|x64.ActiveCfg = Debug|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Debug|x64.Build.0 = Debug|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Debug|x86.ActiveCfg = Debug|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Debug|x86.Build.0 = Debug|Any CPU {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Release|Any CPU.Build.0 = Release|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Release|x64.ActiveCfg = Release|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Release|x64.Build.0 = Release|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Release|x86.ActiveCfg = Release|Any CPU + {980EDBFE-40C2-4EFD-96C2-FED1032FB5E6}.Release|x86.Build.0 = Release|Any CPU {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Debug|x64.Build.0 = Debug|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Debug|x86.ActiveCfg = Debug|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Debug|x86.Build.0 = Debug|Any CPU {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Release|Any CPU.ActiveCfg = Release|Any CPU {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Release|Any CPU.Build.0 = Release|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Release|x64.ActiveCfg = Release|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Release|x64.Build.0 = Release|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Release|x86.ActiveCfg = Release|Any CPU + {0F9FCB99-D00F-4396-8E2B-6E627076ADA0}.Release|x86.Build.0 = Release|Any CPU {20BD7107-8199-4CA8-815B-4D156B522B82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20BD7107-8199-4CA8-815B-4D156B522B82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Debug|x64.ActiveCfg = Debug|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Debug|x64.Build.0 = Debug|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Debug|x86.ActiveCfg = Debug|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Debug|x86.Build.0 = Debug|Any CPU {20BD7107-8199-4CA8-815B-4D156B522B82}.Release|Any CPU.ActiveCfg = Release|Any CPU {20BD7107-8199-4CA8-815B-4D156B522B82}.Release|Any CPU.Build.0 = Release|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Release|x64.ActiveCfg = Release|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Release|x64.Build.0 = Release|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Release|x86.ActiveCfg = Release|Any CPU + {20BD7107-8199-4CA8-815B-4D156B522B82}.Release|x86.Build.0 = Release|Any CPU {19167D25-6383-46B4-9449-B9E364F809FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {19167D25-6383-46B4-9449-B9E364F809FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Debug|x64.ActiveCfg = Debug|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Debug|x64.Build.0 = Debug|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Debug|x86.ActiveCfg = Debug|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Debug|x86.Build.0 = Debug|Any CPU {19167D25-6383-46B4-9449-B9E364F809FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {19167D25-6383-46B4-9449-B9E364F809FF}.Release|Any CPU.Build.0 = Release|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Release|x64.ActiveCfg = Release|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Release|x64.Build.0 = Release|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Release|x86.ActiveCfg = Release|Any CPU + {19167D25-6383-46B4-9449-B9E364F809FF}.Release|x86.Build.0 = Release|Any CPU {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Debug|x64.ActiveCfg = Debug|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Debug|x64.Build.0 = Debug|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Debug|x86.ActiveCfg = Debug|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Debug|x86.Build.0 = Debug|Any CPU {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Release|Any CPU.ActiveCfg = Release|Any CPU {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Release|Any CPU.Build.0 = Release|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Release|x64.ActiveCfg = Release|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Release|x64.Build.0 = Release|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Release|x86.ActiveCfg = Release|Any CPU + {81A01446-A8AA-4F9D-BB9B-B66E21B2C348}.Release|x86.Build.0 = Release|Any CPU {0E60F75D-C44B-428A-9252-A11C365E2C56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0E60F75D-C44B-428A-9252-A11C365E2C56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Debug|x64.Build.0 = Debug|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Debug|x86.Build.0 = Debug|Any CPU {0E60F75D-C44B-428A-9252-A11C365E2C56}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E60F75D-C44B-428A-9252-A11C365E2C56}.Release|Any CPU.Build.0 = Release|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Release|x64.ActiveCfg = Release|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Release|x64.Build.0 = Release|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Release|x86.ActiveCfg = Release|Any CPU + {0E60F75D-C44B-428A-9252-A11C365E2C56}.Release|x86.Build.0 = Release|Any CPU {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Debug|x64.ActiveCfg = Debug|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Debug|x64.Build.0 = Debug|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Debug|x86.ActiveCfg = Debug|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Debug|x86.Build.0 = Debug|Any CPU {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Release|Any CPU.Build.0 = Release|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Release|x64.ActiveCfg = Release|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Release|x64.Build.0 = Release|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Release|x86.ActiveCfg = Release|Any CPU + {FC86D3AB-778D-45D7-AF36-1F89FC16DE55}.Release|x86.Build.0 = Release|Any CPU {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Debug|x64.ActiveCfg = Debug|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Debug|x64.Build.0 = Debug|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Debug|x86.Build.0 = Debug|Any CPU {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Release|Any CPU.Build.0 = Release|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Release|x64.ActiveCfg = Release|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Release|x64.Build.0 = Release|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Release|x86.ActiveCfg = Release|Any CPU + {4D1F117D-48D7-47AD-9DAC-3B2DB45E628A}.Release|x86.Build.0 = Release|Any CPU {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Debug|x64.ActiveCfg = Debug|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Debug|x64.Build.0 = Debug|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Debug|x86.ActiveCfg = Debug|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Debug|x86.Build.0 = Debug|Any CPU {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Release|Any CPU.ActiveCfg = Release|Any CPU {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Release|Any CPU.Build.0 = Release|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Release|x64.ActiveCfg = Release|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Release|x64.Build.0 = Release|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Release|x86.ActiveCfg = Release|Any CPU + {64E12D4C-FBB2-4004-8316-C886CBFC614B}.Release|x86.Build.0 = Release|Any CPU {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Debug|Any CPU.Build.0 = Debug|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Debug|x64.ActiveCfg = Debug|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Debug|x64.Build.0 = Debug|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Debug|x86.ActiveCfg = Debug|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Debug|x86.Build.0 = Debug|Any CPU {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Release|Any CPU.ActiveCfg = Release|Any CPU {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Release|Any CPU.Build.0 = Release|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Release|x64.ActiveCfg = Release|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Release|x64.Build.0 = Release|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Release|x86.ActiveCfg = Release|Any CPU + {268BA5C7-C6FB-4A6B-875A-492659ED4573}.Release|x86.Build.0 = Release|Any CPU {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Debug|x64.ActiveCfg = Debug|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Debug|x64.Build.0 = Debug|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Debug|x86.ActiveCfg = Debug|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Debug|x86.Build.0 = Debug|Any CPU {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Release|Any CPU.ActiveCfg = Release|Any CPU {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Release|Any CPU.Build.0 = Release|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Release|x64.ActiveCfg = Release|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Release|x64.Build.0 = Release|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Release|x86.ActiveCfg = Release|Any CPU + {44A166BD-01E9-4A4B-9BC5-7DE01B472E73}.Release|x86.Build.0 = Release|Any CPU {472BC645-9E2F-4205-A571-4D9184747EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {472BC645-9E2F-4205-A571-4D9184747EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Debug|x64.ActiveCfg = Debug|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Debug|x64.Build.0 = Debug|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Debug|x86.ActiveCfg = Debug|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Debug|x86.Build.0 = Debug|Any CPU {472BC645-9E2F-4205-A571-4D9184747EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU {472BC645-9E2F-4205-A571-4D9184747EC5}.Release|Any CPU.Build.0 = Release|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Release|x64.ActiveCfg = Release|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Release|x64.Build.0 = Release|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Release|x86.ActiveCfg = Release|Any CPU + {472BC645-9E2F-4205-A571-4D9184747EC5}.Release|x86.Build.0 = Release|Any CPU {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Debug|x64.ActiveCfg = Debug|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Debug|x64.Build.0 = Debug|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Debug|x86.ActiveCfg = Debug|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Debug|x86.Build.0 = Debug|Any CPU {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Release|Any CPU.ActiveCfg = Release|Any CPU {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Release|Any CPU.Build.0 = Release|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Release|x64.ActiveCfg = Release|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Release|x64.Build.0 = Release|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Release|x86.ActiveCfg = Release|Any CPU + {84410C57-0F59-F31F-B921-4C1F3D3FF144}.Release|x86.Build.0 = Release|Any CPU {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Debug|x64.Build.0 = Debug|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Debug|x86.Build.0 = Debug|Any CPU {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Release|Any CPU.ActiveCfg = Release|Any CPU {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Release|Any CPU.Build.0 = Release|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Release|x64.ActiveCfg = Release|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Release|x64.Build.0 = Release|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Release|x86.ActiveCfg = Release|Any CPU + {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488}.Release|x86.Build.0 = Release|Any CPU {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Debug|x64.ActiveCfg = Debug|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Debug|x64.Build.0 = Debug|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Debug|x86.ActiveCfg = Debug|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Debug|x86.Build.0 = Debug|Any CPU {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Release|Any CPU.Build.0 = Release|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Release|x64.ActiveCfg = Release|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Release|x64.Build.0 = Release|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Release|x86.ActiveCfg = Release|Any CPU + {F7C9F778-EFDB-4F02-8F19-43A9F4A86003}.Release|x86.Build.0 = Release|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Debug|x64.ActiveCfg = Debug|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Debug|x64.Build.0 = Debug|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Debug|x86.ActiveCfg = Debug|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Debug|x86.Build.0 = Debug|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Release|Any CPU.Build.0 = Release|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Release|x64.ActiveCfg = Release|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Release|x64.Build.0 = Release|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Release|x86.ActiveCfg = Release|Any CPU + {67B76C68-F28F-4A01-AB02-DB661785C690}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -247,6 +530,7 @@ Global {84410C57-0F59-F31F-B921-4C1F3D3FF144} = {4C68702C-1661-4AD9-83FD-E0B52B791969} {4FAE6C5E-53EE-4CCE-85A6-B7551A92C488} = {C31F8C0F-8BCF-4959-9BA1-8645D058EAA0} {F7C9F778-EFDB-4F02-8F19-43A9F4A86003} = {1C5D462D-168D-4D3F-B96E-CCE5517DB197} + {67B76C68-F28F-4A01-AB02-DB661785C690} = {1C5D462D-168D-4D3F-B96E-CCE5517DB197} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E20303B6-8AC9-4FFF-B645-4608309ADA94} diff --git a/README.md b/README.md index 7798527..a0f00b9 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,9 @@ - **Cortex.Mediator.Behaviors.Transactional:** implementation of the Transactional Behaviors for Commands [![NuGet Version](https://img.shields.io/nuget/v/Cortex.Mediator.Behaviors.Transactional?label=Cortex.Mediator.Behaviors.Transactional)](https://www.nuget.org/packages/Cortex.Mediator.Behaviors.Transactional) +- **Cortex.Mediator.SourceGenerator:** optional Roslyn source generator that eliminates runtime reflection by generating compile-time dispatch with switch-based routing and explicit DI registrations. +[![NuGet Version](https://img.shields.io/nuget/v/Cortex.Mediator.SourceGenerator?label=Cortex.Mediator.SourceGenerator)](https://www.nuget.org/packages/Cortex.Mediator.SourceGenerator) + - **Cortex.Vectors:** is a High‑performance vector types—Dense, Sparse, and Bit—for AI. [![NuGet Version](https://img.shields.io/nuget/v/Cortex.Vectors?label=Cortex.Vectors)](https://www.nuget.org/packages/Cortex.Vectors) diff --git a/src/Cortex.Mediator.SourceGenerator/Assets/andyX.png b/src/Cortex.Mediator.SourceGenerator/Assets/andyX.png new file mode 100644 index 0000000000000000000000000000000000000000..101a1fb10887915ba6cd81f7493120090cfab590 GIT binary patch literal 63537 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 V*Ppl8Hi;(v?^jgV{@+>5{|{p6e{lc+ literal 0 HcmV?d00001 diff --git a/src/Cortex.Mediator.SourceGenerator/Assets/license.md b/src/Cortex.Mediator.SourceGenerator/Assets/license.md new file mode 100644 index 0000000..caa98b4 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Assets/license.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2026 Buildersoft + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/Cortex.Mediator.SourceGenerator/Cortex.Mediator.SourceGenerator.csproj b/src/Cortex.Mediator.SourceGenerator/Cortex.Mediator.SourceGenerator.csproj new file mode 100644 index 0000000..5a8e199 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Cortex.Mediator.SourceGenerator.csproj @@ -0,0 +1,77 @@ + + + + netstandard2.0 + 12 + true + true + false + true + true + + 1.0.0 + 1.0.0 + Buildersoft Cortex Framework + Buildersoft + Buildersoft,EnesHoxha + Copyright © Buildersoft 2026 + + + Roslyn source generator for Cortex.Mediator that eliminates runtime reflection by generating compile-time dispatch. Produces a GeneratedMediator with switch-based routing and explicit DI registrations, enabling NativeAOT compatibility and faster cold starts. + + + https://github.com/buildersoftio/cortex + cortex vortex mediator eda cqrs source-generator + + 1.0.0 + license.md + andyX.png + Cortex.Mediator.SourceGenerator + True + True + True + + Just as the Cortex in our brains handles complex processing efficiently, Cortex Data Framework brings brainpower to your data management! + https://buildersoft.io/ + Cortex Mediator Source Generator + README.md + + + + + + + + + + True + \ + Always + + + + + True + + + + True + + + + + + + + + + + + + + + + + + diff --git a/src/Cortex.Mediator.SourceGenerator/CortexMediatorGenerator.cs b/src/Cortex.Mediator.SourceGenerator/CortexMediatorGenerator.cs new file mode 100644 index 0000000..700b0e6 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/CortexMediatorGenerator.cs @@ -0,0 +1,247 @@ +using Cortex.Mediator.SourceGenerator.Diagnostics; +using Cortex.Mediator.SourceGenerator.Discovery; +using Cortex.Mediator.SourceGenerator.Emitters; +using Cortex.Mediator.SourceGenerator.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; + +namespace Cortex.Mediator.SourceGenerator +{ + [Generator(LanguageNames.CSharp)] + public sealed class CortexMediatorGenerator : IIncrementalGenerator + { + private const string AttributeFullName = "Cortex.Mediator.SourceGeneration.CortexMediatorGenerationAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Step 1: Emit the marker attribute so consumers only need the NuGet package + context.RegisterPostInitializationOutput(static ctx => + { + ctx.AddSource("CortexMediatorGenerationAttribute.g.cs", @"// +namespace Cortex.Mediator.SourceGeneration +{ + /// + /// Apply this attribute at assembly level to enable the Cortex Mediator source generator. + /// + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = false)] + internal sealed class CortexMediatorGenerationAttribute : System.Attribute + { + } +} +"); + }); + + // Step 2: Check if the assembly attribute is present (opt-in gate) + var hasAttribute = context.SyntaxProvider + .ForAttributeWithMetadataName( + AttributeFullName, + predicate: static (node, _) => node is CompilationUnitSyntax, + transform: static (ctx, _) => true) + .Collect() + .Select(static (items, _) => items.Length > 0); + + // Step 3: Discover handlers from class declarations + var handlerRegistrations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (node, _) => node is ClassDeclarationSyntax cds && cds.BaseList != null, + transform: static (ctx, ct) => GetHandlerRegistrations(ctx, ct)) + .Where(static r => !r.IsEmpty) + .SelectMany(static (r, _) => r); + + var collectedHandlers = handlerRegistrations.Collect(); + + // Step 4: Discover message types for diagnostics + var messageRegistrations = context.SyntaxProvider + .CreateSyntaxProvider( + predicate: static (node, _) => node is ClassDeclarationSyntax cds && cds.BaseList != null, + transform: static (ctx, ct) => GetMessageRegistrations(ctx, ct)) + .Where(static r => !r.IsEmpty) + .SelectMany(static (r, _) => r); + + var collectedMessages = messageRegistrations.Collect(); + + // Step 5: Combine everything and generate + var combined = collectedHandlers.Combine(collectedMessages).Combine(hasAttribute); + + context.RegisterSourceOutput(combined, static (spc, input) => + { + var ((handlers, messages), isEnabled) = input; + + if (!isEnabled) + return; + + // Report diagnostics + ReportDiagnostics(spc, handlers, messages); + + // Emit GeneratedMediator + var mediatorSource = GeneratedMediatorEmitter.Emit(handlers); + spc.AddSource("GeneratedMediator.g.cs", mediatorSource); + + // Emit GeneratedServiceCollectionExtensions + var diSource = GeneratedDIEmitter.Emit(handlers); + spc.AddSource("GeneratedServiceCollectionExtensions.g.cs", diSource); + }); + } + + private static ImmutableArray GetHandlerRegistrations( + GeneratorSyntaxContext context, + CancellationToken cancellationToken) + { + var classDeclaration = (ClassDeclarationSyntax)context.Node; + var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration, cancellationToken); + + if (symbol is not INamedTypeSymbol namedType) + return ImmutableArray.Empty; + + return HandlerDiscovery.FindHandlers(namedType); + } + + private static ImmutableArray GetMessageRegistrations( + GeneratorSyntaxContext context, + CancellationToken cancellationToken) + { + var classDeclaration = (ClassDeclarationSyntax)context.Node; + var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration, cancellationToken); + + if (symbol is not INamedTypeSymbol namedType) + return ImmutableArray.Empty; + + return MessageDiscovery.FindMessages(namedType); + } + + private static void ReportDiagnostics( + SourceProductionContext spc, + ImmutableArray handlers, + ImmutableArray messages) + { + var handledCommands = new HashSet( + handlers.Where(h => h.Kind == InterfaceKind.ReturningCommand) + .Select(h => h.MessageFullyQualifiedName)); + + var handledVoidCommands = new HashSet( + handlers.Where(h => h.Kind == InterfaceKind.VoidCommand) + .Select(h => h.MessageFullyQualifiedName)); + + var handledQueries = new HashSet( + handlers.Where(h => h.Kind == InterfaceKind.Query) + .Select(h => h.MessageFullyQualifiedName)); + + var handledStreamQueries = new HashSet( + handlers.Where(h => h.Kind == InterfaceKind.StreamQuery) + .Select(h => h.MessageFullyQualifiedName)); + + var handledNotifications = new HashSet( + handlers.Where(h => h.Kind == InterfaceKind.Notification) + .Select(h => h.MessageFullyQualifiedName)); + + // Check for unhandled messages + foreach (var msg in messages) + { + switch (msg.Kind) + { + case InterfaceKind.ReturningCommand: + if (!handledCommands.Contains(msg.MessageFullyQualifiedName)) + { + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.CommandHasNoHandler, + msg.Location, + msg.MessageFullyQualifiedName)); + } + break; + + case InterfaceKind.VoidCommand: + if (!handledVoidCommands.Contains(msg.MessageFullyQualifiedName)) + { + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.CommandHasNoHandler, + msg.Location, + msg.MessageFullyQualifiedName)); + } + break; + + case InterfaceKind.Query: + if (!handledQueries.Contains(msg.MessageFullyQualifiedName)) + { + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.QueryHasNoHandler, + msg.Location, + msg.MessageFullyQualifiedName)); + } + break; + + case InterfaceKind.StreamQuery: + if (!handledStreamQueries.Contains(msg.MessageFullyQualifiedName)) + { + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.StreamQueryHasNoHandler, + msg.Location, + msg.MessageFullyQualifiedName)); + } + break; + + case InterfaceKind.Notification: + if (!handledNotifications.Contains(msg.MessageFullyQualifiedName)) + { + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.NotificationHasNoHandler, + msg.Location, + msg.MessageFullyQualifiedName)); + } + break; + } + } + + // Check for duplicate command handlers + var commandHandlerGroups = handlers + .Where(h => h.Kind == InterfaceKind.ReturningCommand) + .GroupBy(h => h.MessageFullyQualifiedName) + .Where(g => g.Count() > 1); + + foreach (var group in commandHandlerGroups) + { + var handlerNames = string.Join(", ", group.Select(h => h.HandlerFullyQualifiedName)); + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MultipleHandlersForCommand, + Location.None, + group.Key, + handlerNames)); + } + + // Check for duplicate void command handlers + var voidCommandHandlerGroups = handlers + .Where(h => h.Kind == InterfaceKind.VoidCommand) + .GroupBy(h => h.MessageFullyQualifiedName) + .Where(g => g.Count() > 1); + + foreach (var group in voidCommandHandlerGroups) + { + var handlerNames = string.Join(", ", group.Select(h => h.HandlerFullyQualifiedName)); + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MultipleHandlersForCommand, + Location.None, + group.Key, + handlerNames)); + } + + // Check for duplicate query handlers + var queryHandlerGroups = handlers + .Where(h => h.Kind == InterfaceKind.Query) + .GroupBy(h => h.MessageFullyQualifiedName) + .Where(g => g.Count() > 1); + + foreach (var group in queryHandlerGroups) + { + var handlerNames = string.Join(", ", group.Select(h => h.HandlerFullyQualifiedName)); + spc.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MultipleHandlersForQuery, + Location.None, + group.Key, + handlerNames)); + } + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs b/src/Cortex.Mediator.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs new file mode 100644 index 0000000..4a835e6 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs @@ -0,0 +1,55 @@ +using Microsoft.CodeAnalysis; + +namespace Cortex.Mediator.SourceGenerator.Diagnostics +{ + internal static class DiagnosticDescriptors + { + public static readonly DiagnosticDescriptor CommandHasNoHandler = new( + id: "CXMED001", + title: "Command has no handler", + messageFormat: "Command '{0}' has no registered ICommandHandler<{0}, TResult>", + category: "Cortex.Mediator", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor QueryHasNoHandler = new( + id: "CXMED002", + title: "Query has no handler", + messageFormat: "Query '{0}' has no registered IQueryHandler<{0}, TResult>", + category: "Cortex.Mediator", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor StreamQueryHasNoHandler = new( + id: "CXMED003", + title: "StreamQuery has no handler", + messageFormat: "StreamQuery '{0}' has no registered IStreamQueryHandler<{0}, TResult>", + category: "Cortex.Mediator", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor NotificationHasNoHandler = new( + id: "CXMED004", + title: "Notification has no handler", + messageFormat: "Notification '{0}' has no registered INotificationHandler<{0}>", + category: "Cortex.Mediator", + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MultipleHandlersForCommand = new( + id: "CXMED005", + title: "Multiple handlers for same command", + messageFormat: "Command '{0}' has multiple handlers: {1}. Only one handler per command is allowed.", + category: "Cortex.Mediator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MultipleHandlersForQuery = new( + id: "CXMED006", + title: "Multiple handlers for same query", + messageFormat: "Query '{0}' has multiple handlers: {1}. Only one handler per query is allowed.", + category: "Cortex.Mediator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Discovery/HandlerDiscovery.cs b/src/Cortex.Mediator.SourceGenerator/Discovery/HandlerDiscovery.cs new file mode 100644 index 0000000..96275fd --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Discovery/HandlerDiscovery.cs @@ -0,0 +1,61 @@ +using Cortex.Mediator.SourceGenerator.Models; +using Microsoft.CodeAnalysis; +using System.Collections.Immutable; + +namespace Cortex.Mediator.SourceGenerator.Discovery +{ + internal static class HandlerDiscovery + { + private static readonly (string InterfaceName, InterfaceKind Kind, int TypeArgCount)[] HandlerInterfaces = + { + ("Cortex.Mediator.Commands.ICommandHandler", InterfaceKind.ReturningCommand, 2), + ("Cortex.Mediator.Commands.ICommandHandler", InterfaceKind.VoidCommand, 1), + ("Cortex.Mediator.Queries.IQueryHandler", InterfaceKind.Query, 2), + ("Cortex.Mediator.Streaming.IStreamQueryHandler", InterfaceKind.StreamQuery, 2), + ("Cortex.Mediator.Notifications.INotificationHandler", InterfaceKind.Notification, 1), + ("Cortex.Mediator.Processors.IRequestPreProcessor", InterfaceKind.PreProcessor, 1), + ("Cortex.Mediator.Processors.IRequestPostProcessor", InterfaceKind.PostProcessorWithResponse, 2), + ("Cortex.Mediator.Processors.IRequestPostProcessor", InterfaceKind.PostProcessorVoid, 1), + }; + + public static ImmutableArray FindHandlers(INamedTypeSymbol classSymbol) + { + var builder = ImmutableArray.CreateBuilder(); + + if (classSymbol.IsAbstractOrInterface() || classSymbol.IsGenericType) + return builder.ToImmutable(); + + foreach (var iface in classSymbol.AllInterfaces) + { + if (!iface.IsGenericType) + continue; + + var constructed = iface.ConstructedFrom; + var typeArgCount = constructed.TypeArguments.Length; + + foreach (var (interfaceName, kind, expectedArgCount) in HandlerInterfaces) + { + if (typeArgCount != expectedArgCount) + continue; + + if (!constructed.HasFullyQualifiedMetadataName(interfaceName)) + continue; + + var messageType = iface.TypeArguments[0]; + var resultType = typeArgCount >= 2 ? iface.TypeArguments[1] : null; + + var registration = new HandlerRegistration( + kind: kind, + handlerFullyQualifiedName: classSymbol.ToFullyQualifiedString(), + messageFullyQualifiedName: messageType.ToFullyQualifiedString(), + resultFullyQualifiedName: resultType?.ToFullyQualifiedString(), + serviceInterfaceFullyQualifiedName: iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + + builder.Add(registration); + } + } + + return builder.ToImmutable(); + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Discovery/MessageDiscovery.cs b/src/Cortex.Mediator.SourceGenerator/Discovery/MessageDiscovery.cs new file mode 100644 index 0000000..27a52f2 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Discovery/MessageDiscovery.cs @@ -0,0 +1,77 @@ +using Cortex.Mediator.SourceGenerator.Models; +using Microsoft.CodeAnalysis; +using System.Collections.Immutable; + +namespace Cortex.Mediator.SourceGenerator.Discovery +{ + internal static class MessageDiscovery + { + private static readonly (string InterfaceName, InterfaceKind Kind, bool HasResult)[] MessageInterfaces = + { + ("Cortex.Mediator.Commands.ICommand", InterfaceKind.ReturningCommand, true), + ("Cortex.Mediator.Commands.ICommand", InterfaceKind.VoidCommand, false), + ("Cortex.Mediator.Queries.IQuery", InterfaceKind.Query, true), + ("Cortex.Mediator.Streaming.IStreamQuery", InterfaceKind.StreamQuery, true), + ("Cortex.Mediator.Notifications.INotification", InterfaceKind.Notification, false), + }; + + public static ImmutableArray FindMessages(INamedTypeSymbol classSymbol) + { + var builder = ImmutableArray.CreateBuilder(); + + if (classSymbol.IsAbstractOrInterface()) + return builder.ToImmutable(); + + foreach (var iface in classSymbol.AllInterfaces) + { + foreach (var (interfaceName, kind, hasResult) in MessageInterfaces) + { + if (hasResult) + { + if (!iface.IsGenericType || iface.TypeArguments.Length != 1) + continue; + + if (!iface.ConstructedFrom.HasFullyQualifiedMetadataName(interfaceName)) + continue; + + var resultType = iface.TypeArguments[0]; + var location = classSymbol.Locations.Length > 0 + ? classSymbol.Locations[0] + : Location.None; + + builder.Add(new MessageRegistration( + kind: kind, + messageFullyQualifiedName: classSymbol.ToFullyQualifiedString(), + resultFullyQualifiedName: resultType.ToFullyQualifiedString(), + location: location)); + } + else + { + // Non-generic interface: ICommand (void) or INotification + if (iface.IsGenericType) + continue; + + var ifaceName = iface.ToDisplayString(new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)); + + if (ifaceName != interfaceName) + continue; + + var location = classSymbol.Locations.Length > 0 + ? classSymbol.Locations[0] + : Location.None; + + builder.Add(new MessageRegistration( + kind: kind, + messageFullyQualifiedName: classSymbol.ToFullyQualifiedString(), + resultFullyQualifiedName: null, + location: location)); + } + } + } + + return builder.ToImmutable(); + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Discovery/SymbolExtensions.cs b/src/Cortex.Mediator.SourceGenerator/Discovery/SymbolExtensions.cs new file mode 100644 index 0000000..161a989 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Discovery/SymbolExtensions.cs @@ -0,0 +1,34 @@ +using Microsoft.CodeAnalysis; + +namespace Cortex.Mediator.SourceGenerator.Discovery +{ + internal static class SymbolExtensions + { + public static string ToFullyQualifiedString(this ITypeSymbol symbol) + { + return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + public static string ToGlobalPrefixed(this ITypeSymbol symbol) + { + var fqn = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + return fqn; + } + + public static bool HasFullyQualifiedMetadataName(this INamedTypeSymbol symbol, string metadataName) + { + var constructed = symbol.ConstructedFrom; + var name = constructed.ToDisplayString(new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + genericsOptions: SymbolDisplayGenericsOptions.None)); + + return name == metadataName; + } + + public static bool IsAbstractOrInterface(this INamedTypeSymbol symbol) + { + return symbol.IsAbstract || symbol.TypeKind == TypeKind.Interface; + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedDIEmitter.cs b/src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedDIEmitter.cs new file mode 100644 index 0000000..f22cedf --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedDIEmitter.cs @@ -0,0 +1,104 @@ +using Cortex.Mediator.SourceGenerator.Models; +using System.Collections.Immutable; +using System.Linq; + +namespace Cortex.Mediator.SourceGenerator.Emitters +{ + internal static class GeneratedDIEmitter + { + public static string Emit(ImmutableArray handlers) + { + var sb = new SourceTextBuilder(); + + sb.AppendLine("// "); + sb.AppendLine("#nullable enable"); + sb.AppendLine(); + sb.AppendLine("using Cortex.Mediator;"); + sb.AppendLine("using Cortex.Mediator.DependencyInjection;"); + sb.AppendLine("using Cortex.Mediator.Notifications;"); + sb.AppendLine("using Cortex.Mediator.SourceGeneration;"); + sb.AppendLine("using Microsoft.Extensions.DependencyInjection;"); + sb.AppendLine("using System;"); + sb.AppendLine(); + sb.AppendLine("namespace Cortex.Mediator.SourceGeneration"); + sb.OpenBrace(); + + sb.AppendLine("/// "); + sb.AppendLine("/// Source-generated DI registration that avoids assembly scanning."); + sb.AppendLine("/// "); + sb.AppendLine("[System.CodeDom.Compiler.GeneratedCodeAttribute(\"Cortex.Mediator.SourceGenerator\", \"1.0.0\")]"); + sb.AppendLine("internal static class GeneratedServiceCollectionExtensions"); + sb.OpenBrace(); + + sb.AppendLine("public static IServiceCollection AddCortexGeneratedMediator("); + sb.AppendLine(" this IServiceCollection services,"); + sb.AppendLine(" Action? configure = null)"); + sb.OpenBrace(); + + sb.AppendLine("var options = new MediatorOptions();"); + sb.AppendLine("configure?.Invoke(options);"); + sb.AppendLine(); + + sb.AppendLine("// Register the generated mediator"); + sb.AppendLine("services.AddScoped();"); + sb.AppendLine(); + + sb.AppendLine("// Register notification publish strategy"); + sb.AppendLine("services.AddSingleton(typeof(INotificationPublishStrategy), options.NotificationPublishStrategyType);"); + sb.AppendLine(); + + // Group handlers by kind for readability + var handlerKinds = new[] + { + InterfaceKind.ReturningCommand, + InterfaceKind.VoidCommand, + InterfaceKind.Query, + InterfaceKind.StreamQuery, + InterfaceKind.Notification, + }; + + var processorKinds = new[] + { + InterfaceKind.PreProcessor, + InterfaceKind.PostProcessorWithResponse, + InterfaceKind.PostProcessorVoid, + }; + + // Emit handler registrations + var handlerRegs = handlers.Where(h => handlerKinds.Contains(h.Kind)).ToArray(); + if (handlerRegs.Length > 0) + { + sb.AppendLine("// Handler registrations"); + foreach (var reg in handlerRegs) + { + sb.AppendLine($"services.Add(new ServiceDescriptor(typeof({reg.ServiceInterfaceFullyQualifiedName}), typeof({reg.HandlerFullyQualifiedName}), ServiceLifetime.Scoped));"); + } + sb.AppendLine(); + } + + // Emit processor registrations + var processorRegs = handlers.Where(h => processorKinds.Contains(h.Kind)).ToArray(); + if (processorRegs.Length > 0) + { + sb.AppendLine("// Processor registrations"); + foreach (var reg in processorRegs) + { + sb.AppendLine($"services.Add(new ServiceDescriptor(typeof({reg.ServiceInterfaceFullyQualifiedName}), typeof({reg.HandlerFullyQualifiedName}), ServiceLifetime.Transient));"); + } + sb.AppendLine(); + } + + sb.AppendLine("// Register pipeline behaviors (runtime-configured, cannot be generated)"); + sb.AppendLine("ServiceCollectionExtensions.RegisterPipelineBehaviors(services, options);"); + sb.AppendLine(); + sb.AppendLine("return services;"); + + sb.CloseBrace(); // method + + sb.CloseBrace(); // class + sb.CloseBrace(); // namespace + + return sb.ToString(); + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedMediatorEmitter.cs b/src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedMediatorEmitter.cs new file mode 100644 index 0000000..d6722dc --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Emitters/GeneratedMediatorEmitter.cs @@ -0,0 +1,459 @@ +using Cortex.Mediator.SourceGenerator.Models; +using System.Collections.Immutable; +using System.Linq; + +namespace Cortex.Mediator.SourceGenerator.Emitters +{ + internal static class GeneratedMediatorEmitter + { + public static string Emit(ImmutableArray handlers) + { + var returningCommands = handlers + .Where(h => h.Kind == InterfaceKind.ReturningCommand) + .ToArray(); + + var voidCommands = handlers + .Where(h => h.Kind == InterfaceKind.VoidCommand) + .ToArray(); + + var queries = handlers + .Where(h => h.Kind == InterfaceKind.Query) + .ToArray(); + + var streamQueries = handlers + .Where(h => h.Kind == InterfaceKind.StreamQuery) + .ToArray(); + + var notifications = handlers + .Where(h => h.Kind == InterfaceKind.Notification) + .ToArray(); + + var sb = new SourceTextBuilder(); + + sb.AppendLine("// "); + sb.AppendLine("#nullable enable"); + sb.AppendLine(); + sb.AppendLine("using Cortex.Mediator;"); + sb.AppendLine("using Cortex.Mediator.Commands;"); + sb.AppendLine("using Cortex.Mediator.Notifications;"); + sb.AppendLine("using Cortex.Mediator.Queries;"); + sb.AppendLine("using Cortex.Mediator.Streaming;"); + sb.AppendLine("using Microsoft.Extensions.DependencyInjection;"); + sb.AppendLine("using System;"); + sb.AppendLine("using System.Collections.Generic;"); + sb.AppendLine("using System.Linq;"); + sb.AppendLine("using System.Threading;"); + sb.AppendLine("using System.Threading.Tasks;"); + sb.AppendLine(); + sb.AppendLine("namespace Cortex.Mediator.SourceGeneration"); + sb.OpenBrace(); + + sb.AppendLine("/// "); + sb.AppendLine("/// Source-generated mediator that uses compile-time dispatch instead of runtime reflection."); + sb.AppendLine("/// "); + sb.AppendLine("[System.CodeDom.Compiler.GeneratedCodeAttribute(\"Cortex.Mediator.SourceGenerator\", \"1.0.0\")]"); + sb.AppendLine("internal sealed class GeneratedMediator : IMediator"); + sb.OpenBrace(); + + sb.AppendLine("private readonly IServiceProvider _serviceProvider;"); + sb.AppendLine(); + sb.AppendLine("public GeneratedMediator(IServiceProvider serviceProvider)"); + sb.OpenBrace(); + sb.AppendLine("_serviceProvider = serviceProvider;"); + sb.CloseBrace(); + + // SendCommandAsync - strongly typed (no reflection needed) + sb.AppendLine(); + EmitStronglyTypedSendCommandReturning(sb); + + // SendCommandAsync(ICommand) - switch dispatch + sb.AppendLine(); + EmitSwitchSendCommandReturning(sb, returningCommands); + + // SendCommandAsync(TCommand) - strongly typed (no reflection needed) + sb.AppendLine(); + EmitStronglyTypedSendCommandVoid(sb); + + // SendCommandAsync(ICommand) - switch dispatch + sb.AppendLine(); + EmitSwitchSendCommandVoid(sb, voidCommands); + + // SendQueryAsync - strongly typed + sb.AppendLine(); + EmitStronglyTypedSendQuery(sb); + + // SendQueryAsync(IQuery) - switch dispatch + sb.AppendLine(); + EmitSwitchSendQuery(sb, queries); + + // PublishAsync - strongly typed + sb.AppendLine(); + EmitStronglyTypedPublish(sb); + + // PublishAsync(INotification) - switch dispatch + sb.AppendLine(); + EmitSwitchPublish(sb, notifications); + + // CreateStream - strongly typed + sb.AppendLine(); + EmitStronglyTypedCreateStream(sb); + + // CreateStream(IStreamQuery) - switch dispatch + sb.AppendLine(); + EmitSwitchCreateStream(sb, streamQueries); + + // Pipeline wrapper classes + sb.AppendLine(); + EmitPipelineWrapperClasses(sb); + + sb.CloseBrace(); // class + sb.CloseBrace(); // namespace + + return sb.ToString(); + } + + private static void EmitStronglyTypedSendCommandReturning(SourceTextBuilder sb) + { + sb.AppendLine("public async Task SendCommandAsync(TCommand command, CancellationToken cancellationToken = default)"); + sb.AppendLine(" where TCommand : ICommand"); + sb.OpenBrace(); + sb.AppendLine("var handler = _serviceProvider.GetRequiredService>();"); + sb.AppendLine(); + sb.AppendLine("foreach (var behavior in _serviceProvider.GetServices>().Reverse())"); + sb.OpenBrace(); + sb.AppendLine("handler = new PipelineBehaviorNextDelegate(behavior, handler);"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("return await handler.Handle(command, cancellationToken);"); + sb.CloseBrace(); + } + + private static void EmitSwitchSendCommandReturning(SourceTextBuilder sb, HandlerRegistration[] registrations) + { + sb.AppendLine("public Task SendCommandAsync(ICommand command, CancellationToken cancellationToken = default)"); + sb.OpenBrace(); + sb.AppendLine("if (command == null) throw new ArgumentNullException(nameof(command));"); + sb.AppendLine(); + + if (registrations.Length > 0) + { + sb.AppendLine("switch (command)"); + sb.OpenBrace(); + foreach (var reg in registrations) + { + sb.AppendLine($"case {reg.MessageFullyQualifiedName} typed:"); + sb.Indent(); + sb.AppendLine($"return (Task)(object)SendCommandAsync<{reg.MessageFullyQualifiedName}, {reg.ResultFullyQualifiedName}>(typed, cancellationToken);"); + sb.Unindent(); + } + sb.AppendLine("default:"); + sb.Indent(); + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for command type {command.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + sb.Unindent(); + sb.CloseBrace(); + } + else + { + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for command type {command.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + } + + sb.CloseBrace(); + } + + private static void EmitStronglyTypedSendCommandVoid(SourceTextBuilder sb) + { + sb.AppendLine("public async Task SendCommandAsync(TCommand command, CancellationToken cancellationToken = default)"); + sb.AppendLine(" where TCommand : ICommand"); + sb.OpenBrace(); + sb.AppendLine("var handler = _serviceProvider.GetRequiredService>();"); + sb.AppendLine(); + sb.AppendLine("foreach (var behavior in _serviceProvider.GetServices>().Reverse())"); + sb.OpenBrace(); + sb.AppendLine("handler = new VoidPipelineBehaviorNextDelegate(behavior, handler);"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("await handler.Handle(command, cancellationToken);"); + sb.CloseBrace(); + } + + private static void EmitSwitchSendCommandVoid(SourceTextBuilder sb, HandlerRegistration[] registrations) + { + sb.AppendLine("public Task SendCommandAsync(ICommand command, CancellationToken cancellationToken = default)"); + sb.OpenBrace(); + sb.AppendLine("if (command == null) throw new ArgumentNullException(nameof(command));"); + sb.AppendLine(); + + if (registrations.Length > 0) + { + sb.AppendLine("switch (command)"); + sb.OpenBrace(); + foreach (var reg in registrations) + { + sb.AppendLine($"case {reg.MessageFullyQualifiedName} typed:"); + sb.Indent(); + sb.AppendLine($"return SendCommandAsync<{reg.MessageFullyQualifiedName}>(typed, cancellationToken);"); + sb.Unindent(); + } + sb.AppendLine("default:"); + sb.Indent(); + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for command type {command.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + sb.Unindent(); + sb.CloseBrace(); + } + else + { + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for command type {command.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + } + + sb.CloseBrace(); + } + + private static void EmitStronglyTypedSendQuery(SourceTextBuilder sb) + { + sb.AppendLine("public async Task SendQueryAsync(TQuery query, CancellationToken cancellationToken = default)"); + sb.AppendLine(" where TQuery : IQuery"); + sb.OpenBrace(); + sb.AppendLine("var handler = _serviceProvider.GetRequiredService>();"); + sb.AppendLine(); + sb.AppendLine("foreach (var behavior in _serviceProvider.GetServices>().Reverse())"); + sb.OpenBrace(); + sb.AppendLine("handler = new QueryPipelineBehaviorNextDelegate(behavior, handler);"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("return await handler.Handle(query, cancellationToken);"); + sb.CloseBrace(); + } + + private static void EmitSwitchSendQuery(SourceTextBuilder sb, HandlerRegistration[] registrations) + { + sb.AppendLine("public Task SendQueryAsync(IQuery query, CancellationToken cancellationToken = default)"); + sb.OpenBrace(); + sb.AppendLine("if (query == null) throw new ArgumentNullException(nameof(query));"); + sb.AppendLine(); + + if (registrations.Length > 0) + { + sb.AppendLine("switch (query)"); + sb.OpenBrace(); + foreach (var reg in registrations) + { + sb.AppendLine($"case {reg.MessageFullyQualifiedName} typed:"); + sb.Indent(); + sb.AppendLine($"return (Task)(object)SendQueryAsync<{reg.MessageFullyQualifiedName}, {reg.ResultFullyQualifiedName}>(typed, cancellationToken);"); + sb.Unindent(); + } + sb.AppendLine("default:"); + sb.Indent(); + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for query type {query.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + sb.Unindent(); + sb.CloseBrace(); + } + else + { + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for query type {query.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + } + + sb.CloseBrace(); + } + + private static void EmitStronglyTypedPublish(SourceTextBuilder sb) + { + sb.AppendLine("public async Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default)"); + sb.AppendLine(" where TNotification : INotification"); + sb.OpenBrace(); + sb.AppendLine("var handlers = _serviceProvider.GetServices>();"); + sb.AppendLine("var behaviors = _serviceProvider.GetServices>();"); + sb.AppendLine(); + sb.AppendLine("var behaviorList = behaviors as INotificationPipelineBehavior[] ?? behaviors.ToArray();"); + sb.AppendLine("Array.Reverse(behaviorList);"); + sb.AppendLine(); + sb.AppendLine("var handlerList = handlers as INotificationHandler[] ?? handlers.ToArray();"); + sb.AppendLine("if (handlerList.Length == 0) return;"); + sb.AppendLine(); + sb.AppendLine("var handlerDelegates = new Func[handlerList.Length];"); + sb.AppendLine("for (int i = 0; i < handlerList.Length; i++)"); + sb.OpenBrace(); + sb.AppendLine("var handler = handlerList[i];"); + sb.AppendLine("NotificationHandlerDelegate handlerDelegate = () => handler.Handle(notification, cancellationToken);"); + sb.AppendLine(); + sb.AppendLine("foreach (var behavior in behaviorList)"); + sb.OpenBrace(); + sb.AppendLine("var currentDelegate = handlerDelegate;"); + sb.AppendLine("var currentBehavior = behavior;"); + sb.AppendLine("handlerDelegate = () => currentBehavior.Handle(notification, currentDelegate, cancellationToken);"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("var finalDelegate = handlerDelegate;"); + sb.AppendLine("handlerDelegates[i] = () => finalDelegate();"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("var strategy = _serviceProvider.GetService();"); + sb.AppendLine("if (strategy != null)"); + sb.OpenBrace(); + sb.AppendLine("await strategy.PublishAsync(handlerDelegates, cancellationToken);"); + sb.CloseBrace(); + sb.AppendLine("else"); + sb.OpenBrace(); + sb.AppendLine("await Task.WhenAll(handlerDelegates.Select(d => d()));"); + sb.CloseBrace(); + sb.CloseBrace(); + } + + private static void EmitSwitchPublish(SourceTextBuilder sb, HandlerRegistration[] registrations) + { + sb.AppendLine("public Task PublishAsync(INotification notification, CancellationToken cancellationToken = default)"); + sb.OpenBrace(); + sb.AppendLine("if (notification == null) throw new ArgumentNullException(nameof(notification));"); + sb.AppendLine(); + + if (registrations.Length > 0) + { + sb.AppendLine("switch (notification)"); + sb.OpenBrace(); + // Deduplicate notification types (multiple handlers for same notification) + var distinctNotifications = registrations + .Select(r => r.MessageFullyQualifiedName) + .Distinct() + .ToArray(); + foreach (var msgFqn in distinctNotifications) + { + sb.AppendLine($"case {msgFqn} typed:"); + sb.Indent(); + sb.AppendLine($"return PublishAsync<{msgFqn}>(typed, cancellationToken);"); + sb.Unindent(); + } + sb.AppendLine("default:"); + sb.Indent(); + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for notification type {notification.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + sb.Unindent(); + sb.CloseBrace(); + } + else + { + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for notification type {notification.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + } + + sb.CloseBrace(); + } + + private static void EmitStronglyTypedCreateStream(SourceTextBuilder sb) + { + sb.AppendLine("public IAsyncEnumerable CreateStream(TQuery query, CancellationToken cancellationToken = default)"); + sb.AppendLine(" where TQuery : IStreamQuery"); + sb.OpenBrace(); + sb.AppendLine("if (query == null) throw new ArgumentNullException(nameof(query));"); + sb.AppendLine(); + sb.AppendLine("var handler = _serviceProvider.GetRequiredService>();"); + sb.AppendLine("var behaviors = _serviceProvider.GetServices>();"); + sb.AppendLine(); + sb.AppendLine("StreamQueryHandlerDelegate handlerDelegate = () => handler.Handle(query, cancellationToken);"); + sb.AppendLine(); + sb.AppendLine("var behaviorArray = behaviors as IStreamQueryPipelineBehavior[] ?? behaviors.ToArray();"); + sb.AppendLine("for (int i = behaviorArray.Length - 1; i >= 0; i--)"); + sb.OpenBrace(); + sb.AppendLine("var currentDelegate = handlerDelegate;"); + sb.AppendLine("var currentBehavior = behaviorArray[i];"); + sb.AppendLine("handlerDelegate = () => currentBehavior.Handle(query, currentDelegate, cancellationToken);"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("return handlerDelegate();"); + sb.CloseBrace(); + } + + private static void EmitSwitchCreateStream(SourceTextBuilder sb, HandlerRegistration[] registrations) + { + sb.AppendLine("public IAsyncEnumerable CreateStream(IStreamQuery query, CancellationToken cancellationToken = default)"); + sb.OpenBrace(); + sb.AppendLine("if (query == null) throw new ArgumentNullException(nameof(query));"); + sb.AppendLine(); + + if (registrations.Length > 0) + { + sb.AppendLine("switch (query)"); + sb.OpenBrace(); + foreach (var reg in registrations) + { + sb.AppendLine($"case {reg.MessageFullyQualifiedName} typed:"); + sb.Indent(); + sb.AppendLine($"return (IAsyncEnumerable)CreateStream<{reg.MessageFullyQualifiedName}, {reg.ResultFullyQualifiedName}>(typed, cancellationToken);"); + sb.Unindent(); + } + sb.AppendLine("default:"); + sb.Indent(); + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for stream query type {query.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + sb.Unindent(); + sb.CloseBrace(); + } + else + { + sb.AppendLine("throw new InvalidOperationException($\"No handler registered for stream query type {query.GetType().FullName}. Ensure the handler is in the same compilation where [assembly: CortexMediatorGeneration] is applied.\");"); + } + + sb.CloseBrace(); + } + + private static void EmitPipelineWrapperClasses(SourceTextBuilder sb) + { + // PipelineBehaviorNextDelegate + sb.AppendLine("private sealed class PipelineBehaviorNextDelegate : ICommandHandler"); + sb.AppendLine(" where TCommand : ICommand"); + sb.OpenBrace(); + sb.AppendLine("private readonly ICommandPipelineBehavior _behavior;"); + sb.AppendLine("private readonly ICommandHandler _next;"); + sb.AppendLine(); + sb.AppendLine("public PipelineBehaviorNextDelegate(ICommandPipelineBehavior behavior, ICommandHandler next)"); + sb.OpenBrace(); + sb.AppendLine("_behavior = behavior;"); + sb.AppendLine("_next = next;"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("public Task Handle(TCommand command, CancellationToken cancellationToken)"); + sb.OpenBrace(); + sb.AppendLine("return _behavior.Handle(command, () => _next.Handle(command, cancellationToken), cancellationToken);"); + sb.CloseBrace(); + sb.CloseBrace(); + + sb.AppendLine(); + + // VoidPipelineBehaviorNextDelegate + sb.AppendLine("private sealed class VoidPipelineBehaviorNextDelegate : ICommandHandler"); + sb.AppendLine(" where TCommand : ICommand"); + sb.OpenBrace(); + sb.AppendLine("private readonly ICommandPipelineBehavior _behavior;"); + sb.AppendLine("private readonly ICommandHandler _next;"); + sb.AppendLine(); + sb.AppendLine("public VoidPipelineBehaviorNextDelegate(ICommandPipelineBehavior behavior, ICommandHandler next)"); + sb.OpenBrace(); + sb.AppendLine("_behavior = behavior;"); + sb.AppendLine("_next = next;"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("public Task Handle(TCommand command, CancellationToken cancellationToken)"); + sb.OpenBrace(); + sb.AppendLine("return _behavior.Handle(command, () => _next.Handle(command, cancellationToken), cancellationToken);"); + sb.CloseBrace(); + sb.CloseBrace(); + + sb.AppendLine(); + + // QueryPipelineBehaviorNextDelegate + sb.AppendLine("private sealed class QueryPipelineBehaviorNextDelegate : IQueryHandler"); + sb.AppendLine(" where TQuery : IQuery"); + sb.OpenBrace(); + sb.AppendLine("private readonly IQueryPipelineBehavior _behavior;"); + sb.AppendLine("private readonly IQueryHandler _next;"); + sb.AppendLine(); + sb.AppendLine("public QueryPipelineBehaviorNextDelegate(IQueryPipelineBehavior behavior, IQueryHandler next)"); + sb.OpenBrace(); + sb.AppendLine("_behavior = behavior;"); + sb.AppendLine("_next = next;"); + sb.CloseBrace(); + sb.AppendLine(); + sb.AppendLine("public Task Handle(TQuery query, CancellationToken cancellationToken)"); + sb.OpenBrace(); + sb.AppendLine("return _behavior.Handle(query, () => _next.Handle(query, cancellationToken), cancellationToken);"); + sb.CloseBrace(); + sb.CloseBrace(); + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Emitters/SourceTextBuilder.cs b/src/Cortex.Mediator.SourceGenerator/Emitters/SourceTextBuilder.cs new file mode 100644 index 0000000..ef72823 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Emitters/SourceTextBuilder.cs @@ -0,0 +1,52 @@ +using System.Text; + +namespace Cortex.Mediator.SourceGenerator.Emitters +{ + internal sealed class SourceTextBuilder + { + private readonly StringBuilder _sb = new StringBuilder(); + private int _indent; + + public SourceTextBuilder Indent() + { + _indent++; + return this; + } + + public SourceTextBuilder Unindent() + { + if (_indent > 0) _indent--; + return this; + } + + public SourceTextBuilder AppendLine(string line = "") + { + if (string.IsNullOrEmpty(line)) + { + _sb.AppendLine(); + } + else + { + _sb.Append(new string(' ', _indent * 4)); + _sb.AppendLine(line); + } + return this; + } + + public SourceTextBuilder OpenBrace() + { + AppendLine("{"); + Indent(); + return this; + } + + public SourceTextBuilder CloseBrace(string suffix = "") + { + Unindent(); + AppendLine("}" + suffix); + return this; + } + + public override string ToString() => _sb.ToString(); + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Models/HandlerRegistration.cs b/src/Cortex.Mediator.SourceGenerator/Models/HandlerRegistration.cs new file mode 100644 index 0000000..ea611ac --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Models/HandlerRegistration.cs @@ -0,0 +1,54 @@ +using System; + +namespace Cortex.Mediator.SourceGenerator.Models +{ + internal sealed class HandlerRegistration : IEquatable + { + public InterfaceKind Kind { get; } + public string HandlerFullyQualifiedName { get; } + public string MessageFullyQualifiedName { get; } + public string ResultFullyQualifiedName { get; } + public string ServiceInterfaceFullyQualifiedName { get; } + + public HandlerRegistration( + InterfaceKind kind, + string handlerFullyQualifiedName, + string messageFullyQualifiedName, + string resultFullyQualifiedName, + string serviceInterfaceFullyQualifiedName) + { + Kind = kind; + HandlerFullyQualifiedName = handlerFullyQualifiedName; + MessageFullyQualifiedName = messageFullyQualifiedName; + ResultFullyQualifiedName = resultFullyQualifiedName ?? ""; + ServiceInterfaceFullyQualifiedName = serviceInterfaceFullyQualifiedName; + } + + public bool Equals(HandlerRegistration other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Kind == other.Kind + && HandlerFullyQualifiedName == other.HandlerFullyQualifiedName + && MessageFullyQualifiedName == other.MessageFullyQualifiedName + && ResultFullyQualifiedName == other.ResultFullyQualifiedName + && ServiceInterfaceFullyQualifiedName == other.ServiceInterfaceFullyQualifiedName; + } + + public override bool Equals(object obj) => Equals(obj as HandlerRegistration); + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 31 + Kind.GetHashCode(); + hash = hash * 31 + HandlerFullyQualifiedName.GetHashCode(); + hash = hash * 31 + MessageFullyQualifiedName.GetHashCode(); + hash = hash * 31 + ResultFullyQualifiedName.GetHashCode(); + hash = hash * 31 + ServiceInterfaceFullyQualifiedName.GetHashCode(); + return hash; + } + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Models/InterfaceKind.cs b/src/Cortex.Mediator.SourceGenerator/Models/InterfaceKind.cs new file mode 100644 index 0000000..d501bab --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Models/InterfaceKind.cs @@ -0,0 +1,14 @@ +namespace Cortex.Mediator.SourceGenerator.Models +{ + internal enum InterfaceKind + { + ReturningCommand, + VoidCommand, + Query, + StreamQuery, + Notification, + PreProcessor, + PostProcessorWithResponse, + PostProcessorVoid + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/Models/MessageRegistration.cs b/src/Cortex.Mediator.SourceGenerator/Models/MessageRegistration.cs new file mode 100644 index 0000000..353c141 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/Models/MessageRegistration.cs @@ -0,0 +1,47 @@ +using System; + +namespace Cortex.Mediator.SourceGenerator.Models +{ + internal sealed class MessageRegistration : IEquatable + { + public InterfaceKind Kind { get; } + public string MessageFullyQualifiedName { get; } + public string ResultFullyQualifiedName { get; } + public Microsoft.CodeAnalysis.Location Location { get; } + + public MessageRegistration( + InterfaceKind kind, + string messageFullyQualifiedName, + string resultFullyQualifiedName, + Microsoft.CodeAnalysis.Location location) + { + Kind = kind; + MessageFullyQualifiedName = messageFullyQualifiedName; + ResultFullyQualifiedName = resultFullyQualifiedName ?? ""; + Location = location; + } + + public bool Equals(MessageRegistration other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Kind == other.Kind + && MessageFullyQualifiedName == other.MessageFullyQualifiedName + && ResultFullyQualifiedName == other.ResultFullyQualifiedName; + } + + public override bool Equals(object obj) => Equals(obj as MessageRegistration); + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 31 + Kind.GetHashCode(); + hash = hash * 31 + MessageFullyQualifiedName.GetHashCode(); + hash = hash * 31 + ResultFullyQualifiedName.GetHashCode(); + return hash; + } + } + } +} diff --git a/src/Cortex.Mediator.SourceGenerator/README.md b/src/Cortex.Mediator.SourceGenerator/README.md new file mode 100644 index 0000000..5d3bf27 --- /dev/null +++ b/src/Cortex.Mediator.SourceGenerator/README.md @@ -0,0 +1,182 @@ +# Cortex.Mediator.SourceGenerator + +**Cortex.Mediator.SourceGenerator** is an optional Roslyn incremental source generator for [Cortex.Mediator](https://www.nuget.org/packages/Cortex.Mediator) that eliminates runtime reflection by generating compile-time dispatch. + +Built as part of the [Cortex Data Framework](https://github.com/buildersoftio/cortex), this package replaces the five `ConcurrentDictionary` caches and `MakeGenericMethod` + `Invoke` calls in the default `Mediator` with a `GeneratedMediator` class that uses `switch`-based routing and explicit DI registrations. + +--- + +[![GitHub License](https://img.shields.io/github/license/buildersoftio/cortex)](https://github.com/buildersoftio/cortex/blob/master/LICENSE) +[![NuGet Version](https://img.shields.io/nuget/v/Cortex.Mediator?label=Cortex.Mediator)](https://www.nuget.org/packages/Cortex.Mediator) +[![GitHub contributors](https://img.shields.io/github/contributors/buildersoftio/cortex)](https://github.com/buildersoftio/cortex) +[![Discord Shield](https://discord.com/api/guilds/1310034212371566612/widget.png?style=shield)](https://discord.gg/JnMJV33QHu) + +## Why Use This? + +| Aspect | Default `Mediator` | `GeneratedMediator` | +|--------|-------------------|---------------------| +| Non-generic dispatch | Runtime reflection (`MakeGenericMethod`) | Compile-time `switch` | +| Cold start | Dictionary allocation + reflection lookup | Zero overhead | +| DI registrations | Assembly scanning at startup | Explicit `ServiceDescriptor` calls | +| NativeAOT / Trimming | Incompatible (reflection) | Compatible | +| Pipeline behaviors | Fully supported | Fully supported (identical logic) | + +## Getting Started + +### Install via NuGet + +```bash +dotnet add package Cortex.Mediator +dotnet add package Cortex.Mediator.SourceGenerator +``` + +Or in your `.csproj`: + +```xml + + +``` + +### Enable the Generator + +Add the assembly attribute in any `.cs` file in your project: + +```csharp +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] +``` + +### Register Services + +Replace `AddCortexMediator` with `AddCortexGeneratedMediator`: + +```csharp +// Before (reflection-based): +services.AddCortexMediator( + new[] { typeof(Program) }, + options => options.AddDefaultBehaviors() +); + +// After (source-generated): +services.AddCortexGeneratedMediator(options => +{ + options.AddDefaultBehaviors(); +}); +``` + +No assembly marker types needed — the generator discovers all handlers at compile time. + +## What Gets Generated + +### `GeneratedMediator.g.cs` + +A class implementing `IMediator` with switch-based dispatch for all non-generic overloads: + +```csharp +public Task SendCommandAsync(ICommand command, CancellationToken ct) +{ + switch (command) + { + case CreateUserCommand typed: + return (Task)(object)SendCommandAsync(typed, ct); + case UpdateUserCommand typed: + return (Task)(object)SendCommandAsync(typed, ct); + default: + throw new InvalidOperationException(...); + } +} +``` + +The strongly-typed methods (`SendCommandAsync`, etc.) use the same pipeline behavior wrapping as the default `Mediator` — no reflection involved. + +### `GeneratedServiceCollectionExtensions.g.cs` + +Explicit DI registrations for every discovered handler and processor: + +```csharp +public static IServiceCollection AddCortexGeneratedMediator( + this IServiceCollection services, + Action? configure = null) +{ + services.AddScoped(); + services.AddSingleton(typeof(INotificationPublishStrategy), options.NotificationPublishStrategyType); + + // Explicit handler registrations (no assembly scanning) + services.Add(new ServiceDescriptor( + typeof(ICommandHandler), + typeof(CreateUserCommandHandler), + ServiceLifetime.Scoped)); + // ... + + // Pipeline behaviors are still runtime-configured + ServiceCollectionExtensions.RegisterPipelineBehaviors(services, options); + return services; +} +``` + +## Compile-Time Diagnostics + +The generator reports diagnostics during compilation: + +| ID | Severity | Condition | +|----|----------|-----------| +| `CXMED001` | Warning | Command has no handler | +| `CXMED002` | Warning | Query has no handler | +| `CXMED003` | Warning | StreamQuery has no handler | +| `CXMED004` | Info | Notification has no handler (zero handlers is valid) | +| `CXMED005` | Error | Multiple handlers for same command | +| `CXMED006` | Error | Multiple handlers for same query | + +## Supported Handler Types + +The generator discovers all handler and processor implementations: + +- `ICommandHandler` — Returning commands +- `ICommandHandler` — Void commands +- `IQueryHandler` — Queries +- `IStreamQueryHandler` — Streaming queries +- `INotificationHandler` — Notifications +- `IRequestPreProcessor` — Pre-processors +- `IRequestPostProcessor` — Post-processors with response +- `IRequestPostProcessor` — Post-processors for void commands + +## Scope & Limitations + +- **Single compilation**: The generator scans handlers in the compilation where `[assembly: CortexMediatorGeneration]` is applied. Cross-assembly handlers are not discovered. +- **Pipeline behaviors**: Configured at runtime via `MediatorOptions`, not generated (they use open generics). +- **Fallback**: The existing reflection-based `Mediator` remains the default. The generator is opt-in. + +## Contributing + +We welcome contributions from the community! Whether it's reporting bugs, suggesting features, or submitting pull requests, your involvement helps improve Cortex for everyone. + +### How to Contribute +1. **Fork the Repository** +2. **Create a Feature Branch** +```bash +git checkout -b feature/YourFeature +``` +3. **Commit Your Changes** +```bash +git commit -m "Add your feature" +``` +4. **Push to Your Fork** +```bash +git push origin feature/YourFeature +``` +5. **Open a Pull Request** + +Describe your changes and submit the pull request for review. + +## License +This project is licensed under the MIT License. + +## Contact +- Email: cortex@buildersoft.io +- Website: https://buildersoft.io +- GitHub Issues: [Cortex Data Framework Issues](https://github.com/buildersoftio/cortex/issues) +- Join our Discord Community: [![Discord Shield](https://discord.com/api/guilds/1310034212371566612/widget.png?style=shield)](https://discord.gg/JnMJV33QHu) + +Thank you for using Cortex Data Framework! We hope it empowers you to build scalable and efficient data processing pipelines effortlessly. + +Built with love by the Buildersoft team. diff --git a/src/Cortex.Mediator/Cortex.Mediator.csproj b/src/Cortex.Mediator/Cortex.Mediator.csproj index e0e6275..bbe3f66 100644 --- a/src/Cortex.Mediator/Cortex.Mediator.csproj +++ b/src/Cortex.Mediator/Cortex.Mediator.csproj @@ -67,5 +67,6 @@ + diff --git a/src/Cortex.Mediator/DependencyInjection/MediatorOptions.cs b/src/Cortex.Mediator/DependencyInjection/MediatorOptions.cs index bd2ad5e..553be84 100644 --- a/src/Cortex.Mediator/DependencyInjection/MediatorOptions.cs +++ b/src/Cortex.Mediator/DependencyInjection/MediatorOptions.cs @@ -31,7 +31,7 @@ public class MediatorOptions /// Defaults to . /// Use to change. /// - internal Type NotificationPublishStrategyType { get; private set; } = typeof(ParallelNotificationStrategy); + public Type NotificationPublishStrategyType { get; private set; } = typeof(ParallelNotificationStrategy); /// /// Sets the strategy used to publish notifications to multiple handlers. diff --git a/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs b/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs index 60e0c06..78a19e9 100644 --- a/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Cortex.Mediator/DependencyInjection/ServiceCollectionExtensions.cs @@ -111,7 +111,7 @@ private static void ScanAndRegister( } } - private static void RegisterPipelineBehaviors(IServiceCollection services, MediatorOptions options) + public static void RegisterPipelineBehaviors(IServiceCollection services, MediatorOptions options) { // Sort each behavior list by Order (stable sort preserves registration order for equal values). // OrderBy in LINQ is a stable sort. diff --git a/src/Cortex.Tests/Cortex.Tests.csproj b/src/Cortex.Tests/Cortex.Tests.csproj index 24f83f5..d216e09 100644 --- a/src/Cortex.Tests/Cortex.Tests.csproj +++ b/src/Cortex.Tests/Cortex.Tests.csproj @@ -23,10 +23,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/Cortex.Tests/Mediator/SourceGenerator/DiagnosticTests.cs b/src/Cortex.Tests/Mediator/SourceGenerator/DiagnosticTests.cs new file mode 100644 index 0000000..5f9472c --- /dev/null +++ b/src/Cortex.Tests/Mediator/SourceGenerator/DiagnosticTests.cs @@ -0,0 +1,254 @@ +using Cortex.Mediator.SourceGenerator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Reflection; + +namespace Cortex.Tests.Mediator.SourceGenerator; + +public class DiagnosticTests +{ + private static readonly MetadataReference[] SharedReferences = GetSharedReferences(); + + private static MetadataReference[] GetSharedReferences() + { + var assemblies = new[] + { + typeof(object).Assembly, + typeof(Task).Assembly, + typeof(IServiceProvider).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions).Assembly, + typeof(Cortex.Mediator.IMediator).Assembly, + Assembly.Load("System.Runtime"), + Assembly.Load("System.Collections"), + Assembly.Load("System.Linq"), + Assembly.Load("netstandard"), + }; + + return assemblies.Select(a => MetadataReference.CreateFromFile(a.Location)) + .Cast() + .ToArray(); + } + + private static GeneratorDriverRunResult RunGenerator(string source) + { + var syntaxTree = CSharpSyntaxTree.ParseText(source); + + var compilation = CSharpCompilation.Create( + assemblyName: "TestAssembly", + syntaxTrees: new[] { syntaxTree }, + references: SharedReferences, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + var generator = new CortexMediatorGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + + driver = driver.RunGeneratorsAndUpdateCompilation( + compilation, + out _, + out _); + + return driver.GetRunResult(); + } + + [Fact] + public void CXMED001_CommandWithNoHandler_ProducesWarning() + { + var source = @" +using Cortex.Mediator.Commands; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class OrphanCommand : ICommand { } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.Contains(diagnostics, d => d.Id == "CXMED001"); + } + + [Fact] + public void CXMED001_VoidCommandWithNoHandler_ProducesWarning() + { + var source = @" +using Cortex.Mediator.Commands; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class OrphanVoidCommand : ICommand { } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.Contains(diagnostics, d => d.Id == "CXMED001"); + } + + [Fact] + public void CXMED002_QueryWithNoHandler_ProducesWarning() + { + var source = @" +using Cortex.Mediator.Queries; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class OrphanQuery : IQuery { } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.Contains(diagnostics, d => d.Id == "CXMED002"); + } + + [Fact] + public void CXMED003_StreamQueryWithNoHandler_ProducesWarning() + { + var source = @" +using Cortex.Mediator.Streaming; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class OrphanStreamQuery : IStreamQuery { } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.Contains(diagnostics, d => d.Id == "CXMED003"); + } + + [Fact] + public void CXMED004_NotificationWithNoHandler_ProducesInfo() + { + var source = @" +using Cortex.Mediator.Notifications; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class OrphanNotification : INotification { } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.Contains(diagnostics, d => d.Id == "CXMED004"); + } + + [Fact] + public void CXMED005_MultipleHandlersForSameCommand_ProducesError() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyCommand : ICommand { } + + public class Handler1 : ICommandHandler + { + public Task Handle(MyCommand cmd, CancellationToken ct) => Task.FromResult(1); + } + + public class Handler2 : ICommandHandler + { + public Task Handle(MyCommand cmd, CancellationToken ct) => Task.FromResult(2); + } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.Contains(diagnostics, d => d.Id == "CXMED005" && d.Severity == DiagnosticSeverity.Error); + } + + [Fact] + public void CXMED006_MultipleHandlersForSameQuery_ProducesError() + { + var source = @" +using Cortex.Mediator.Queries; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyQuery : IQuery { } + + public class QueryHandler1 : IQueryHandler + { + public Task Handle(MyQuery q, CancellationToken ct) => Task.FromResult(""a""); + } + + public class QueryHandler2 : IQueryHandler + { + public Task Handle(MyQuery q, CancellationToken ct) => Task.FromResult(""b""); + } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.Contains(diagnostics, d => d.Id == "CXMED006" && d.Severity == DiagnosticSeverity.Error); + } + + [Fact] + public void NoAttribute_NoDiagnostics() + { + var source = @" +using Cortex.Mediator.Commands; + +namespace TestApp +{ + public class OrphanCommand : ICommand { } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + // No diagnostics should be produced without the assembly attribute + Assert.Empty(diagnostics); + } + + [Fact] + public void CommandWithHandler_NoDiagnostic() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyCommand : ICommand { } + + public class MyHandler : ICommandHandler + { + public Task Handle(MyCommand cmd, CancellationToken ct) => Task.FromResult(1); + } +}"; + + var result = RunGenerator(source); + var diagnostics = result.Results[0].Diagnostics; + + Assert.DoesNotContain(diagnostics, d => d.Id == "CXMED001"); + } +} diff --git a/src/Cortex.Tests/Mediator/SourceGenerator/GeneratedMediatorIntegrationTests.cs b/src/Cortex.Tests/Mediator/SourceGenerator/GeneratedMediatorIntegrationTests.cs new file mode 100644 index 0000000..0437e8b --- /dev/null +++ b/src/Cortex.Tests/Mediator/SourceGenerator/GeneratedMediatorIntegrationTests.cs @@ -0,0 +1,326 @@ +using Cortex.Mediator.SourceGenerator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Reflection; + +namespace Cortex.Tests.Mediator.SourceGenerator; + +public class GeneratedMediatorIntegrationTests +{ + private static readonly MetadataReference[] SharedReferences = GetSharedReferences(); + + private static MetadataReference[] GetSharedReferences() + { + var assemblies = new[] + { + typeof(object).Assembly, + typeof(Task).Assembly, + typeof(IServiceProvider).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.ServiceCollection).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions).Assembly, + typeof(Cortex.Mediator.IMediator).Assembly, + Assembly.Load("System.Runtime"), + Assembly.Load("System.Collections"), + Assembly.Load("System.Linq"), + Assembly.Load("netstandard"), + }; + + return assemblies.Select(a => MetadataReference.CreateFromFile(a.Location)) + .Cast() + .ToArray(); + } + + private static (GeneratorDriverRunResult Result, Compilation OutputCompilation) RunGenerator(string source) + { + var syntaxTree = CSharpSyntaxTree.ParseText(source); + + var compilation = CSharpCompilation.Create( + assemblyName: "TestAssembly", + syntaxTrees: new[] { syntaxTree }, + references: SharedReferences, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + var generator = new CortexMediatorGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + + driver = driver.RunGeneratorsAndUpdateCompilation( + compilation, + out var outputCompilation, + out _); + + var result = driver.GetRunResult(); + return (result, outputCompilation); + } + + [Fact] + public void GeneratedCode_CompilesWith_AllHandlerTypes() + { + var source = @" +using Cortex.Mediator.Commands; +using Cortex.Mediator.Queries; +using Cortex.Mediator.Notifications; +using Cortex.Mediator.Streaming; +using Cortex.Mediator.Processors; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + // Returning command + public class CreateCommand : ICommand { public string Name { get; set; } } + public class CreateHandler : ICommandHandler + { + public Task Handle(CreateCommand cmd, CancellationToken ct) => Task.FromResult(42); + } + + // Void command + public class DeleteCommand : ICommand { public int Id { get; set; } } + public class DeleteHandler : ICommandHandler + { + public Task Handle(DeleteCommand cmd, CancellationToken ct) => Task.CompletedTask; + } + + // Query + public class GetQuery : IQuery { public int Id { get; set; } } + public class GetHandler : IQueryHandler + { + public Task Handle(GetQuery q, CancellationToken ct) => Task.FromResult(""result""); + } + + // Notification + public class ItemCreated : INotification { public int ItemId { get; set; } } + public class ItemCreatedHandler1 : INotificationHandler + { + public Task Handle(ItemCreated n, CancellationToken ct) => Task.CompletedTask; + } + public class ItemCreatedHandler2 : INotificationHandler + { + public Task Handle(ItemCreated n, CancellationToken ct) => Task.CompletedTask; + } + + // Stream query + public class StreamItems : IStreamQuery { } + public class StreamItemsHandler : IStreamQueryHandler + { + public async IAsyncEnumerable Handle(StreamItems q, CancellationToken ct) + { + yield return ""item1""; + yield return ""item2""; + } + } + + // Pre-processor + public class CreatePreProcessor : IRequestPreProcessor + { + public Task ProcessAsync(CreateCommand request, CancellationToken ct) => Task.CompletedTask; + } + + // Post-processor + public class CreatePostProcessor : IRequestPostProcessor + { + public Task ProcessAsync(CreateCommand request, int response, CancellationToken ct) => Task.CompletedTask; + } + + // Void post-processor + public class DeletePostProcessor : IRequestPostProcessor + { + public Task ProcessAsync(DeleteCommand request, CancellationToken ct) => Task.CompletedTask; + } +}"; + + var (result, outputCompilation) = RunGenerator(source); + + // Check no compilation errors in generated code + var errors = outputCompilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToArray(); + + Assert.Empty(errors); + + // Verify all expected files were generated + var generatedSources = result.Results[0].GeneratedSources; + Assert.Equal(3, generatedSources.Length); + + // Verify mediator source content + var mediatorSource = generatedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + // All switch cases should be present + Assert.Contains("case global::TestApp.CreateCommand typed:", mediatorSource); + Assert.Contains("case global::TestApp.DeleteCommand typed:", mediatorSource); + Assert.Contains("case global::TestApp.GetQuery typed:", mediatorSource); + Assert.Contains("case global::TestApp.ItemCreated typed:", mediatorSource); + Assert.Contains("case global::TestApp.StreamItems typed:", mediatorSource); + + // Verify DI source content + var diSource = generatedSources + .First(s => s.HintName == "GeneratedServiceCollectionExtensions.g.cs") + .SourceText.ToString(); + + // All handlers registered + Assert.Contains("global::TestApp.CreateHandler", diSource); + Assert.Contains("global::TestApp.DeleteHandler", diSource); + Assert.Contains("global::TestApp.GetHandler", diSource); + Assert.Contains("global::TestApp.ItemCreatedHandler1", diSource); + Assert.Contains("global::TestApp.ItemCreatedHandler2", diSource); + Assert.Contains("global::TestApp.StreamItemsHandler", diSource); + + // Processors registered + Assert.Contains("global::TestApp.CreatePreProcessor", diSource); + Assert.Contains("global::TestApp.CreatePostProcessor", diSource); + Assert.Contains("global::TestApp.DeletePostProcessor", diSource); + } + + [Fact] + public void GeneratedMediator_HasCorrectPipelineWrappers() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyCommand : ICommand { } + public class MyHandler : ICommandHandler + { + public Task Handle(MyCommand cmd, CancellationToken ct) => Task.FromResult(1); + } +}"; + + var (result, outputCompilation) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + // Verify pipeline wrapper classes exist + Assert.Contains("PipelineBehaviorNextDelegate", mediatorSource); + Assert.Contains("VoidPipelineBehaviorNextDelegate", mediatorSource); + Assert.Contains("QueryPipelineBehaviorNextDelegate", mediatorSource); + + // No compilation errors + var errors = outputCompilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToArray(); + Assert.Empty(errors); + } + + [Fact] + public void GeneratedDI_RegistersPipelineBehaviors() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyCommand : ICommand { } + public class MyHandler : ICommandHandler + { + public Task Handle(MyCommand cmd, CancellationToken ct) => Task.FromResult(1); + } +}"; + + var (result, _) = RunGenerator(source); + + var diSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedServiceCollectionExtensions.g.cs") + .SourceText.ToString(); + + // Verify it calls RegisterPipelineBehaviors + Assert.Contains("ServiceCollectionExtensions.RegisterPipelineBehaviors(services, options)", diSource); + + // Verify it registers the notification publish strategy + Assert.Contains("options.NotificationPublishStrategyType", diSource); + + // Verify it accepts configuration action + Assert.Contains("Action?", diSource); + } + + [Fact] + public void Notification_MultipleHandlers_SingleSwitchCase() + { + var source = @" +using Cortex.Mediator.Notifications; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyEvent : INotification { } + + public class Handler1 : INotificationHandler + { + public Task Handle(MyEvent n, CancellationToken ct) => Task.CompletedTask; + } + + public class Handler2 : INotificationHandler + { + public Task Handle(MyEvent n, CancellationToken ct) => Task.CompletedTask; + } +}"; + + var (result, _) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + // Only one switch case for the notification (deduplicated) + var caseCount = mediatorSource.Split("case global::TestApp.MyEvent typed:").Length - 1; + Assert.Equal(1, caseCount); + + // But both handlers registered in DI + var diSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedServiceCollectionExtensions.g.cs") + .SourceText.ToString(); + + Assert.Contains("global::TestApp.Handler1", diSource); + Assert.Contains("global::TestApp.Handler2", diSource); + } + + [Fact] + public void GeneratedMediator_IncludesNullChecks() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyCommand : ICommand { } + public class MyHandler : ICommandHandler + { + public Task Handle(MyCommand cmd, CancellationToken ct) => Task.FromResult(1); + } +}"; + + var (result, _) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + // Non-generic overloads should have null checks + Assert.Contains("throw new ArgumentNullException(nameof(command))", mediatorSource); + Assert.Contains("throw new ArgumentNullException(nameof(query))", mediatorSource); + Assert.Contains("throw new ArgumentNullException(nameof(notification))", mediatorSource); + } +} diff --git a/src/Cortex.Tests/Mediator/SourceGenerator/GeneratorDriverTests.cs b/src/Cortex.Tests/Mediator/SourceGenerator/GeneratorDriverTests.cs new file mode 100644 index 0000000..5913ba9 --- /dev/null +++ b/src/Cortex.Tests/Mediator/SourceGenerator/GeneratorDriverTests.cs @@ -0,0 +1,420 @@ +using Cortex.Mediator.SourceGenerator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Collections.Immutable; +using System.Reflection; + +namespace Cortex.Tests.Mediator.SourceGenerator; + +public class GeneratorDriverTests +{ + private static readonly MetadataReference[] SharedReferences = GetSharedReferences(); + + private static MetadataReference[] GetSharedReferences() + { + var assemblies = new[] + { + typeof(object).Assembly, + typeof(Task).Assembly, + typeof(IServiceProvider).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.IServiceCollection).Assembly, + typeof(Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions).Assembly, + typeof(Cortex.Mediator.IMediator).Assembly, + Assembly.Load("System.Runtime"), + Assembly.Load("System.Collections"), + Assembly.Load("System.Linq"), + Assembly.Load("netstandard"), + }; + + return assemblies.Select(a => MetadataReference.CreateFromFile(a.Location)) + .Cast() + .ToArray(); + } + + private static (GeneratorDriverRunResult Result, Compilation OutputCompilation) RunGenerator(string source) + { + var syntaxTree = CSharpSyntaxTree.ParseText(source); + + var compilation = CSharpCompilation.Create( + assemblyName: "TestAssembly", + syntaxTrees: new[] { syntaxTree }, + references: SharedReferences, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + var generator = new CortexMediatorGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + + driver = driver.RunGeneratorsAndUpdateCompilation( + compilation, + out var outputCompilation, + out var diagnostics); + + var result = driver.GetRunResult(); + return (result, outputCompilation); + } + + [Fact] + public void NoAttributeProducesNoGeneratedSource() + { + var source = @" +namespace TestApp +{ + public class MyClass { } +}"; + + var (result, _) = RunGenerator(source); + + // Without the assembly attribute, only the attribute definition file is generated (PostInitializationOutput) + // No GeneratedMediator or GeneratedServiceCollectionExtensions should be emitted + var generatedFiles = result.Results[0].GeneratedSources; + Assert.Single(generatedFiles); // Only the attribute definition + Assert.Contains("CortexMediatorGenerationAttribute", generatedFiles[0].SourceText.ToString()); + Assert.DoesNotContain(generatedFiles, f => f.HintName == "GeneratedMediator.g.cs"); + } + + [Fact] + public void AttributePresent_GeneratesMediator() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class CreateUserCommand : ICommand + { + public string Name { get; set; } + } + + public class CreateUserCommandHandler : ICommandHandler + { + public Task Handle(CreateUserCommand command, CancellationToken cancellationToken) + { + return Task.FromResult(System.Guid.NewGuid()); + } + } +}"; + + var (result, outputCompilation) = RunGenerator(source); + + var generatedSources = result.Results[0].GeneratedSources; + + // Should have 3 files: attribute + GeneratedMediator + GeneratedServiceCollectionExtensions + Assert.Equal(3, generatedSources.Length); + + var mediatorSource = generatedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + // Verify switch dispatch + Assert.Contains("case global::TestApp.CreateUserCommand typed:", mediatorSource); + Assert.Contains("SendCommandAsync", mediatorSource); + } + + [Fact] + public void AttributePresent_GeneratesDIRegistrations() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class CreateUserCommand : ICommand + { + public string Name { get; set; } + } + + public class CreateUserCommandHandler : ICommandHandler + { + public Task Handle(CreateUserCommand command, CancellationToken cancellationToken) + { + return Task.FromResult(System.Guid.NewGuid()); + } + } +}"; + + var (result, _) = RunGenerator(source); + + var diSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedServiceCollectionExtensions.g.cs") + .SourceText.ToString(); + + Assert.Contains("AddCortexGeneratedMediator", diSource); + Assert.Contains("GeneratedMediator", diSource); + Assert.Contains("global::TestApp.CreateUserCommandHandler", diSource); + Assert.Contains("ServiceLifetime.Scoped", diSource); + } + + [Fact] + public void VoidCommand_GeneratesSwitchDispatch() + { + var source = @" +using Cortex.Mediator.Commands; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class DeleteUserCommand : ICommand + { + public int UserId { get; set; } + } + + public class DeleteUserCommandHandler : ICommandHandler + { + public Task Handle(DeleteUserCommand command, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +}"; + + var (result, _) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + Assert.Contains("case global::TestApp.DeleteUserCommand typed:", mediatorSource); + } + + [Fact] + public void Query_GeneratesSwitchDispatch() + { + var source = @" +using Cortex.Mediator.Queries; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class GetUserQuery : IQuery + { + public int UserId { get; set; } + } + + public class GetUserQueryHandler : IQueryHandler + { + public Task Handle(GetUserQuery query, CancellationToken cancellationToken) + { + return Task.FromResult(""user""); + } + } +}"; + + var (result, _) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + Assert.Contains("case global::TestApp.GetUserQuery typed:", mediatorSource); + Assert.Contains("SendQueryAsync", mediatorSource); + } + + [Fact] + public void Notification_GeneratesSwitchDispatch() + { + var source = @" +using Cortex.Mediator.Notifications; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class UserCreatedNotification : INotification + { + public int UserId { get; set; } + } + + public class UserCreatedHandler : INotificationHandler + { + public Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +}"; + + var (result, _) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + Assert.Contains("case global::TestApp.UserCreatedNotification typed:", mediatorSource); + } + + [Fact] + public void StreamQuery_GeneratesSwitchDispatch() + { + var source = @" +using Cortex.Mediator.Streaming; +using System.Collections.Generic; +using System.Threading; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class GetUsersStreamQuery : IStreamQuery + { + } + + public class GetUsersStreamQueryHandler : IStreamQueryHandler + { + public async IAsyncEnumerable Handle(GetUsersStreamQuery query, System.Threading.CancellationToken cancellationToken) + { + yield return ""user1""; + } + } +}"; + + var (result, _) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + Assert.Contains("case global::TestApp.GetUsersStreamQuery typed:", mediatorSource); + } + + [Fact] + public void Processor_RegisteredInDI() + { + var source = @" +using Cortex.Mediator.Commands; +using Cortex.Mediator.Processors; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class MyCommand : ICommand { } + + public class MyCommandHandler : ICommandHandler + { + public Task Handle(MyCommand command, CancellationToken ct) => Task.FromResult(1); + } + + public class MyPreProcessor : IRequestPreProcessor + { + public Task ProcessAsync(MyCommand request, CancellationToken ct) => Task.CompletedTask; + } +}"; + + var (result, _) = RunGenerator(source); + + var diSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedServiceCollectionExtensions.g.cs") + .SourceText.ToString(); + + Assert.Contains("global::TestApp.MyPreProcessor", diSource); + Assert.Contains("ServiceLifetime.Transient", diSource); + } + + [Fact] + public void MultipleHandlers_AllIncludedInSwitch() + { + var source = @" +using Cortex.Mediator.Commands; +using Cortex.Mediator.Queries; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class CommandA : ICommand { } + public class CommandB : ICommand { } + public class QueryA : IQuery { } + + public class HandlerA : ICommandHandler + { + public Task Handle(CommandA command, CancellationToken ct) => Task.FromResult(1); + } + + public class HandlerB : ICommandHandler + { + public Task Handle(CommandB command, CancellationToken ct) => Task.FromResult(""b""); + } + + public class QueryHandlerA : IQueryHandler + { + public Task Handle(QueryA query, CancellationToken ct) => Task.FromResult(true); + } +}"; + + var (result, _) = RunGenerator(source); + + var mediatorSource = result.Results[0].GeneratedSources + .First(s => s.HintName == "GeneratedMediator.g.cs") + .SourceText.ToString(); + + Assert.Contains("case global::TestApp.CommandA typed:", mediatorSource); + Assert.Contains("case global::TestApp.CommandB typed:", mediatorSource); + Assert.Contains("case global::TestApp.QueryA typed:", mediatorSource); + } + + [Fact] + public void GeneratedCode_CompilesToValidOutput() + { + var source = @" +using Cortex.Mediator.Commands; +using Cortex.Mediator.Queries; +using Cortex.Mediator.Notifications; +using System.Threading; +using System.Threading.Tasks; + +[assembly: Cortex.Mediator.SourceGeneration.CortexMediatorGeneration] + +namespace TestApp +{ + public class CreateCommand : ICommand { } + public class DeleteCommand : ICommand { } + public class GetQuery : IQuery { } + public class AlertNotification : INotification { } + + public class CreateHandler : ICommandHandler + { + public Task Handle(CreateCommand cmd, CancellationToken ct) => Task.FromResult(42); + } + public class DeleteHandler : ICommandHandler + { + public Task Handle(DeleteCommand cmd, CancellationToken ct) => Task.CompletedTask; + } + public class GetHandler : IQueryHandler + { + public Task Handle(GetQuery q, CancellationToken ct) => Task.FromResult(""ok""); + } + public class AlertHandler : INotificationHandler + { + public Task Handle(AlertNotification n, CancellationToken ct) => Task.CompletedTask; + } +}"; + + var (result, outputCompilation) = RunGenerator(source); + + var diagnostics = outputCompilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToArray(); + + Assert.Empty(diagnostics); + } +}