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 0000000..101a1fb Binary files /dev/null and b/src/Cortex.Mediator.SourceGenerator/Assets/andyX.png differ 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); + } +}