From 2d022e0dfa05ce892c1b99bcbc5ebd398636918d Mon Sep 17 00:00:00 2001 From: "codebelt-aicia[bot]" Date: Wed, 22 Jul 2026 22:11:20 +0000 Subject: [PATCH 01/14] V5.0.10/service update --- .nuget/Savvyio.App/PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../Savvyio.Commands/PackageReleaseNotes.txt | 6 ++++++ .nuget/Savvyio.Core/PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .nuget/Savvyio.Domain/PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../PackageReleaseNotes.txt | 6 ++++++ .../Savvyio.Messaging/PackageReleaseNotes.txt | 6 ++++++ .nuget/Savvyio.Queries/PackageReleaseNotes.txt | 6 ++++++ CHANGELOG.md | 4 ++++ Directory.Packages.props | 18 +++++++++--------- 37 files changed, 223 insertions(+), 9 deletions(-) diff --git a/.nuget/Savvyio.App/PackageReleaseNotes.txt b/.nuget/Savvyio.App/PackageReleaseNotes.txt index dedd780..3fe93ed 100644 --- a/.nuget/Savvyio.App/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.App/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt b/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt index c0a47ce..c7d85cb 100644 --- a/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Commands.Messaging/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Commands/PackageReleaseNotes.txt b/.nuget/Savvyio.Commands/PackageReleaseNotes.txt index cbf0b51..7999e77 100644 --- a/.nuget/Savvyio.Commands/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Commands/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Core/PackageReleaseNotes.txt b/.nuget/Savvyio.Core/PackageReleaseNotes.txt index 2318ce7..274c5cd 100644 --- a/.nuget/Savvyio.Core/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Core/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt b/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt index 86e2b46..f84f83b 100644 --- a/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Domain.EventSourcing/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Domain/PackageReleaseNotes.txt index ac683a8..5605fcc 100644 --- a/.nuget/Savvyio.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt b/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt index 480e5a8..fe01329 100644 --- a/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.EventDriven.Messaging/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt b/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt index b5c5a7c..87a6fa8 100644 --- a/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.EventDriven/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt index 0612dde..2ee3221 100644 --- a/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Dapper/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt index 3fa6c31..bf55a82 100644 --- a/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DapperExtensions/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt index 136c839..88badf4 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Dapper/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt index 20e9b0b..ebdc4f5 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.DapperExtensions/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt index 76814b2..ceecad5 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt index 07ae9e6..4bf0d7f 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt index 7f71dc7..e7122be 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt index c2addbd..5e007f7 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.EFCore/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt index fe79f84..4c6533e 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.NATS/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt index f7ff529..a109b33 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Newtonsoft.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt index 3944cf3..70f81ef 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.QueueStorage/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt index 3e50867..244842d 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.RabbitMQ/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt index 392d3ac..9238149 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.SimpleQueueService/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt index c9071f1..f78f5d5 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection.Text.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt index cc12a26..c947e37 100644 --- a/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.DependencyInjection/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt index f6748ed..14e1e68 100644 --- a/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Dispatchers/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt index fcf5490..cd9e231 100644 --- a/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.EFCore.Domain.EventSourcing/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt index 3d9c069..84fac6c 100644 --- a/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.EFCore.Domain/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt index 4c4b29c..bd6d58c 100644 --- a/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.EFCore/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt index c63adf6..7e9679d 100644 --- a/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.NATS/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt index 9752d9a..cd10d8d 100644 --- a/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Newtonsoft.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt index b8a631a..2029f86 100644 --- a/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.QueueStorage/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt index 815ff8f..021fc52 100644 --- a/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.RabbitMQ/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt index d66e30b..574e639 100644 --- a/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.SimpleQueueService/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt b/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt index 2b78a72..0c7bb11 100644 --- a/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Extensions.Text.Json/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt b/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt index e2809f6..23ec0b7 100644 --- a/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Messaging/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Savvyio.Queries/PackageReleaseNotes.txt b/.nuget/Savvyio.Queries/PackageReleaseNotes.txt index c8be857..a14ca8e 100644 --- a/.nuget/Savvyio.Queries/PackageReleaseNotes.txt +++ b/.nuget/Savvyio.Queries/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 5.0.10 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 5.0.9 Availability: .NET 10 and .NET 9 diff --git a/CHANGELOG.md b/CHANGELOG.md index f3413a6..98342c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. +## [5.0.10] - 2026-07-22 + +This is a service update that focuses on package dependencies. + ## [5.0.9] - 2026-07-01 This is a patch release focused on API documentation expansion with comprehensive namespace and type examples, DocFX infrastructure restructuring, clear documentation maintenance standards for agents, and multiple NuGet package updates to latest stable versions. diff --git a/Directory.Packages.props b/Directory.Packages.props index 03ea2ce..ae5c607 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,15 +10,15 @@ - - - - - - - - - + + + + + + + + + From 0fe0fb3e2346975aa4d45351e18090d0fa08c58f Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Fri, 24 Jul 2026 15:11:24 +0200 Subject: [PATCH 02/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20upgrade=20dependenci?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update all dependencies to latest stable versions including AWSSDK SDKs, Microsoft.Data.Sqlite, Microsoft.EntityFrameworkCore, NATS clients, and related packages. --- Directory.Packages.props | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ae5c607..508fe83 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,12 +4,12 @@ true - - + + - + @@ -21,14 +21,15 @@ - - - + + + + - - - + + + @@ -39,14 +40,14 @@ - - - + + + - - - + + + \ No newline at end of file From eee89e5cf3ef6a10f09e69fb74dde3aff9c5579a Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Fri, 24 Jul 2026 15:11:41 +0200 Subject: [PATCH 03/14] =?UTF-8?q?=E2=9C=85=20add=20sqlitepclraw=20for=20tr?= =?UTF-8?q?ansitive=20test=20security?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The direct inclusion of SQLitePCLRaw.bundle_e_sqlite3 satisfies a transitive security warning. This package is only referenced by test projects and does not affect Savvyio production code. --- .../Savvyio.Assets.Dapper.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Savvyio.Assets.Dapper.Tests/Savvyio.Assets.Dapper.Tests.csproj b/test/Savvyio.Assets.Dapper.Tests/Savvyio.Assets.Dapper.Tests.csproj index 3870b56..8ffc88b 100644 --- a/test/Savvyio.Assets.Dapper.Tests/Savvyio.Assets.Dapper.Tests.csproj +++ b/test/Savvyio.Assets.Dapper.Tests/Savvyio.Assets.Dapper.Tests.csproj @@ -6,6 +6,7 @@ + From b2f5bfe91283d3b0271cca5e4f204c6599240780 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Fri, 24 Jul 2026 15:11:57 +0200 Subject: [PATCH 04/14] =?UTF-8?q?=F0=9F=94=A7=20enable=20analyzers=20and?= =?UTF-8?q?=20code=20style=20enforcement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable .NET code analyzers with latest analysis level and recommended mode. Add code style enforcement in build to catch violations during compilation. Suppress CA2260 and S6618 warnings. --- Directory.Build.props | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f6525a0..895b01c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,8 +32,12 @@ true true $(MSBuildThisFileDirectory)savvyio.snk - 7035 + true + latest + Recommended + 7035,CA2260,S6618 v + true @@ -62,7 +66,7 @@ false true 0 - none + none NU1701,NETSDK1206 false true From ea553c530449d3c7377079911db9ce7a330ad461 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Fri, 24 Jul 2026 15:12:12 +0200 Subject: [PATCH 05/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20nginx=20bas?= =?UTF-8?q?e=20image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update NGINX base image from 1.31.2-alpine to 1.31-alpine for DocFX container. --- .docfx/Dockerfile.docfx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docfx/Dockerfile.docfx b/.docfx/Dockerfile.docfx index 2229c73..7d97dde 100644 --- a/.docfx/Dockerfile.docfx +++ b/.docfx/Dockerfile.docfx @@ -1,4 +1,4 @@ -ARG NGINX_VERSION=1.31.2-alpine +ARG NGINX_VERSION=1.31-alpine FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base RUN rm -rf /usr/share/nginx/html/* From 39e8b0d1d8f6ba6aeedf5f9647ed4cea599a5a0b Mon Sep 17 00:00:00 2001 From: gimlichael Date: Fri, 24 Jul 2026 15:18:42 +0200 Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=94=A7=20add=20project=20reference?= =?UTF-8?q?=20for=20Newtonsoft.Json=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Savvyio.Assets.EfCore.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Savvyio.Assets.EfCore.Tests/Savvyio.Assets.EfCore.Tests.csproj b/test/Savvyio.Assets.EfCore.Tests/Savvyio.Assets.EfCore.Tests.csproj index b7f8617..f693c47 100644 --- a/test/Savvyio.Assets.EfCore.Tests/Savvyio.Assets.EfCore.Tests.csproj +++ b/test/Savvyio.Assets.EfCore.Tests/Savvyio.Assets.EfCore.Tests.csproj @@ -11,6 +11,7 @@ + From e032f093a97380b9a2ac89d8c6b455a4388bd72a Mon Sep 17 00:00:00 2001 From: gimlichael Date: Fri, 24 Jul 2026 15:32:14 +0200 Subject: [PATCH 07/14] =?UTF-8?q?=F0=9F=8E=A8=20add=20.editorconfig=20for?= =?UTF-8?q?=20code=20style=20and=20formatting=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a083db5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,176 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Default: prefer spaces for data/markup +indent_style = space +indent_size = 2 +tab_width = 2 + +# This style rule concern the use of the range operator, which is available in C# 8.0 and later. +# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0057 +[*.{cs,vb}] +dotnet_diagnostic.IDE0057.severity = none + +# This style rule concerns the use of switch expressions versus switch statements. +# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0066 +[*.{cs,vb}] +dotnet_diagnostic.IDE0066.severity = none + +# Performance rules +# https://docs.microsoft.com/da-dk/dotnet/fundamentals/code-analysis/quality-rules/performance-warnings +[*.{cs,vb}] +dotnet_analyzer_diagnostic.category-Performance.severity = none # Because many of the suggestions by performance analyzers are not compatible with .NET Standard 2.0 + +# This style rule concerns the use of using statements without curly braces, also known as using declarations. This alternative syntax was introduced in C# 8.0. +# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0063 +[*.{cs,vb}] +dotnet_diagnostic.IDE0063.severity = none + +# This style rule concerns with simplification of interpolated strings to improve code readability. It recommends removal of certain explicit method calls, such as ToString(), when the same method would be implicitly invoked by the compiler if the explicit method call is removed. +# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0071 +[*.{cs,vb}] +dotnet_diagnostic.IDE0071.severity = none + +# S3267: Loops should be simplified with "LINQ" expressions +# https://rules.sonarsource.com/csharp/RSPEC-3267 +dotnet_diagnostic.S3267.severity = none + +# CA1859: Use concrete types when possible for improved performance +# This is a violation of Framework Design Guidelines. +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1859 +[*.{cs,vb}] +dotnet_diagnostic.CA1859.severity = none + +# IDE0008: Use explicit type +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0007-ide0008 +[*.{cs,vb}] +dotnet_diagnostic.IDE0008.severity = none + +[*.{cs,vb}] +indent_style = space +indent_size = 4 + +# IDE0161: Namespace declaration preferences +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0161 +# Prefer file-scoped namespaces for new files; existing block-scoped files should not be converted unless explicitly asked +[*.cs] +csharp_style_namespace_declarations = file_scoped:suggestion + +# Top-level statements: DO NOT USE +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0210-ide0211 +# This is enforced via a style preference set to error severity, not a language-level prohibition. +# Always use explicit class declarations with a proper namespace and Main method where applicable. +[*.cs] +csharp_style_prefer_top_level_statements = false:error + +[*.xml] +indent_style = space +indent_size = 2 + +# IDE0078: Use pattern matching +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0078 +[*.{cs,vb}] +dotnet_diagnostic.IDE0078.severity = none + +# IDE0290: Use primary constructor +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0290 +[*.{cs,vb}] +dotnet_diagnostic.IDE0290.severity = none + +# IDE0305: Use collection expression for fluent +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0305 +[*.{cs,vb}] +dotnet_diagnostic.IDE0305.severity = none + +# IDE0011: Add braces +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0011 +[*.{cs,vb}] +dotnet_diagnostic.IDE0011.severity = none + +# IDE0028: Use collection initializers or expressions +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0028 +[*.{cs,vb}] +dotnet_diagnostic.IDE0028.severity = none + +# IDE0039: Use collection expression for array +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0300 +[*.{cs,vb}] +dotnet_diagnostic.IDE0300.severity = none + +# IDE0031: Use collection expression for empty +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0301 +[*.{cs,vb}] +dotnet_diagnostic.IDE0301.severity = none + +# IDE0046: Use conditional expression for return +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0046 +[*.{cs,vb}] +dotnet_diagnostic.IDE0046.severity = none + +# IDE0047: Parentheses preferences +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0047-ide0048 +[*.{cs,vb}] +dotnet_diagnostic.IDE0047.severity = none + +# CA1716: Identifiers should not match keywords +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1716 +[*.{cs,vb}] +dotnet_diagnostic.CA1716.severity = none + +# CA1720: Identifiers should not contain type names +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1720 +[*.{cs,vb}] +dotnet_diagnostic.CA1720.severity = none + +# CA1846: Prefer AsSpan over Substring +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1846 +# Excluded while TFMs include netstandard2.0 +[*.{cs,vb}] +dotnet_diagnostic.CA1846.severity = none + +# CA1847: Use String.Contains(char) instead of String.Contains(string) with single characters +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1847 +# Excluded while TFMs include netstandard2.0 +[*.{cs,vb}] +dotnet_diagnostic.CA1847.severity = none + +# CA1865-CA1867: Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1865-ca1867 +# Excluded while TFMs include netstandard2.0 +[*.{cs,vb}] +dotnet_diagnostic.CA1865.severity = none +dotnet_diagnostic.CA1866.severity = none +dotnet_diagnostic.CA1867.severity = none + +# CA2263: Prefer generic overload when type is known +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2263 +# Excluded while TFMs include netstandard2.0 +[*.{cs,vb}] +dotnet_diagnostic.CA2263.severity = none + +# CA2249: Consider using String.Contains instead of String.IndexOf +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2249 +# Excluded while TFMs include netstandard2.0 +[*.{cs,vb}] +dotnet_diagnostic.CA2249.severity = none + +# IDE0022: Use expression body for methods +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0022 +[*.{cs,vb}] +dotnet_diagnostic.IDE0022.severity = none + +# IDE0032: Use auto-property +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0032 +[*.{cs,vb}] +dotnet_diagnostic.IDE0032.severity = none + +# Order modifiers +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0036 +# Excluded becuase of inconsistency with other analyzers +[*.{cs,vb}] +dotnet_diagnostic.IDE0036.severity = none From 90443178090d47b8e96d2c050ad2e843a5156a4b Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Fri, 24 Jul 2026 16:29:16 +0200 Subject: [PATCH 08/14] =?UTF-8?q?=F0=9F=94=A7=20configure=20code=20analysi?= =?UTF-8?q?s=20suppressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Roslyn analyzer suppressions CA1711, CA1725, CA1848, and CA1873 to the build configuration. Update GlobalSuppressions.cs in Savvyio.Core and Savvyio.Extensions.EFCore to justify design decisions. Create GlobalSuppressions.cs for Savvyio.Extensions.Newtonsoft.Json to align with analyzer expectations across all packages. --- Directory.Build.props | 2 +- src/Savvyio.Core/GlobalSuppressions.cs | 5 +++-- src/Savvyio.Extensions.EFCore/GlobalSuppressions.cs | 4 +--- .../GlobalSuppressions.cs | 8 ++++++++ 4 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 src/Savvyio.Extensions.Newtonsoft.Json/GlobalSuppressions.cs diff --git a/Directory.Build.props b/Directory.Build.props index 895b01c..a86adfa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -35,7 +35,7 @@ true latest Recommended - 7035,CA2260,S6618 + 7035,CA2260,S6618,CA1711,CA1725,CA1848,CA1873 v true diff --git a/src/Savvyio.Core/GlobalSuppressions.cs b/src/Savvyio.Core/GlobalSuppressions.cs index 1afa65d..1b0f0f9 100644 --- a/src/Savvyio.Core/GlobalSuppressions.cs +++ b/src/Savvyio.Core/GlobalSuppressions.cs @@ -1,4 +1,4 @@ -// This file is used by Code Analysis to maintain SuppressMessage +// This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. @@ -8,4 +8,5 @@ [assembly: SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "By design; marker interface - inheriting interface requires specification of DTO.", Scope = "type", Target = "~T:Savvyio.Data.IDataStore`1")] [assembly: SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "By design; marker interface - inheriting interface requires specification of Entity.", Scope = "type", Target = "~T:Savvyio.Domain.IRepository`2")] [assembly: SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "By design; marker interface - inheriting interface requires specification of Request.", Scope = "type", Target = "~T:Savvyio.IHandler`1")] -[assembly: SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "By design; marker interface - inheriting interface requires specification of Request.", Scope = "type", Target = "~T:Savvyio.Queries.IQuery`1")] \ No newline at end of file +[assembly: SuppressMessage("Major Code Smell", "S2326:Unused type parameters should be removed", Justification = "By design; marker interface - inheriting interface requires specification of Request.", Scope = "type", Target = "~T:Savvyio.Queries.IQuery`1")] +[assembly: SuppressMessage("Naming", "CA1710:Identifiers should have correct suffix", Justification = "By design - Dictionary suffix confuses context.", Scope = "type", Target = "~T:Savvyio.EventDriven.Messaging.CloudEvents.ICloudEvent`1")] diff --git a/src/Savvyio.Extensions.EFCore/GlobalSuppressions.cs b/src/Savvyio.Extensions.EFCore/GlobalSuppressions.cs index afb01e8..a1dfb4a 100644 --- a/src/Savvyio.Extensions.EFCore/GlobalSuppressions.cs +++ b/src/Savvyio.Extensions.EFCore/GlobalSuppressions.cs @@ -1,4 +1,4 @@ -// This file is used by Code Analysis to maintain SuppressMessage +// This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. @@ -6,6 +6,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Major Code Smell", "S3358:Ternary operators should not be nested", Justification = "By design. No added complexity in this scope.", Scope = "member", Target = "~M:Savvyio.Extensions.EFCore.EfCoreRepository`2.FindAllAsync(System.Linq.Expressions.Expression{System.Func{`0,System.Boolean}},System.Action{Cuemon.Threading.AsyncOptions})~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{`0}}")] -[assembly: SuppressMessage("Major Code Smell", "S3358:Ternary operators should not be nested", Justification = "By design. No added complexity in this scope.", Scope = "member", Target = "~M:Savvyio.Extensions.EFCore.DefaultEfCoreDataStore`1.FindAllAsync(System.Action{Savvyio.Extensions.EFCore.EfCoreQueryOptions{`0}})~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{`0}}")] -[assembly: SuppressMessage("Major Code Smell", "S6966:Awaitable method should be used", Justification = "https://github.com/SonarSource/sonar-dotnet/pull/9318", Scope = "member", Target = "~M:Savvyio.Extensions.EFCore.DefaultEfCoreDataStore`1.CreateAsync(`0,System.Action{Cuemon.Threading.AsyncOptions})~System.Threading.Tasks.Task")] [assembly: SuppressMessage("Major Code Smell", "S6966:Awaitable method should be used", Justification = "https://github.com/SonarSource/sonar-dotnet/pull/9318", Scope = "member", Target = "~M:Savvyio.Extensions.EFCore.EfCoreDataStore`1.CreateAsync(`0,System.Action{Cuemon.Threading.AsyncOptions})~System.Threading.Tasks.Task")] diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/GlobalSuppressions.cs b/src/Savvyio.Extensions.Newtonsoft.Json/GlobalSuppressions.cs new file mode 100644 index 0000000..c648670 --- /dev/null +++ b/src/Savvyio.Extensions.Newtonsoft.Json/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Performance", "CA1805:Do not initialize unnecessarily", Justification = "False-Positive.", Scope = "member", Target = "~P:Savvyio.Extensions.Newtonsoft.Json.Converters.RequestConverter.CanWrite")] From e3df8fce41d3b83c373b74ce09011463e737ade7 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Fri, 24 Jul 2026 16:29:37 +0200 Subject: [PATCH 09/14] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20modernize=20for=20.N?= =?UTF-8?q?ET=209=20&=20improve=20code=20quality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace object locker with System.Threading.Lock throughout the codebase for improved performance and modern .NET 9 interop. Add CultureInfo.InvariantCulture to all culture-aware StringBuilder and string operations to ensure consistent output regardless of system locale. Optimize performance by replacing Enumerable.Any() with Count > 0 checks on known collection types. Add necessary using directives for System.Globalization and System.Threading where required. Update test assertions to reflect the API modernizations. --- src/Savvyio.Core/HandlerServicesDescriptor.cs | 14 ++++++++------ src/Savvyio.Core/Handlers/FireForgetManager.cs | 2 +- src/Savvyio.Core/Handlers/RequestReplyManager.cs | 2 +- src/Savvyio.Core/MetadataDictionary.cs | 8 ++++---- src/Savvyio.Core/Reflection/AssemblyContext.cs | 11 ++++++++++- .../TracedAggregateRoot.cs | 2 +- src/Savvyio.Domain/ValueObject.cs | 7 ++++--- .../Cryptography/SignedCloudEventExtensions.cs | 4 ++-- src/Savvyio.Extensions.Dapper/DapperDataSource.cs | 2 +- src/Savvyio.Extensions.Dapper/DapperDataStore.cs | 2 +- .../TracedDomainEventExtensions.cs | 2 +- src/Savvyio.Extensions.EFCore/EfCoreDbContext.cs | 14 +++++++------- .../Bootstrapper.cs | 5 +++-- .../Converters/AggregateRootConverter.cs | 8 ++++---- .../Converters/MessageConverter.cs | 10 +++++----- .../Converters/RequestConverter.cs | 10 +++++----- .../Converters/SingleValueObjectConverter.cs | 6 +++--- .../Converters/ValueObjectConverter.cs | 8 ++++---- .../AmazonResourceNameOptions.cs | 2 +- src/Savvyio.Extensions.Text.Json/Bootstrapper.cs | 5 +++-- .../Converters/MessageConverter.cs | 6 +++--- .../Converters/RequestConverter.cs | 2 +- .../Converters/SingleValueObjectConverter.cs | 2 +- .../Cryptography/SignedMessageExtensions.cs | 4 ++-- src/Savvyio.Messaging/MessageAsyncEnumerable.cs | 2 +- src/Savvyio.Messaging/MessageAsyncEnumerator.cs | 2 +- .../AmazonResourceNameOptionsTest.cs | 2 +- 27 files changed, 79 insertions(+), 65 deletions(-) diff --git a/src/Savvyio.Core/HandlerServicesDescriptor.cs b/src/Savvyio.Core/HandlerServicesDescriptor.cs index 88f646b..7e5a311 100644 --- a/src/Savvyio.Core/HandlerServicesDescriptor.cs +++ b/src/Savvyio.Core/HandlerServicesDescriptor.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; +using System.Threading; using Cuemon; using Cuemon.Extensions; using Cuemon.Extensions.Runtime; @@ -27,7 +29,7 @@ namespace Savvyio public class HandlerServicesDescriptor : IHandlerServicesDescriptor { private List _models; - private readonly object _locker = new(); + private readonly Lock _locker = new(); /// /// Initializes a new instance of the class. @@ -59,16 +61,16 @@ public override string ToString() foreach (var assembly in model.Assemblies) { builder.AppendLine(); - builder.AppendLine($"Assembly: {assembly.Name}"); - builder.AppendLine($"Namespace: {assembly.Namespace}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"Assembly: {assembly.Name}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"Namespace: {assembly.Namespace}"); builder.AppendLine(); foreach (var implementation in assembly.Implementations) { - builder.AppendLine($"<{implementation.Name}>"); + builder.AppendLine(CultureInfo.InvariantCulture, $"<{implementation.Name}>"); foreach (var @delegate in implementation.Delegates) { - builder.AppendLine($"\t*{@delegate.Type} --> &{@delegate.Handler}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"\t*{@delegate.Type} --> &{@delegate.Handler}"); } builder.AppendLine(); } diff --git a/src/Savvyio.Core/Handlers/FireForgetManager.cs b/src/Savvyio.Core/Handlers/FireForgetManager.cs index 4c517cc..661dd93 100644 --- a/src/Savvyio.Core/Handlers/FireForgetManager.cs +++ b/src/Savvyio.Core/Handlers/FireForgetManager.cs @@ -6,7 +6,7 @@ namespace Savvyio.Handlers { - internal class FireForgetManager : IFireForgetRegistry, IFireForgetActivator + internal sealed class FireForgetManager : IFireForgetRegistry, IFireForgetActivator { private readonly ConcurrentDictionary> _handlers = new(); private readonly ConcurrentDictionary> _asyncHandlers = new(); diff --git a/src/Savvyio.Core/Handlers/RequestReplyManager.cs b/src/Savvyio.Core/Handlers/RequestReplyManager.cs index b1e523e..b8c19f1 100644 --- a/src/Savvyio.Core/Handlers/RequestReplyManager.cs +++ b/src/Savvyio.Core/Handlers/RequestReplyManager.cs @@ -6,7 +6,7 @@ namespace Savvyio.Handlers { - internal class RequestReplyManager : IRequestReplyRegistry, IRequestReplyActivator + internal sealed class RequestReplyManager : IRequestReplyRegistry, IRequestReplyActivator { private readonly ConcurrentDictionary> _handlers = new(); private readonly ConcurrentDictionary>> _asyncHandlers = new(); diff --git a/src/Savvyio.Core/MetadataDictionary.cs b/src/Savvyio.Core/MetadataDictionary.cs index 0f2429b..a362ee1 100644 --- a/src/Savvyio.Core/MetadataDictionary.cs +++ b/src/Savvyio.Core/MetadataDictionary.cs @@ -95,13 +95,13 @@ public object this[string key] public bool IsReadOnly => _dictionary.IsReadOnly; /// - /// Gets an containing the keys of the . + /// Gets an containing the keys of the . /// /// A containing the keys in the . public ICollection Keys => _dictionary.Keys; /// - /// Gets an containing the values in the . + /// Gets an containing the values in the . /// /// A containing the values in the . public ICollection Values => _dictionary.Values; @@ -137,7 +137,7 @@ public void Clear() /// /// Copies the elements of the to an array of type , starting at the specified array index. /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. + /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in at which copying begins. public void CopyTo(KeyValuePair[] array, int arrayIndex) { @@ -207,7 +207,7 @@ public IEnumerator> GetEnumerator() /// /// Returns an enumerator that iterates through a collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_dictionary).GetEnumerator(); diff --git a/src/Savvyio.Core/Reflection/AssemblyContext.cs b/src/Savvyio.Core/Reflection/AssemblyContext.cs index ee95d3b..4c89837 100644 --- a/src/Savvyio.Core/Reflection/AssemblyContext.cs +++ b/src/Savvyio.Core/Reflection/AssemblyContext.cs @@ -1 +1,10 @@ -using Cuemon.Extensions.Collections.Generic; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Cuemon; namespace Savvyio.Reflection { /// /// Provides a set of static methods and properties to manage and filter assemblies in the current application domain. /// public static class AssemblyContext { private static readonly Lazy> AssemblyLoadFactory = new(() => AppDomain .CurrentDomain .GetAssemblies() .Where(AssemblyFilterCallback) .SelectMany(AssemblyDependenciesCallback) .Distinct() .Except(typeof(AssemblyContext).Assembly.Yield()) .ToList() .AsReadOnly()); private static Func _assemblyFilterCallback = DefaultAssemblyFilter; private static Func> _assemblyDependenciesCallback = DefaultAssemblyDependencies; private static Func _assemblyDependenciesFilterCallback = DefaultAssemblyDependenciesFilter; /// /// Gets or sets the function delegate that filters assemblies from the current application domain. /// /// The function delegate that filters assemblies from the current application domain. /// The default implementation filters away assemblies that suggest being part of the .NET runtime themselves. /// /// cannot be null. /// public static Func AssemblyFilterCallback { get => _assemblyFilterCallback; set => _assemblyFilterCallback = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets the function delegate that recursively discover dependencies for an assembly in the current application domain. /// /// The function delegate that recursively discover dependencies for an assembly in the current application domain. /// /// cannot be null. /// public static Func> AssemblyDependenciesCallback { get => _assemblyDependenciesCallback; set => _assemblyDependenciesCallback = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets the function delegate that filters assembly dependencies from the current application domain. /// /// The function delegate that filters assembly dependencies from the current application domain. /// The default implementation filters away assembly dependencies that suggest being part of the .NET runtime themselves. /// /// cannot be null. /// public static Func AssemblyDependenciesFilterCallback { get => _assemblyDependenciesFilterCallback; set => _assemblyDependenciesFilterCallback = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets the qualified assemblies from the current application domain. /// /// The qualified assemblies from the current application domain. /// This result of this property is coupled with and . public static IReadOnlyList CurrentDomainAssemblies => AssemblyLoadFactory.Value; private static bool DefaultAssemblyFilter(Assembly assembly) { return assembly.FullName != null && !assembly.FullName.StartsWith(nameof(System)) && !assembly.FullName.StartsWith(nameof(Microsoft)); } private static bool DefaultAssemblyDependenciesFilter(AssemblyName assemblyName) { return !assemblyName.FullName.StartsWith(nameof(System)) && !assemblyName.FullName.StartsWith(nameof(Microsoft)); } private static IEnumerable DefaultAssemblyDependencies(Assembly assembly) { var stack = new Stack(); var guard = new HashSet(); yield return assembly; stack.Push(assembly); guard.Add(assembly.FullName); while (stack.TryPop(out var assemblyToTraverse)) { foreach (var assemblyName in assemblyToTraverse.GetReferencedAssemblies().Where(AssemblyDependenciesFilterCallback)) { if (!guard.Add(assemblyName.FullName)) { continue; } if (Patterns.TryInvoke(() => Assembly.Load(assemblyName), out var referencedAssembly) && referencedAssembly != null) { stack.Push(referencedAssembly); yield return referencedAssembly; } } } } } } \ No newline at end of file +using Cuemon.Extensions.Collections.Generic; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Cuemon; namespace Savvyio.Reflection { /// /// Provides a set of static methods and properties to manage and filter assemblies in the current application domain. /// public static class AssemblyContext { private static readonly Lazy> AssemblyLoadFactory = new(() => AppDomain .CurrentDomain .GetAssemblies() .Where(AssemblyFilterCallback) .SelectMany(AssemblyDependenciesCallback) .Distinct() .Except(typeof(AssemblyContext).Assembly.Yield()) .ToList() .AsReadOnly()); private static Func _assemblyFilterCallback = DefaultAssemblyFilter; private static Func> _assemblyDependenciesCallback = DefaultAssemblyDependencies; private static Func _assemblyDependenciesFilterCallback = DefaultAssemblyDependenciesFilter; /// /// Gets or sets the function delegate that filters assemblies from the current application domain. /// /// The function delegate that filters assemblies from the current application domain. /// The default implementation filters away assemblies that suggest being part of the .NET runtime themselves. /// /// cannot be null. /// public static Func AssemblyFilterCallback { get => _assemblyFilterCallback; set => _assemblyFilterCallback = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets the function delegate that recursively discover dependencies for an assembly in the current application domain. /// /// The function delegate that recursively discover dependencies for an assembly in the current application domain. /// /// cannot be null. /// public static Func> AssemblyDependenciesCallback { get => _assemblyDependenciesCallback; set => _assemblyDependenciesCallback = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets or sets the function delegate that filters assembly dependencies from the current application domain. /// /// The function delegate that filters assembly dependencies from the current application domain. /// The default implementation filters away assembly dependencies that suggest being part of the .NET runtime themselves. /// /// cannot be null. /// public static Func AssemblyDependenciesFilterCallback { get => _assemblyDependenciesFilterCallback; set => _assemblyDependenciesFilterCallback = value ?? throw new ArgumentNullException(nameof(value)); } /// /// Gets the qualified assemblies from the current application domain. /// /// The qualified assemblies from the current application domain. /// This result of this property is coupled with and . public static IReadOnlyList CurrentDomainAssemblies => AssemblyLoadFactory.Value; private static bool DefaultAssemblyFilter(Assembly assembly) { return assembly.FullName != null && + !assembly.FullName.StartsWith(nameof(System), StringComparison.Ordinal) && + !assembly.FullName.StartsWith(nameof(Microsoft), StringComparison.Ordinal); + } + + private static bool DefaultAssemblyDependenciesFilter(AssemblyName assemblyName) + { + return !assemblyName.FullName.StartsWith(nameof(System), StringComparison.Ordinal) && + !assemblyName.FullName.StartsWith(nameof(Microsoft), StringComparison.Ordinal); + } private static IEnumerable DefaultAssemblyDependencies(Assembly assembly) { var stack = new Stack(); var guard = new HashSet(); yield return assembly; stack.Push(assembly); guard.Add(assembly.FullName); while (stack.TryPop(out var assemblyToTraverse)) { foreach (var assemblyName in assemblyToTraverse.GetReferencedAssemblies().Where(AssemblyDependenciesFilterCallback)) { if (!guard.Add(assemblyName.FullName)) { continue; } if (Patterns.TryInvoke(() => Assembly.Load(assemblyName), out var referencedAssembly) && referencedAssembly != null) { stack.Push(referencedAssembly); yield return referencedAssembly; } } } } } } \ No newline at end of file diff --git a/src/Savvyio.Domain.EventSourcing/TracedAggregateRoot.cs b/src/Savvyio.Domain.EventSourcing/TracedAggregateRoot.cs index 5bd2abe..4475f1e 100644 --- a/src/Savvyio.Domain.EventSourcing/TracedAggregateRoot.cs +++ b/src/Savvyio.Domain.EventSourcing/TracedAggregateRoot.cs @@ -63,7 +63,7 @@ private void ReplayEvents(IEnumerable events) /// /// Adds an event to the Aggregate. /// - /// The event to be added to the end of . + /// The event to be added to the end of . protected sealed override void AddEvent(ITracedDomainEvent e) { ApplyChange(e); diff --git a/src/Savvyio.Domain/ValueObject.cs b/src/Savvyio.Domain/ValueObject.cs index fc17e5c..a2ef8dc 100644 --- a/src/Savvyio.Domain/ValueObject.cs +++ b/src/Savvyio.Domain/ValueObject.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Threading; using Cuemon; using Cuemon.Extensions; using Cuemon.Reflection; @@ -9,11 +10,11 @@ namespace Savvyio.Domain /// /// Represents an object whose equality is based on the value rather than identity as specified in Domain Driven Design. /// - /// + /// public abstract record ValueObject { private const int NullHashCode = 472074819; - private readonly object _locker = new(); + private readonly Lock _locker = new(); private IEnumerable _equalityComponents; /// diff --git a/src/Savvyio.EventDriven.Messaging/CloudEvents/Cryptography/SignedCloudEventExtensions.cs b/src/Savvyio.EventDriven.Messaging/CloudEvents/Cryptography/SignedCloudEventExtensions.cs index 49a6221..cb18d35 100644 --- a/src/Savvyio.EventDriven.Messaging/CloudEvents/Cryptography/SignedCloudEventExtensions.cs +++ b/src/Savvyio.EventDriven.Messaging/CloudEvents/Cryptography/SignedCloudEventExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using Cuemon; using Savvyio.Messaging; using Savvyio.Messaging.Cryptography; @@ -35,7 +35,7 @@ public static void CheckCloudEventSignature(this ISignedCloudEvent cloudEv Validator.ThrowIfNull(marshaller); Validator.ThrowIfInvalidConfigurator(setup, out _); var baseCloudEvent = (cloudEvent.Clone(() => new CloudEvent(cloudEvent, cloudEvent.Specversion)) as ICloudEvent).SignCloudEvent(marshaller, setup); - if (!cloudEvent.Signature.Equals(baseCloudEvent.Signature)) + if (!cloudEvent.Signature.Equals(baseCloudEvent.Signature, StringComparison.Ordinal)) { throw new ArgumentOutOfRangeException(nameof(cloudEvent), cloudEvent.Signature, "The signature of the cloud event does not match the cryptographically calculated value. Either you are using an incorrect secret and/or algorithm or the message has been tampered with."); } diff --git a/src/Savvyio.Extensions.Dapper/DapperDataSource.cs b/src/Savvyio.Extensions.Dapper/DapperDataSource.cs index 365d8b9..0ed9276 100644 --- a/src/Savvyio.Extensions.Dapper/DapperDataSource.cs +++ b/src/Savvyio.Extensions.Dapper/DapperDataSource.cs @@ -31,7 +31,7 @@ public DapperDataSource(DapperDataSourceOptions options) } /// - /// Called when this object is being disposed by either or having disposing set to true and is false. + /// Called when this object is being disposed by either or having disposing set to true and is false. /// protected override void OnDisposeManagedResources() { diff --git a/src/Savvyio.Extensions.Dapper/DapperDataStore.cs b/src/Savvyio.Extensions.Dapper/DapperDataStore.cs index a3453e9..353130d 100644 --- a/src/Savvyio.Extensions.Dapper/DapperDataStore.cs +++ b/src/Savvyio.Extensions.Dapper/DapperDataStore.cs @@ -73,7 +73,7 @@ protected DapperDataStore(IDapperDataSource source) public abstract Task DeleteAsync(T dto, Action setup = null); /// - /// Called when this object is being disposed by either or having disposing set to true and is false. + /// Called when this object is being disposed by either or having disposing set to true and is false. /// protected override void OnDisposeManagedResources() { diff --git a/src/Savvyio.Extensions.EFCore.Domain.EventSourcing/TracedDomainEventExtensions.cs b/src/Savvyio.Extensions.EFCore.Domain.EventSourcing/TracedDomainEventExtensions.cs index 0795163..521c58f 100644 --- a/src/Savvyio.Extensions.EFCore.Domain.EventSourcing/TracedDomainEventExtensions.cs +++ b/src/Savvyio.Extensions.EFCore.Domain.EventSourcing/TracedDomainEventExtensions.cs @@ -13,7 +13,7 @@ public static class TracedDomainEventExtensions /// /// The domain event to convert. /// The that is used when converting into an array of bytes. - /// A that is equivalent to . + /// A byte[] that is equivalent to . public static byte[] ToByteArray(this ITracedDomainEvent domainEvent, IMarshaller marshaller) { var bytes = marshaller.Serialize(domainEvent, typeof(ITracedDomainEvent)).ToByteArray(); diff --git a/src/Savvyio.Extensions.EFCore/EfCoreDbContext.cs b/src/Savvyio.Extensions.EFCore/EfCoreDbContext.cs index bf5b146..32201fa 100644 --- a/src/Savvyio.Extensions.EFCore/EfCoreDbContext.cs +++ b/src/Savvyio.Extensions.EFCore/EfCoreDbContext.cs @@ -26,10 +26,10 @@ public EfCoreDbContext(EfCoreDataSourceOptions options) /// The base implementation does nothing. /// /// - /// In situations where an instance of may or may not have been passed - /// to the constructor, you can use to determine if + /// In situations where an instance of may or may not have been passed + /// to the constructor, you can use to determine if /// the options have already been set, and skip some or all of the logic in - /// . + /// . /// /// /// A builder used to create or modify options for this context. Databases (and other extensions) @@ -44,10 +44,10 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) /// /// Override this method to set defaults and configure conventions before they run. This method is invoked before - /// . + /// . /// /// The builder being used to set defaults and configure conventions that will be used to build the model for this context. - /// If a model is explicitly set on the options for this context (via ) + /// If a model is explicitly set on the options for this context (via ) /// then this method will not be run. protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { @@ -57,14 +57,14 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura /// /// Override this method to further configure the model that was discovered by convention from the entity types - /// exposed in properties on your derived context. The resulting model may be cached + /// exposed in properties on your derived context. The resulting model may be cached /// and re-used for subsequent instances of your derived context. /// /// The builder being used to construct the model for this context. Databases (and other extensions) typically /// define extension methods on this object that allow you to configure aspects of the model that are specific /// to a given database. /// - /// If a model is explicitly set on the options for this context (via ) + /// If a model is explicitly set on the options for this context (via ) /// then this method will not be run. /// /// diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Bootstrapper.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Bootstrapper.cs index 6eb3506..44d2a3f 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Bootstrapper.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Bootstrapper.cs @@ -1,10 +1,11 @@ -using Codebelt.Extensions.Newtonsoft.Json.Formatters; +using Codebelt.Extensions.Newtonsoft.Json.Formatters; +using System.Threading; namespace Savvyio.Extensions.Newtonsoft.Json { internal static class Bootstrapper { - private static readonly object PadLock = new(); + private static readonly Lock PadLock = new(); private static bool _initialized; internal static void Initialize() diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs index a36b66d..f6074bd 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -35,7 +35,7 @@ public AggregateRootConverter(Action setup = null) /// /// Writes the JSON representation of the object. /// - /// The to write to. + /// The to write to. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, AggregateRoot value, JsonSerializer serializer) @@ -57,7 +57,7 @@ public override void WriteJson(JsonWriter writer, AggregateRoot value, Jso /// /// Reads the JSON representation of the object. /// - /// The to read from. + /// The to read from. /// Type of the object. /// The existing value of object being read. If there is no existing value then null will be used. /// The existing value has a value. @@ -101,7 +101,7 @@ public override AggregateRoot ReadJson(JsonReader reader, Type objectType, var ctors = objectType.GetConstructors(_options.Flags).ToList(); - if (ctors.Any()) + if (ctors.Count > 0) { var matchingCtor = ctors.SingleOrDefault(info => { diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/MessageConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/MessageConverter.cs index 5c58c1a..516abe4 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/MessageConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/MessageConverter.cs @@ -27,7 +27,7 @@ public class MessageConverter : JsonConverter /// /// Writes the JSON representation of the object. /// - /// The to write to. + /// The to write to. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) @@ -47,7 +47,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s /// /// Reads the JSON representation of the object. /// - /// The to read from. + /// The to read from. /// Type of the object. /// The existing value of object being read. /// The calling serializer. @@ -76,7 +76,7 @@ public override bool CanConvert(Type objectType) } } - internal class MessageConverter : JsonConverter> where T : IRequest + internal sealed class MessageConverter : JsonConverter> where T : IRequest { public MessageConverter() { @@ -147,13 +147,13 @@ public override IMessage ReadJson(JsonReader reader, Type objectType, IMessag var specVersionKey = serializer.ResolvePropertyKeyByConvention(nameof(ICloudEvent.Specversion)); var requestType = objectType.GetGenericArguments()[0]; - var cloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent")); + var cloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent", StringComparison.Ordinal)); var specVersion = document.Root[specVersionKey]!.Value(); var cloudEvent = Activator.CreateInstance(cloudEventType.MakeGenericType(requestType), [message, specVersion]) as IMessage; if (objectType.HasInterfaces(typeof(ISignedCloudEvent<>))) { - var signedCloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent")); + var signedCloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent", StringComparison.Ordinal)); var signature = document.Root[signatureKey]!.Value(); return Activator.CreateInstance(signedCloudEventType.MakeGenericType(requestType), [cloudEvent, signature]) as IMessage; diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs index c20be9f..b0f73a4 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs @@ -23,7 +23,7 @@ public RequestConverter() /// /// Writes the JSON representation of the object. /// - /// The to write to. + /// The to write to. /// The value. /// The calling serializer. /// @@ -35,16 +35,16 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s } /// - /// Gets a value indicating whether this can write JSON. + /// Gets a value indicating whether this can write JSON. /// - /// true if this can write JSON; otherwise, false. + /// true if this can write JSON; otherwise, false. public override bool CanWrite { get; } = false; /// /// Reads the JSON representation of the object. /// - /// The to read from. + /// The to read from. /// Type of the object. /// The existing value of object being read. /// The calling serializer. @@ -71,7 +71,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist else { var field = property.IsAutoProperty() - ? objectType.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>")) + ? objectType.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>", StringComparison.Ordinal)) : objectType.GetAllFields().SingleOrDefault(fi => fi.Name.Equals($"_{property.Name}>", StringComparison.OrdinalIgnoreCase)); if (field != null) { diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/SingleValueObjectConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/SingleValueObjectConverter.cs index 026a01f..18b7a22 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/SingleValueObjectConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/SingleValueObjectConverter.cs @@ -25,7 +25,7 @@ public SingleValueObjectConverter() /// /// Writes the JSON representation of the object. /// - /// The to write to. + /// The to write to. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) @@ -46,7 +46,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s /// /// Reads the JSON representation of the object. /// - /// The to read from. + /// The to read from. /// Type of the object. /// The existing value of object being read. /// The calling serializer. @@ -75,7 +75,7 @@ public override bool CanConvert(Type objectType) } } - internal class SingleValueObjectConverter : JsonConverter> + internal sealed class SingleValueObjectConverter : JsonConverter> { public SingleValueObjectConverter() { diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs index 371c603..2ec2dd0 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -35,7 +35,7 @@ public ValueObjectConverter(Action setup = null) /// /// Writes the JSON representation of the object. /// - /// The to write to. + /// The to write to. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, ValueObject value, JsonSerializer serializer) @@ -63,7 +63,7 @@ public override void WriteJson(JsonWriter writer, ValueObject value, JsonSeriali /// /// Reads the JSON representation of the object. /// - /// The to read from. + /// The to read from. /// Type of the object. /// The existing value of object being read. If there is no existing value then null will be used. /// The existing value has a value. @@ -112,7 +112,7 @@ public override ValueObject ReadJson(JsonReader reader, Type objectType, ValueOb } var ctors = objectType.GetConstructors(_options.Flags).ToList(); - if (ctors.Any()) + if (ctors.Count > 0) { var matchingCtor = ctors.SingleOrDefault(info => { diff --git a/src/Savvyio.Extensions.SimpleQueueService/AmazonResourceNameOptions.cs b/src/Savvyio.Extensions.SimpleQueueService/AmazonResourceNameOptions.cs index 19548e8..087ce66 100644 --- a/src/Savvyio.Extensions.SimpleQueueService/AmazonResourceNameOptions.cs +++ b/src/Savvyio.Extensions.SimpleQueueService/AmazonResourceNameOptions.cs @@ -73,7 +73,7 @@ public void ValidateOptions() { Validator.ThrowIfInvalidState(Partition.IsNullOrWhiteSpace()); Validator.ThrowIfInvalidState(Region.IsNullOrWhiteSpace()); - Validator.ThrowIfInvalidState(AccountId.IsNullOrWhiteSpace() || AccountId.Length != 12 || !AccountId.IsNumeric(NumberStyles.Integer)); + Validator.ThrowIfInvalidState(AccountId.IsNullOrWhiteSpace() || AccountId.Length != 12 || !AccountId.IsNumeric(NumberStyles.Integer, CultureInfo.InvariantCulture)); } } } diff --git a/src/Savvyio.Extensions.Text.Json/Bootstrapper.cs b/src/Savvyio.Extensions.Text.Json/Bootstrapper.cs index 46dcfe7..732a75d 100644 --- a/src/Savvyio.Extensions.Text.Json/Bootstrapper.cs +++ b/src/Savvyio.Extensions.Text.Json/Bootstrapper.cs @@ -1,10 +1,11 @@ -using Cuemon.Extensions.Text.Json.Formatters; +using Cuemon.Extensions.Text.Json.Formatters; +using System.Threading; namespace Savvyio.Extensions.Text.Json { internal static class Bootstrapper { - private static readonly object PadLock = new(); + private static readonly Lock PadLock = new(); private static bool _initialized; internal static void Initialize() diff --git a/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs b/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs index 37929a3..7e2c742 100644 --- a/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs +++ b/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs @@ -61,7 +61,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer } } - internal class MessageConverter : JsonConverter> where T : IRequest + internal sealed class MessageConverter : JsonConverter> where T : IRequest { public MessageConverter() { @@ -109,13 +109,13 @@ public override IMessage Read(ref Utf8JsonReader reader, Type typeToConvert, var specVersionKey = options.PropertyNamingPolicy.ConvertName(nameof(ICloudEvent.Specversion)); var requestType = typeToConvert.GetGenericArguments()[0]; - var cloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent")); + var cloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent", StringComparison.Ordinal)); var specVersion = document.RootElement.GetProperty(specVersionKey).GetString(); var cloudEvent = Activator.CreateInstance(cloudEventType.MakeGenericType(requestType), [message, specVersion]) as IMessage; if (typeToConvert.HasInterfaces(typeof(ISignedCloudEvent<>))) { - var signedCloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent")); + var signedCloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent", StringComparison.Ordinal)); var signature = document.RootElement.GetProperty(signatureKey).GetString(); return Activator.CreateInstance(signedCloudEventType.MakeGenericType(requestType), [cloudEvent, signature]) as IMessage; diff --git a/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs b/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs index a8bf4ed..0bb4eb1 100644 --- a/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs +++ b/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs @@ -53,7 +53,7 @@ public override IRequest Read(ref Utf8JsonReader reader, Type typeToConvert, Jso else { var field = property.IsAutoProperty() - ? typeToConvert.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>")) + ? typeToConvert.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>", StringComparison.Ordinal)) : typeToConvert.GetAllFields().SingleOrDefault(fi => fi.Name.Equals($"_{property.Name}>", StringComparison.OrdinalIgnoreCase)); if (field != null) { diff --git a/src/Savvyio.Extensions.Text.Json/Converters/SingleValueObjectConverter.cs b/src/Savvyio.Extensions.Text.Json/Converters/SingleValueObjectConverter.cs index 558e8df..b75e20f 100644 --- a/src/Savvyio.Extensions.Text.Json/Converters/SingleValueObjectConverter.cs +++ b/src/Savvyio.Extensions.Text.Json/Converters/SingleValueObjectConverter.cs @@ -52,7 +52,7 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer } } - internal class SingleValueObjectConverter : JsonConverter> + internal sealed class SingleValueObjectConverter : JsonConverter> { public SingleValueObjectConverter() { diff --git a/src/Savvyio.Messaging/Cryptography/SignedMessageExtensions.cs b/src/Savvyio.Messaging/Cryptography/SignedMessageExtensions.cs index 7f7369a..6d9768c 100644 --- a/src/Savvyio.Messaging/Cryptography/SignedMessageExtensions.cs +++ b/src/Savvyio.Messaging/Cryptography/SignedMessageExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using Cuemon; namespace Savvyio.Messaging.Cryptography @@ -32,7 +32,7 @@ public static void CheckSignature(this ISignedMessage message, IMarshaller Validator.ThrowIfNull(marshaller); Validator.ThrowIfInvalidConfigurator(setup, out _); var baseMessage = message.Clone().Sign(marshaller, setup); - if (!message.Signature.Equals(baseMessage.Signature)) + if (!message.Signature.Equals(baseMessage.Signature, StringComparison.Ordinal)) { throw new ArgumentOutOfRangeException(nameof(message), message.Signature, "The signature of the message does not match the cryptographically calculated value. Either you are using an incorrect secret and/or algorithm or the message has been tampered with."); } diff --git a/src/Savvyio.Messaging/MessageAsyncEnumerable.cs b/src/Savvyio.Messaging/MessageAsyncEnumerable.cs index a2f9cb2..d40c7d1 100644 --- a/src/Savvyio.Messaging/MessageAsyncEnumerable.cs +++ b/src/Savvyio.Messaging/MessageAsyncEnumerable.cs @@ -48,7 +48,7 @@ public MessageAsyncEnumerable(IAsyncEnumerable> source, Action /// Returns an enumerator that iterates asynchronously through the collection. /// - /// A that may be used to cancel the asynchronous iteration. + /// A that may be used to cancel the asynchronous iteration. /// An enumerator that can be used to iterate asynchronously through the collection. public virtual IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default) { diff --git a/src/Savvyio.Messaging/MessageAsyncEnumerator.cs b/src/Savvyio.Messaging/MessageAsyncEnumerator.cs index bf83dc9..8f7be43 100644 --- a/src/Savvyio.Messaging/MessageAsyncEnumerator.cs +++ b/src/Savvyio.Messaging/MessageAsyncEnumerator.cs @@ -4,7 +4,7 @@ namespace Savvyio.Messaging { - internal class MessageAsyncEnumerator : IAsyncEnumerator> where T : IRequest + internal sealed class MessageAsyncEnumerator : IAsyncEnumerator> where T : IRequest { private readonly IAsyncEnumerator> _source; private readonly MessageAsyncEnumerableOptions _options; diff --git a/test/Savvyio.Extensions.SimpleQueueService.Tests/AmazonResourceNameOptionsTest.cs b/test/Savvyio.Extensions.SimpleQueueService.Tests/AmazonResourceNameOptionsTest.cs index fbffc67..6fc48c1 100644 --- a/test/Savvyio.Extensions.SimpleQueueService.Tests/AmazonResourceNameOptionsTest.cs +++ b/test/Savvyio.Extensions.SimpleQueueService.Tests/AmazonResourceNameOptionsTest.cs @@ -37,7 +37,7 @@ public void ValidateOptions_ThrowsInvalidOperationException_WhenAccountIdIsNullO var sut2 = Assert.Throws(() => sut1.ValidateOptions()); var sut3 = Assert.Throws(() => Validator.ThrowIfInvalidOptions(sut1)); - Assert.Equal($"Operation is not valid due to the current state of the object. (Expression '{nameof(AmazonResourceNameOptions.AccountId)}.IsNullOrWhiteSpace() || {nameof(AmazonResourceNameOptions.AccountId)}.Length != 12 || !{nameof(AmazonResourceNameOptions.AccountId)}.IsNumeric(NumberStyles.Integer)')", sut2.Message); + Assert.Equal($"Operation is not valid due to the current state of the object. (Expression '{nameof(AmazonResourceNameOptions.AccountId)}.IsNullOrWhiteSpace() || {nameof(AmazonResourceNameOptions.AccountId)}.Length != 12 || !{nameof(AmazonResourceNameOptions.AccountId)}.IsNumeric(NumberStyles.Integer, CultureInfo.InvariantCulture)')", sut2.Message); Assert.Equal($"{nameof(AmazonResourceNameOptions)} are not in a valid state. (Parameter '{nameof(sut1)}')", sut3.Message); Assert.IsType(sut3.InnerException); } From b50f62ca4b5e915a0d7fed02a76d6b81d4673986 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Fri, 24 Jul 2026 16:29:58 +0200 Subject: [PATCH 10/14] =?UTF-8?q?=F0=9F=93=9D=20update=20xml=20documentati?= =?UTF-8?q?on=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update XML doc comment examples in Copilot instructions to use correct tag formats: replace see cref with type references, and use c tags for array types instead of see cref. These corrections align documentation with .NET documentation best practices. --- .github/copilot-instructions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4cd6ee6..f114fb5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -541,9 +541,9 @@ namespace Cuemon.Security } /// - /// Computes the hash value for the specified . + /// Computes the hash value for the specified IConvertible[]. /// - /// The to compute the hash code for. + /// The IConvertible[] to compute the hash code for. /// A containing the computed hash code of the specified . public virtual HashResult ComputeHash(params IConvertible[] input) { @@ -561,9 +561,9 @@ namespace Cuemon.Security } /// - /// Computes the hash value for the specified . + /// Computes the hash value for the specified byte[]. /// - /// The to compute the hash code for. + /// The byte[] to compute the hash code for. /// A containing the computed hash code of the specified . public abstract HashResult ComputeHash(byte[] input); From 32b18818325125516b472b15e06f3dc4bbe7f915 Mon Sep 17 00:00:00 2001 From: gimlichael Date: Fri, 24 Jul 2026 16:35:10 +0200 Subject: [PATCH 11/14] =?UTF-8?q?=F0=9F=93=9D=20update=20changelog=20for?= =?UTF-8?q?=20v5.0.10=20with=20dependency=20updates=20and=20enhancements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98342c0..037f20e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. -## [5.0.10] - 2026-07-22 +## [5.0.10] - 2026-07-24 -This is a service update that focuses on package dependencies. +This is a patch release focused on dependency updates, code analysis hardening, .NET 9 modernization, and code style enforcement. + +### Added + +- SQLitePCLRaw.bundle_e_sqlite3 package to resolve transitive security warnings in test projects, +- Code analysis enforcement in build pipeline with latest analyzer level and recommended mode, +- `.editorconfig` configuration for consistent code style and formatting rules across the codebase. + +### Changed + +- Dependency versions bumped to latest stable releases: AWSSDK.SQS and AWSSDK.SimpleNotificationService (4.0.100 → 4.0.100.6), NATS.Client packages (2.8.2 → 3.0.1), Microsoft.Data.Sqlite (10.0.9 → 10.0.10), Microsoft.Extensions.Logging.Abstractions (10.0.9 → 10.0.10), Microsoft.NET.Test.Sdk (18.7.0 → 18.8.1), EntityFrameworkCore net9 (9.0.17 → 9.0.18), EntityFrameworkCore net10 (10.0.9 → 10.0.10), Codebelt extensions (10.1.5 → 10.1.6, 11.1.1 → 11.1.2), Cuemon extensions (10.5.4 → 10.5.5), +- NGINX base image for DocFX container (1.31.2-alpine → 1.31-alpine), +- Core platform code modernized for .NET 9: replaced object locks with System.Threading.Lock, added CultureInfo.InvariantCulture to culture-aware StringBuilder and string operations, optimized Enumerable.Any() checks to Count > 0 on known collection types, +- Roslyn analyzer suppressions added for CA1711, CA1725, CA1848, and CA1873 with justifications, +- XML documentation examples corrected in Copilot instructions to use proper see cref and c tag formats. ## [5.0.9] - 2026-07-01 @@ -1064,7 +1078,8 @@ Noticeable highlights: - QueryHandler class in the Savvyio.Queries namespace that defines a generic and consistent way of handling Query objects that implements the IQuery interface - SavvyioOptionsExtensions class in the Savvyio.Queries namespace that consist of extension methods for the SavvyioOptions class: AddQueryHandler, AddQueryDispatcher -[Unreleased]: https://github.com/codebeltnet/savvyio/compare/v5.0.9...HEAD +[Unreleased]: https://github.com/codebeltnet/savvyio/compare/v5.0.10...HEAD +[5.0.10]: https://github.com/codebeltnet/savvyio/compare/v5.0.9...v5.0.10 [5.0.9]: https://github.com/codebeltnet/savvyio/compare/v5.0.8...v5.0.9 [5.0.8]: https://github.com/codebeltnet/savvyio/compare/v5.0.7...v5.0.8 [5.0.7]: https://github.com/codebeltnet/savvyio/compare/v5.0.6...v5.0.7 From ef7ec3f003f307257469f87b824c9620bc5ea471 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Sat, 25 Jul 2026 17:55:08 +0200 Subject: [PATCH 12/14] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20for=20imp?= =?UTF-8?q?roved=20code=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract helper methods in HandlerServicesDescriptor and converters (Newtonsoft.Json and Text.Json) to improve code organization and maintainability without changing behavior. --- src/Savvyio.Core/HandlerServicesDescriptor.cs | 78 +++++++----- .../Converters/AggregateRootConverter.cs | 115 +++++++++-------- .../Converters/RequestConverter.cs | 39 +++--- .../Converters/ValueObjectConverter.cs | 116 ++++++++++-------- .../Converters/MessageConverter.cs | 95 +++++++------- .../Converters/RequestConverter.cs | 39 +++--- 6 files changed, 273 insertions(+), 209 deletions(-) diff --git a/src/Savvyio.Core/HandlerServicesDescriptor.cs b/src/Savvyio.Core/HandlerServicesDescriptor.cs index 7e5a311..e07b7f5 100644 --- a/src/Savvyio.Core/HandlerServicesDescriptor.cs +++ b/src/Savvyio.Core/HandlerServicesDescriptor.cs @@ -55,25 +55,12 @@ public override string ToString() var builder = new StringBuilder(); foreach (var model in GenerateHandlerDiscoveries()) { - var discovery = $"Discovered {model.ImplementationsCount} {model.AbstractionType} implementation{(model.ImplementationsCount > 1 ? "s" : "")} covering a total of {model.DelegatesCount} {model.DelegateType} method{(model.DelegatesCount > 1 ? "s" : "")}"; + var discovery = FormatDiscoveryHeader(model); builder.Append(discovery); builder.AppendLine(); foreach (var assembly in model.Assemblies) { - builder.AppendLine(); - builder.AppendLine(CultureInfo.InvariantCulture, $"Assembly: {assembly.Name}"); - builder.AppendLine(CultureInfo.InvariantCulture, $"Namespace: {assembly.Namespace}"); - builder.AppendLine(); - - foreach (var implementation in assembly.Implementations) - { - builder.AppendLine(CultureInfo.InvariantCulture, $"<{implementation.Name}>"); - foreach (var @delegate in implementation.Delegates) - { - builder.AppendLine(CultureInfo.InvariantCulture, $"\t*{@delegate.Type} --> &{@delegate.Handler}"); - } - builder.AppendLine(); - } + AppendAssembly(builder, assembly); } builder.AppendLine(Generate.FixedString('-', discovery.Length.Max(discovery.Length.Max(discovery.Length)))); builder.AppendLine(); @@ -81,36 +68,61 @@ public override string ToString() return builder.ToString().TrimEnd(); } + private static string FormatDiscoveryHeader(HandlerDiscoveryModel model) + { + return $"Discovered {model.ImplementationsCount} {model.AbstractionType} implementation{(model.ImplementationsCount > 1 ? "s" : "")} covering a total of {model.DelegatesCount} {model.DelegateType} method{(model.DelegatesCount > 1 ? "s" : "")}"; + } + + private static void AppendAssembly(StringBuilder builder, HandlerServiceAssemblyModel assembly) + { + builder.AppendLine(); + builder.AppendLine(CultureInfo.InvariantCulture, $"Assembly: {assembly.Name}"); + builder.AppendLine(CultureInfo.InvariantCulture, $"Namespace: {assembly.Namespace}"); + builder.AppendLine(); + + foreach (var implementation in assembly.Implementations) + { + builder.AppendLine(CultureInfo.InvariantCulture, $"<{implementation.Name}>"); + foreach (var @delegate in implementation.Delegates) + { + builder.AppendLine(CultureInfo.InvariantCulture, $"\t*{@delegate.Type} --> &{@delegate.Handler}"); + } + builder.AppendLine(); + } + } + /// /// Generates the handler discoveries. /// /// A collection of representing the handler discoveries. public IEnumerable GenerateHandlerDiscoveries() { - if (_models == null) + if (_models != null) { return _models; } + + lock (_locker) { - lock (_locker) + _models ??= BuildHandlerDiscoveries(); + } + return _models; + } + + private List BuildHandlerDiscoveries() + { + var models = new List(); + foreach (var serviceType in ServiceTypes) + { + var serviceRequestType = serviceType.GetInterface("IHandler`1")?.GenericTypeArguments.Single(); + if (serviceRequestType == null) { continue; } + foreach (var discoveredServicesGroup in DiscoveredServices) { - if (_models == null) + var model = new HandlerDiscoveryModel(serviceType, serviceRequestType, discoveredServicesGroup); + if (model.ImplementationsCount > 0) { - _models = new List(); - foreach (var serviceType in ServiceTypes) - { - var serviceRequestType = serviceType.GetInterface("IHandler`1")?.GenericTypeArguments.Single(); - if (serviceRequestType == null) { continue; } - foreach (var discoveredServicesGroup in DiscoveredServices) - { - var model = new HandlerDiscoveryModel(serviceType, serviceRequestType, discoveredServicesGroup); - if (model.ImplementationsCount > 0) - { - _models.Add(model); - } - } - } + models.Add(model); } } } - return _models; + return models; } } } diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs index f6074bd..003c02c 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/AggregateRootConverter.cs @@ -69,69 +69,86 @@ public override AggregateRoot ReadJson(JsonReader reader, Type objectType, var properties = objectType.GetRuntimePropertiesExceptOf>().Where(pi => pi.CanRead).ToList(); if (idProperty != null) { properties.Insert(0, idProperty); } + var propertyData = ReadPropertyData(reader, properties, serializer); + + var result = CreateAggregateRoot(objectType, properties, propertyData); + if (result != null) { return result; } + + throw ExceptionInsights.Embed(new InvalidOperationException($"Unable to deserialize {objectType.FullName}; consider adding a custom converter for this type."), MethodBase.GetCurrentMethod(), Arguments.ToArray(reader, objectType, existingValue, hasExistingValue, serializer)); + } + + private List ReadPropertyData(JsonReader reader, List properties, JsonSerializer serializer) + { var propertyData = new List(); - if (reader.TokenType == JsonToken.StartObject) + if (reader.TokenType != JsonToken.StartObject) { return propertyData; } + + var depth = reader.Depth; + while (reader.Read()) { - var depth = reader.Depth; - while (reader.Read()) + switch (reader.TokenType) { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - var propertyName = (string)reader.Value; - var matchingProperty = properties.FirstOrDefault(pi => pi.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); - if (matchingProperty != null) - { - reader.Read(); - if (matchingProperty.PropertyType.HasTypes(typeof(SingleValueObject<>))) - { - propertyData.Add(new DataPair(matchingProperty.Name, serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType)); - } - else - { - propertyData.Add(new DataPair(matchingProperty.Name, ParserFactory.FromObject().Parse(reader.Value?.ToString(), matchingProperty.PropertyType, o => o.FormatProvider = _options.FormatProvider) ?? serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType)); - } - } - break; - } - - if (reader.Depth == depth && reader.TokenType == JsonToken.EndObject) { break; } + case JsonToken.PropertyName: + var propertyName = (string)reader.Value; + var matchingProperty = properties.FirstOrDefault(pi => pi.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); + if (matchingProperty != null) + { + reader.Read(); + propertyData.Add(ReadDataPair(reader, matchingProperty, serializer)); + } + break; } + + if (reader.Depth == depth && reader.TokenType == JsonToken.EndObject) { break; } } + return propertyData; + } + private DataPair ReadDataPair(JsonReader reader, PropertyInfo matchingProperty, JsonSerializer serializer) + { + if (matchingProperty.PropertyType.HasTypes(typeof(SingleValueObject<>))) + { + return new DataPair(matchingProperty.Name, serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType); + } + + return new DataPair(matchingProperty.Name, ParserFactory.FromObject().Parse(reader.Value?.ToString(), matchingProperty.PropertyType, o => o.FormatProvider = _options.FormatProvider) ?? serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType); + } + + private AggregateRoot CreateAggregateRoot(Type objectType, List properties, List propertyData) + { var ctors = objectType.GetConstructors(_options.Flags).ToList(); - if (ctors.Count > 0) + if (ctors.Count == 0) { return null; } + + var matchingCtor = ctors.SingleOrDefault(info => MatchesConstructor(info, propertyData)); + if (matchingCtor != null) { - var matchingCtor = ctors.SingleOrDefault(info => - { - var paramters = info.GetParameters().ToList(); - return paramters.Count == propertyData.Count && paramters.Select(pi => pi.ParameterType).SequenceEqual(propertyData.Select(pair => pair.Type)); - }); + return matchingCtor.Invoke(propertyData.Select(pair => pair.Value).ToArray()) as AggregateRoot; + } - if (matchingCtor != null) - { - return matchingCtor.Invoke(propertyData.Select(pair => pair.Value).ToArray()) as AggregateRoot; - } - else + var defaultCtor = ctors.SingleOrDefault(ci => ci.GetParameters().Length == 0); + if (defaultCtor == null) { return null; } + + return PopulateDefaultConstructed(defaultCtor, properties, propertyData); + } + + private static bool MatchesConstructor(ConstructorInfo info, List propertyData) + { + var paramters = info.GetParameters().ToList(); + return paramters.Count == propertyData.Count && paramters.Select(pi => pi.ParameterType).SequenceEqual(propertyData.Select(pair => pair.Type)); + } + + private static AggregateRoot PopulateDefaultConstructed(ConstructorInfo defaultCtor, List properties, List propertyData) + { + var ar = defaultCtor.Invoke(Array.Empty()) as AggregateRoot; + foreach (var property in properties) + { + if (property.CanWrite) { - var defaultCtor = ctors.SingleOrDefault(ci => ci.GetParameters().Length == 0); - if (defaultCtor != null) - { - var ar = defaultCtor.Invoke(Array.Empty()) as AggregateRoot; - foreach (var property in properties) - { - if (property.CanWrite) - { - property.SetValue(ar, propertyData.SingleOrDefault(pair => pair.Name.Equals(property.Name, StringComparison.OrdinalIgnoreCase))?.Value); - } - } - return ar; - } + property.SetValue(ar, propertyData.SingleOrDefault(pair => pair.Name.Equals(property.Name, StringComparison.OrdinalIgnoreCase))?.Value); } } - throw ExceptionInsights.Embed(new InvalidOperationException($"Unable to deserialize {objectType.FullName}; consider adding a custom converter for this type."), MethodBase.GetCurrentMethod(), Arguments.ToArray(reader, objectType, existingValue, hasExistingValue, serializer)); + return ar; } } } diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs index b0f73a4..ea4c41d 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/RequestConverter.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; using Cuemon.Extensions.Reflection; using Newtonsoft.Json; @@ -64,30 +65,32 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist if (document.Root[jProperty.Name] != null) { var value = serializer.Deserialize(document.Root[jProperty.Name].CreateReader(), property.PropertyType); - if (property.CanWrite) - { - property.SetValue(instance, value); - } - else - { - var field = property.IsAutoProperty() - ? objectType.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>", StringComparison.Ordinal)) - : objectType.GetAllFields().SingleOrDefault(fi => fi.Name.Equals($"_{property.Name}>", StringComparison.OrdinalIgnoreCase)); - if (field != null) - { - field.SetValue(instance, value); - } - else - { - throw new NotSupportedException($"This deserializer only supports rehydration of {nameof(IRequest)} implementations that either use auto-properties or have a naming convention that makes it possible to tie non-writable properties with the backing field equivalent."); - } - } + SetPropertyOrField(instance, objectType, property, value); } } return instance as IRequest; } + private static void SetPropertyOrField(object instance, Type objectType, PropertyInfo property, object value) + { + if (property.CanWrite) + { + property.SetValue(instance, value); + return; + } + + var field = property.IsAutoProperty() + ? objectType.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>", StringComparison.Ordinal)) + : objectType.GetAllFields().SingleOrDefault(fi => fi.Name.Equals($"_{property.Name}>", StringComparison.OrdinalIgnoreCase)); + if (field == null) + { + throw new NotSupportedException($"This deserializer only supports rehydration of {nameof(IRequest)} implementations that either use auto-properties or have a naming convention that makes it possible to tie non-writable properties with the backing field equivalent."); + } + + field.SetValue(instance, value); + } + /// /// Determines whether this instance can convert the specified object type. /// diff --git a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs index 2ec2dd0..757a49e 100644 --- a/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs +++ b/src/Savvyio.Extensions.Newtonsoft.Json/Converters/ValueObjectConverter.cs @@ -81,68 +81,86 @@ public override ValueObject ReadJson(JsonReader reader, Type objectType, ValueOb } } + var propertyData = ReadPropertyData(reader, properties, serializer); + + var result = CreateValueObject(objectType, properties, propertyData); + if (result != null) { return result; } + + throw ExceptionInsights.Embed(new InvalidOperationException($"Unable to deserialize {objectType.FullName}; consider adding a custom converter for this type."), MethodBase.GetCurrentMethod(), Arguments.ToArray(reader, objectType, existingValue, hasExistingValue, serializer)); + } + + private List ReadPropertyData(JsonReader reader, List properties, JsonSerializer serializer) + { var propertyData = new List(); - if (reader.TokenType == JsonToken.StartObject) + if (reader.TokenType != JsonToken.StartObject) { return propertyData; } + + var depth = reader.Depth; + while (reader.Read()) { - var depth = reader.Depth; - while (reader.Read()) + switch (reader.TokenType) { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - var propertyName = (string)reader.Value; - var matchingProperty = properties.FirstOrDefault(pi => pi.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); - if (matchingProperty != null) - { - reader.Read(); - if (matchingProperty.PropertyType.HasTypes(typeof(SingleValueObject<>))) - { - propertyData.Add(new DataPair(matchingProperty.Name, serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType)); - } - else - { - propertyData.Add(new DataPair(matchingProperty.Name, ParserFactory.FromObject().Parse(reader.Value?.ToString(), matchingProperty.PropertyType, o => o.FormatProvider = _options.FormatProvider) ?? serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType)); - } - } - break; - } - - if (reader.Depth == depth && reader.TokenType == JsonToken.EndObject) { break; } + case JsonToken.PropertyName: + var propertyName = (string)reader.Value; + var matchingProperty = properties.FirstOrDefault(pi => pi.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); + if (matchingProperty != null) + { + reader.Read(); + propertyData.Add(ReadDataPair(reader, matchingProperty, serializer)); + } + break; } + + if (reader.Depth == depth && reader.TokenType == JsonToken.EndObject) { break; } } + return propertyData; + } + + private DataPair ReadDataPair(JsonReader reader, PropertyInfo matchingProperty, JsonSerializer serializer) + { + if (matchingProperty.PropertyType.HasTypes(typeof(SingleValueObject<>))) + { + return new DataPair(matchingProperty.Name, serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType); + } + + return new DataPair(matchingProperty.Name, ParserFactory.FromObject().Parse(reader.Value?.ToString(), matchingProperty.PropertyType, o => o.FormatProvider = _options.FormatProvider) ?? serializer.Deserialize(reader, matchingProperty.PropertyType), matchingProperty.PropertyType); + } + + private ValueObject CreateValueObject(Type objectType, List properties, List propertyData) + { var ctors = objectType.GetConstructors(_options.Flags).ToList(); - if (ctors.Count > 0) + if (ctors.Count == 0) { return null; } + + var matchingCtor = ctors.SingleOrDefault(info => MatchesConstructor(info, propertyData)); + if (matchingCtor != null) { - var matchingCtor = ctors.SingleOrDefault(info => - { - var paramters = info.GetParameters().ToList(); - return paramters.Count == propertyData.Count && paramters.Select(pi => pi.ParameterType).SequenceEqual(propertyData.Select(pair => pair.Type)); - }); + return matchingCtor.Invoke(propertyData.Select(pair => pair.Value).ToArray()) as ValueObject; + } - if (matchingCtor != null) - { - return matchingCtor.Invoke(propertyData.Select(pair => pair.Value).ToArray()) as ValueObject; - } - else + var defaultCtor = ctors.SingleOrDefault(ci => ci.GetParameters().Length == 0); + if (defaultCtor == null) { return null; } + + return PopulateDefaultConstructed(defaultCtor, properties, propertyData); + } + + private static bool MatchesConstructor(ConstructorInfo info, List propertyData) + { + var paramters = info.GetParameters().ToList(); + return paramters.Count == propertyData.Count && paramters.Select(pi => pi.ParameterType).SequenceEqual(propertyData.Select(pair => pair.Type)); + } + + private static ValueObject PopulateDefaultConstructed(ConstructorInfo defaultCtor, List properties, List propertyData) + { + var vo = defaultCtor.Invoke(Array.Empty()) as ValueObject; + foreach (var property in properties) + { + if (property.CanWrite) { - var defaultCtor = ctors.SingleOrDefault(ci => ci.GetParameters().Length == 0); - if (defaultCtor != null) - { - var vo = defaultCtor.Invoke(Array.Empty()) as ValueObject; - foreach (var property in properties) - { - if (property.CanWrite) - { - property.SetValue(vo, propertyData.SingleOrDefault(pair => pair.Name.Equals(property.Name, StringComparison.OrdinalIgnoreCase))?.Value); - } - } - return vo; - } + property.SetValue(vo, propertyData.SingleOrDefault(pair => pair.Name.Equals(property.Name, StringComparison.OrdinalIgnoreCase))?.Value); } } - throw ExceptionInsights.Embed(new InvalidOperationException($"Unable to deserialize {objectType.FullName}; consider adding a custom converter for this type."), MethodBase.GetCurrentMethod(), Arguments.ToArray(reader, objectType, existingValue, hasExistingValue, serializer)); + return vo; } } } diff --git a/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs b/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs index 7e2c742..06dbfae 100644 --- a/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs +++ b/src/Savvyio.Extensions.Text.Json/Converters/MessageConverter.cs @@ -85,53 +85,15 @@ public override IMessage Read(ref Utf8JsonReader reader, Type typeToConvert, var memberType = typeToConvert.GenericTypeArguments[0]; var time = document.RootElement.GetProperty(timeKey).GetDateTimeOffset().UtcDateTime; var data = (T)document.RootElement.GetProperty(dataKey).Deserialize(memberType!, options); - if (data is IMetadata) // for unknown reasons, Microsoft does not use the custom converter for IMetadataDictionary here; have to fiddle extra around as seen below .. for the record; this just works with Newtonsoft! - { - var md = document.RootElement.GetProperty(dataKey).GetProperty(metadataKey).Deserialize(options); - var property = memberType.GetAllProperties().SingleOrDefault(pi => pi.Name == nameof(IMetadata.Metadata)); - if (property != null) - { - if (property.CanWrite) - { - property.SetValue(data, md); - } - else - { - memberType.GetAllFields().SingleOrDefault(fi => fi.Name.Contains(nameof(IMetadata.Metadata)))?.SetValue(data, md); - } - } - } + + ApplyMetadata(data, memberType, document.RootElement.GetProperty(dataKey), metadataKey, options); var message = new Message(id, source, type, data, time); if (typeToConvert.HasInterfaces(typeof(ICloudEvent<>))) { - var specVersionKey = options.PropertyNamingPolicy.ConvertName(nameof(ICloudEvent.Specversion)); - - var requestType = typeToConvert.GetGenericArguments()[0]; - var cloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent", StringComparison.Ordinal)); - var specVersion = document.RootElement.GetProperty(specVersionKey).GetString(); - var cloudEvent = Activator.CreateInstance(cloudEventType.MakeGenericType(requestType), [message, specVersion]) as IMessage; - - if (typeToConvert.HasInterfaces(typeof(ISignedCloudEvent<>))) - { - var signedCloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent", StringComparison.Ordinal)); - var signature = document.RootElement.GetProperty(signatureKey).GetString(); - - return Activator.CreateInstance(signedCloudEventType.MakeGenericType(requestType), [cloudEvent, signature]) as IMessage; - } - - var reservedKeys = new[] { idKey, sourceKey, timeKey, typeKey, dataKey, metadataKey, signatureKey, specVersionKey }; - - if (cloudEvent is IDictionary dictionary) - { - foreach (var property in document.RootElement.EnumerateObject().Where(jp => !reservedKeys.Contains(jp.Name))) - { - dictionary.Add(property.Name, property.Value.Deserialize(property.Value.GetType(), options)); - } - } - - return cloudEvent; + var reservedKeys = new[] { idKey, sourceKey, timeKey, typeKey, dataKey, metadataKey, signatureKey }; + return CreateCloudEvent(typeToConvert, message, document.RootElement, signatureKey, reservedKeys, options); } if (typeToConvert.HasInterfaces(typeof(ISignedMessage<>))) @@ -144,6 +106,55 @@ public override IMessage Read(ref Utf8JsonReader reader, Type typeToConvert, } } + // for unknown reasons, Microsoft does not use the custom converter for IMetadataDictionary here; have to fiddle extra around as seen below .. for the record; this just works with Newtonsoft! + private static void ApplyMetadata(T data, Type memberType, JsonElement dataElement, string metadataKey, JsonSerializerOptions options) + { + if (data is not IMetadata) { return; } + + var md = dataElement.GetProperty(metadataKey).Deserialize(options); + var property = memberType.GetAllProperties().SingleOrDefault(pi => pi.Name == nameof(IMetadata.Metadata)); + if (property == null) { return; } + + if (property.CanWrite) + { + property.SetValue(data, md); + } + else + { + memberType.GetAllFields().SingleOrDefault(fi => fi.Name.Contains(nameof(IMetadata.Metadata)))?.SetValue(data, md); + } + } + + private static IMessage CreateCloudEvent(Type typeToConvert, Message message, JsonElement root, string signatureKey, string[] reservedKeys, JsonSerializerOptions options) + { + var specVersionKey = options.PropertyNamingPolicy!.ConvertName(nameof(ICloudEvent.Specversion)); + var requestType = typeToConvert.GetGenericArguments()[0]; + var cloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.CloudEvent", StringComparison.Ordinal)); + var specVersion = root.GetProperty(specVersionKey).GetString(); + var cloudEvent = Activator.CreateInstance(cloudEventType.MakeGenericType(requestType), [message, specVersion]) as IMessage; + + if (typeToConvert.HasInterfaces(typeof(ISignedCloudEvent<>))) + { + var signedCloudEventType = MessageConverter.CloudEventTypes.Value.Single(ti => ti.FullName!.StartsWith("Savvyio.EventDriven.Messaging.CloudEvents.Cryptography.SignedCloudEvent", StringComparison.Ordinal)); + var signature = root.GetProperty(signatureKey).GetString(); + + return Activator.CreateInstance(signedCloudEventType.MakeGenericType(requestType), [cloudEvent, signature]) as IMessage; + } + + AddExtensionAttributes(cloudEvent, root, [.. reservedKeys, specVersionKey], options); + return cloudEvent; + } + + private static void AddExtensionAttributes(IMessage cloudEvent, JsonElement root, string[] reservedKeys, JsonSerializerOptions options) + { + if (cloudEvent is not IDictionary dictionary) { return; } + + foreach (var property in root.EnumerateObject().Where(jp => !reservedKeys.Contains(jp.Name))) + { + dictionary.Add(property.Name, property.Value.Deserialize(property.Value.GetType(), options)); + } + } + public override void Write(Utf8JsonWriter writer, IMessage value, JsonSerializerOptions options) { writer.WriteStartObject(); diff --git a/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs b/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs index 0bb4eb1..0b5f67b 100644 --- a/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs +++ b/src/Savvyio.Extensions.Text.Json/Converters/RequestConverter.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; @@ -46,30 +47,32 @@ public override IRequest Read(ref Utf8JsonReader reader, Type typeToConvert, Jso if (document.RootElement.TryGetProperty(jProperty.Name, out var element)) { var value = element.Deserialize(property.PropertyType, options); - if (property.CanWrite) - { - property.SetValue(instance, value); - } - else - { - var field = property.IsAutoProperty() - ? typeToConvert.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>", StringComparison.Ordinal)) - : typeToConvert.GetAllFields().SingleOrDefault(fi => fi.Name.Equals($"_{property.Name}>", StringComparison.OrdinalIgnoreCase)); - if (field != null) - { - field.SetValue(instance, value); - } - else - { - throw new NotSupportedException($"This deserializer only supports rehydration of {nameof(IRequest)} implementations that either use auto-properties or have a naming convention that makes it possible to tie non-writable properties with the backing field equivalent."); - } - } + SetPropertyOrField(instance, typeToConvert, property, value); } } return instance as IRequest; } } + private static void SetPropertyOrField(object instance, Type typeToConvert, PropertyInfo property, object value) + { + if (property.CanWrite) + { + property.SetValue(instance, value); + return; + } + + var field = property.IsAutoProperty() + ? typeToConvert.GetAllFields().SingleOrDefault(fi => fi.Name.StartsWith($"<{property.Name}>", StringComparison.Ordinal)) + : typeToConvert.GetAllFields().SingleOrDefault(fi => fi.Name.Equals($"_{property.Name}>", StringComparison.OrdinalIgnoreCase)); + if (field == null) + { + throw new NotSupportedException($"This deserializer only supports rehydration of {nameof(IRequest)} implementations that either use auto-properties or have a naming convention that makes it possible to tie non-writable properties with the backing field equivalent."); + } + + field.SetValue(instance, value); + } + /// /// Writes a specified as JSON. /// From 868573d2901313def9cdd28f658ae1eaf40ce6e9 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Sat, 25 Jul 2026 17:55:25 +0200 Subject: [PATCH 13/14] =?UTF-8?q?=E2=9C=85=20add=20json=20converter=20test?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive tests for Text.Json converters (MessageConverter, RequestConverter) and extend Newtonsoft.Json RequestConverter tests with writable properties coverage. --- .../RequestConverterTest.cs | 17 ++++ .../Converters/MessageConverterTest.cs | 86 ++++++++++++++++++ .../Converters/RequestConverterTest.cs | 89 +++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 test/Savvyio.Extensions.Text.Json.Tests/Converters/MessageConverterTest.cs create mode 100644 test/Savvyio.Extensions.Text.Json.Tests/Converters/RequestConverterTest.cs diff --git a/test/Savvyio.Extensions.Newtonsoft.Json.Tests/RequestConverterTest.cs b/test/Savvyio.Extensions.Newtonsoft.Json.Tests/RequestConverterTest.cs index efef9e3..0eeecf6 100644 --- a/test/Savvyio.Extensions.Newtonsoft.Json.Tests/RequestConverterTest.cs +++ b/test/Savvyio.Extensions.Newtonsoft.Json.Tests/RequestConverterTest.cs @@ -47,6 +47,18 @@ public void RequestConverter_ShouldRehydrateAutoPropertyRequests() Assert.Equal("jd@office.com", sut.EmailAddress); } + [Fact] + public void RequestConverter_ShouldRehydrateWritableProperties() + { + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new RequestConverter()); + + var sut = JsonConvert.DeserializeObject("{\"name\":\"Jane Doe\"}", settings); + + Assert.NotNull(sut); + Assert.Equal("Jane Doe", sut.Name); + } + [Fact] public void RequestConverter_ShouldFailWhenNoSupportedBackingFieldExists() { @@ -58,6 +70,11 @@ public void RequestConverter_ShouldFailWhenNoSupportedBackingFieldExists() Assert.StartsWith("This deserializer only supports rehydration", ex.Message); } + private sealed class WritableRequest : IRequest + { + public string Name { get; set; } + } + private sealed class UnsupportedRequest : IRequest { private readonly string _name = string.Empty; diff --git a/test/Savvyio.Extensions.Text.Json.Tests/Converters/MessageConverterTest.cs b/test/Savvyio.Extensions.Text.Json.Tests/Converters/MessageConverterTest.cs new file mode 100644 index 0000000..ea6a551 --- /dev/null +++ b/test/Savvyio.Extensions.Text.Json.Tests/Converters/MessageConverterTest.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Cuemon.Extensions; +using Cuemon.Extensions.IO; +using Codebelt.Extensions.Xunit; +using Savvyio.Assets.EventDriven; +using Savvyio.EventDriven.Messaging; +using Savvyio.EventDriven.Messaging.CloudEvents; +using Savvyio.Messaging; +using Xunit; + +namespace Savvyio.Extensions.Text.Json.Converters +{ + public class MessageConverterTest : Test + { + public MessageConverterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void MessageConverter_ShouldConvertOnlyMessages() + { + var sut = new MessageConverter(); + + Assert.True(sut.CanConvert(typeof(Message))); + Assert.False(sut.CanConvert(typeof(MemberCreated))); + Assert.False(sut.CanConvert(typeof(string))); + } + + [Fact] + public void MessageConverter_ShouldRoundtripCloudEventExtensionAttributes() + { + var utc = DateTime.Parse("2023-11-16T23:24:17.8414532Z", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + var cloudEvent = new MemberCreated("Jane Doe", "jd@office.com").SetEventId("69bccf3b1117425397c5ed9ed757bb0f").SetTimestamp(utc) + .ToMessage("https://fancy.api/members".ToUri(), nameof(MemberCreated), o => + { + o.MessageId = "2d4030d32a254ee8a27046e5bafe696a"; + o.Time = utc; + }).ToCloudEvent(); + + ((IDictionary)cloudEvent).Add("traceparent", "00-abc-def-01"); + + var marshaller = new JsonMarshaller(); + var json = marshaller.Serialize(cloudEvent); + var jsonString = json.ToEncodedString(o => o.LeaveOpen = true); + + TestOutput.WriteLine(jsonString); + + var result = marshaller.Deserialize>(json); + + Assert.Contains("traceparent", jsonString); + Assert.True(((IDictionary)result).ContainsKey("traceparent")); + } + + [Fact] + public void MessageConverter_ShouldRoundtripDataWithWritableMetadata() + { + var utc = DateTime.Parse("2023-11-16T23:24:17.8414532Z", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal); + var data = new WritableMetadataRequest { Name = "Jane Doe" }; + data.Metadata.Add("custom", "value"); + var message = new Message("2d4030d32a254ee8a27046e5bafe696a", "https://fancy.api/members".ToUri(), nameof(WritableMetadataRequest), data, utc); + + var marshaller = new JsonMarshaller(); + var json = marshaller.Serialize(message); + var jsonString = json.ToEncodedString(o => o.LeaveOpen = true); + + TestOutput.WriteLine(jsonString); + + var result = marshaller.Deserialize>(json); + + Assert.NotNull(result); + Assert.Equal("Jane Doe", result.Data.Name); + Assert.NotNull(result.Data.Metadata); + Assert.True(result.Data.Metadata.ContainsKey("custom")); + Assert.Equal("value", result.Data.Metadata["custom"].ToString()); + } + + private sealed record WritableMetadataRequest : IRequest, IMetadata + { + public string Name { get; set; } + + public IMetadataDictionary Metadata { get; set; } = new MetadataDictionary(); + } + } +} diff --git a/test/Savvyio.Extensions.Text.Json.Tests/Converters/RequestConverterTest.cs b/test/Savvyio.Extensions.Text.Json.Tests/Converters/RequestConverterTest.cs new file mode 100644 index 0000000..15e417a --- /dev/null +++ b/test/Savvyio.Extensions.Text.Json.Tests/Converters/RequestConverterTest.cs @@ -0,0 +1,89 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Codebelt.Extensions.Xunit; +using Savvyio.Assets.Commands; +using Xunit; + +namespace Savvyio.Extensions.Text.Json.Converters +{ + public class RequestConverterTest : Test + { + public RequestConverterTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void RequestConverter_ShouldConvertOnlyRequests() + { + var sut = new RequestConverter(); + + Assert.True(sut.CanConvert(typeof(CreateMemberCommand))); + Assert.False(sut.CanConvert(typeof(string))); + } + + [Fact] + public void RequestConverter_ShouldRehydrateAutoPropertyRequests() + { + var sut = JsonSerializer.Deserialize("{\"name\":\"Jane Doe\",\"age\":21,\"emailAddress\":\"jd@office.com\"}", CreateOptions()); + + Assert.NotNull(sut); + Assert.Equal("Jane Doe", sut.Name); + Assert.Equal((byte)21, sut.Age); + Assert.Equal("jd@office.com", sut.EmailAddress); + } + + [Fact] + public void RequestConverter_ShouldRehydrateWritableProperties() + { + var sut = JsonSerializer.Deserialize("{\"name\":\"Jane Doe\"}", CreateOptions()); + + Assert.NotNull(sut); + Assert.Equal("Jane Doe", sut.Name); + } + + [Fact] + public void RequestConverter_ShouldFailWhenNoSupportedBackingFieldExists() + { + var ex = Assert.Throws(() => JsonSerializer.Deserialize("{\"name\":\"Jane Doe\"}", CreateOptions())); + + Assert.StartsWith("This deserializer only supports rehydration", ex.Message); + } + + [Fact] + public void RequestConverter_ShouldRoundtripThroughWrite() + { + var options = CreateOptions(); + var json = JsonSerializer.Serialize(new WritableRequest { Name = "Jane Doe" }, options); + + TestOutput.WriteLine(json); + + var sut = JsonSerializer.Deserialize(json, options); + + Assert.NotNull(sut); + Assert.Equal("Jane Doe", sut.Name); + } + + private static JsonSerializerOptions CreateOptions() + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + options.Converters.Add(new RequestConverter()); + return options; + } + + private sealed class WritableRequest : IRequest + { + public string Name { get; set; } + } + + private sealed class UnsupportedRequest : IRequest + { + private readonly string _name = string.Empty; + + public string Name => _name; + } + } +} From dce9c3da03bdbf736dc247ead00cfdcd31dcec2d Mon Sep 17 00:00:00 2001 From: gimlichael Date: Sat, 25 Jul 2026 18:21:21 +0200 Subject: [PATCH 14/14] =?UTF-8?q?=F0=9F=93=9D=20update=20changelog=20for?= =?UTF-8?q?=20v5.0.10=20with=20test=20coverage=20expansion=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 037f20e..2fa7920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. -## [5.0.10] - 2026-07-24 +## [5.0.10] - 2026-07-26 -This is a patch release focused on dependency updates, code analysis hardening, .NET 9 modernization, and code style enforcement. +This is a patch release focused on dependency updates, code analysis hardening, .NET 9 modernization, code style enforcement, and test coverage expansion. ### Added - SQLitePCLRaw.bundle_e_sqlite3 package to resolve transitive security warnings in test projects, - Code analysis enforcement in build pipeline with latest analyzer level and recommended mode, -- `.editorconfig` configuration for consistent code style and formatting rules across the codebase. +- `.editorconfig` configuration for consistent code style and formatting rules across the codebase, +- Comprehensive tests for Text.Json converters (MessageConverter, RequestConverter), +- Extended Newtonsoft.Json RequestConverter tests with writable properties coverage. ### Changed @@ -20,7 +22,8 @@ This is a patch release focused on dependency updates, code analysis hardening, - NGINX base image for DocFX container (1.31.2-alpine → 1.31-alpine), - Core platform code modernized for .NET 9: replaced object locks with System.Threading.Lock, added CultureInfo.InvariantCulture to culture-aware StringBuilder and string operations, optimized Enumerable.Any() checks to Count > 0 on known collection types, - Roslyn analyzer suppressions added for CA1711, CA1725, CA1848, and CA1873 with justifications, -- XML documentation examples corrected in Copilot instructions to use proper see cref and c tag formats. +- XML documentation examples corrected in Copilot instructions to use proper see cref and c tag formats, +- HandlerServicesDescriptor and converter helper methods extracted for improved code organization and maintainability. ## [5.0.9] - 2026-07-01 @@ -1078,7 +1081,6 @@ Noticeable highlights: - QueryHandler class in the Savvyio.Queries namespace that defines a generic and consistent way of handling Query objects that implements the IQuery interface - SavvyioOptionsExtensions class in the Savvyio.Queries namespace that consist of extension methods for the SavvyioOptions class: AddQueryHandler, AddQueryDispatcher -[Unreleased]: https://github.com/codebeltnet/savvyio/compare/v5.0.10...HEAD [5.0.10]: https://github.com/codebeltnet/savvyio/compare/v5.0.9...v5.0.10 [5.0.9]: https://github.com/codebeltnet/savvyio/compare/v5.0.8...v5.0.9 [5.0.8]: https://github.com/codebeltnet/savvyio/compare/v5.0.7...v5.0.8