diff --git a/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/ConfigureEndpointAzureServiceBusTransport.cs b/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/ConfigureEndpointAzureServiceBusTransport.cs index 5bd0ff5..7f8a329 100644 --- a/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/ConfigureEndpointAzureServiceBusTransport.cs +++ b/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/ConfigureEndpointAzureServiceBusTransport.cs @@ -1,8 +1,4 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using NServiceBus; +using Microsoft.Extensions.DependencyInjection; using NServiceBus.AcceptanceTesting.Customization; using NServiceBus.AcceptanceTesting.Support; using NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests; diff --git a/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests.csproj b/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests.csproj index 169c858..9ac0f00 100644 --- a/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests.csproj +++ b/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests.csproj @@ -3,6 +3,7 @@ net10.0 enable + NServiceBus.AcceptanceTests @@ -14,15 +15,27 @@ - + - + + + + + CloudEvents\When_amqp_binary_message_received.cs + + + CloudEvents\When_http_binary_message_received.cs + + + CloudEvents\When_json_structured_message_received.cs + + diff --git a/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/TestIndependenceSkipBehavior.cs b/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/TestIndependenceSkipBehavior.cs index 075b51f..73543a4 100644 --- a/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/TestIndependenceSkipBehavior.cs +++ b/src/NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests/TestIndependenceSkipBehavior.cs @@ -1,8 +1,8 @@ namespace NServiceBus.Envelope.CloudEvents.ASB.AcceptanceTests; -using NServiceBus.AcceptanceTesting; -using NServiceBus.Pipeline; +using AcceptanceTesting; using NUnit.Framework; +using Pipeline; class TestIndependenceSkipBehavior : IBehavior { diff --git a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_amqp_binary_message_received.cs b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_amqp_binary_message_received.cs index 0888c6f..ed9e3ac 100644 --- a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_amqp_binary_message_received.cs +++ b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_amqp_binary_message_received.cs @@ -1,16 +1,12 @@ -namespace NServiceBus.Envelope.CloudEvents.AcceptanceTests.CloudEvents; +namespace NServiceBus.AcceptanceTests.CloudEvents; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using AcceptanceTesting; using Configuration.AdvancedExtensibility; -using NServiceBus.AcceptanceTests; -using NServiceBus.AcceptanceTests.EndpointTemplates; -using Pipeline; -using Transport; +using EndpointTemplates; +using Envelope.CloudEvents; +using NServiceBus.Pipeline; using NUnit.Framework; +using Transport; public class When_amqp_binary_message_received : NServiceBusAcceptanceTest { @@ -25,7 +21,7 @@ public async Task An_amqp_binary_cloud_event_is_received() // Azure sends CloudEvents as JSON Structured. Below is the equivalent // in the AMQP Binary format. // The headers are set in the CustomSerializationBehavior. - return b.SendLocal(new Message() + return b.SendLocal(new Message { Api = "PutBlockList", ClientRequestId = "4c5dd7fb-2c48-4a27-bb30-5361b5de920a", diff --git a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_http_binary_message_received.cs b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_http_binary_message_received.cs index cdad636..53af526 100644 --- a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_http_binary_message_received.cs +++ b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_http_binary_message_received.cs @@ -1,16 +1,12 @@ -namespace NServiceBus.Envelope.CloudEvents.AcceptanceTests.CloudEvents; +namespace NServiceBus.AcceptanceTests.CloudEvents; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using AcceptanceTesting; using Configuration.AdvancedExtensibility; -using NServiceBus.AcceptanceTests; -using NServiceBus.AcceptanceTests.EndpointTemplates; -using Pipeline; -using Transport; +using EndpointTemplates; +using Envelope.CloudEvents; +using NServiceBus.Pipeline; using NUnit.Framework; +using Transport; public class When_http_binary_message_received : NServiceBusAcceptanceTest { @@ -25,7 +21,7 @@ public async Task An_http_binary_cloud_event_is_received() // Azure sends CloudEvents as JSON Structured. Below is the equivalent // in the HTTP Binary format. // The headers are set in the CustomSerializationBehavior. - return b.SendLocal(new Message() + return b.SendLocal(new Message { Api = "PutBlockList", ClientRequestId = "4c5dd7fb-2c48-4a27-bb30-5361b5de920a", diff --git a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_json_structured_message_received.cs b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_json_structured_message_received.cs index de8f6a0..b13a75c 100644 --- a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_json_structured_message_received.cs +++ b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/CloudEvents/When_json_structured_message_received.cs @@ -1,16 +1,12 @@ -namespace NServiceBus.Envelope.CloudEvents.AcceptanceTests.CloudEvents; +namespace NServiceBus.AcceptanceTests.CloudEvents; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using AcceptanceTesting; using Configuration.AdvancedExtensibility; -using NServiceBus.AcceptanceTests; -using NServiceBus.AcceptanceTests.EndpointTemplates; -using Pipeline; -using Transport; +using EndpointTemplates; +using Envelope.CloudEvents; +using NServiceBus.Pipeline; using NUnit.Framework; +using Transport; public class When_json_structured_message_received : NServiceBusAcceptanceTest { @@ -23,7 +19,7 @@ public async Task A_json_structured_cloud_event_is_received() // The following represents a CloudEvent that Azure Blob Storage generates // to notify that a new blob item has been created. // The headers are set in the CustomSerializationBehavior. - return b.SendLocal(new Message() + return b.SendLocal(new Message { SpecVersion = "1.0", Type = "Microsoft.Storage.BlobCreated", @@ -32,7 +28,7 @@ public async Task A_json_structured_cloud_event_is_received() Id = "9aeb0fdf-c01e-0131-0922-9eb54906e209", Time = "2019-11-18T15:13:39.4589254Z", Subject = "blobServices/default/containers/{storage-container}/blobs/{new-file}", - Data = new NestedData() + Data = new NestedData { Api = "PutBlockList", ClientRequestId = "4c5dd7fb-2c48-4a27-bb30-5361b5de920a", diff --git a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/ConfigureEndpointAcceptanceTestingTransportWithCloudEvents.cs b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/ConfigureEndpointAcceptanceTestingTransportWithCloudEvents.cs index aed0993..42da7e7 100644 --- a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/ConfigureEndpointAcceptanceTestingTransportWithCloudEvents.cs +++ b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/ConfigureEndpointAcceptanceTestingTransportWithCloudEvents.cs @@ -1,11 +1,7 @@ namespace NServiceBus.AcceptanceTests; -using System; -using System.IO; -using System.Threading.Tasks; using AcceptanceTesting.Customization; -using NServiceBus; -using NServiceBus.AcceptanceTesting.Support; +using AcceptanceTesting.Support; using NUnit.Framework; public class ConfigureEndpointAcceptanceTestingTransportWithCloudEvents( diff --git a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/NServiceBus.Envelope.CloudEvents.AcceptanceTests.csproj b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/NServiceBus.Envelope.CloudEvents.AcceptanceTests.csproj index 3162ed9..d9b174d 100644 --- a/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/NServiceBus.Envelope.CloudEvents.AcceptanceTests.csproj +++ b/src/NServiceBus.Envelope.CloudEvents.AcceptanceTests/NServiceBus.Envelope.CloudEvents.AcceptanceTests.csproj @@ -3,6 +3,7 @@ net10.0 enable + NServiceBus.AcceptanceTests @@ -13,7 +14,7 @@ - + diff --git a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/Cleanup.cs b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/Cleanup.cs index 5c561a4..595f5d0 100644 --- a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/Cleanup.cs +++ b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/Cleanup.cs @@ -1,9 +1,5 @@ namespace NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Amazon.S3; using Amazon.S3.Model; using Amazon.S3.Util; diff --git a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ClientFactories.cs b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ClientFactories.cs index 5c4d476..0d5cc16 100644 --- a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ClientFactories.cs +++ b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ClientFactories.cs @@ -2,7 +2,6 @@ namespace NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests; -using System; using Amazon.Runtime; using Amazon.S3; using Amazon.SimpleNotificationService; diff --git a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ConfigureEndpointSqsTransport.cs b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ConfigureEndpointSqsTransport.cs index c34aaf9..df9bedc 100644 --- a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ConfigureEndpointSqsTransport.cs +++ b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/ConfigureEndpointSqsTransport.cs @@ -1,6 +1,5 @@ namespace NServiceBus.AcceptanceTests; -using System.Threading.Tasks; using AcceptanceTesting.Customization; using AcceptanceTesting.Support; using Envelope.CloudEvents.SQS.AcceptanceTests; diff --git a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests.csproj b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests.csproj index fbc8bf5..8238141 100644 --- a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests.csproj +++ b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests.csproj @@ -9,17 +9,29 @@ - + - + + + + + CloudEvents\When_amqp_binary_message_received.cs + + + CloudEvents\When_http_binary_message_received.cs + + + CloudEvents\When_json_structured_message_received.cs + + diff --git a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/SetupFixture.cs b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/SetupFixture.cs index 1eb2243..f244e85 100644 --- a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/SetupFixture.cs +++ b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/SetupFixture.cs @@ -1,8 +1,6 @@ namespace NServiceBus.AcceptanceTests; -using System; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Envelope.CloudEvents.SQS.AcceptanceTests; using NUnit.Framework; diff --git a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/TestNameHelper.cs b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/TestNameHelper.cs index 62329a2..3a9825b 100644 --- a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/TestNameHelper.cs +++ b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/TestNameHelper.cs @@ -1,7 +1,5 @@ namespace NServiceBus.AcceptanceTests; -using System; -using System.Linq; using System.Text; static class TestNameHelper diff --git a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/When_receiving_http_binary.cs b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/When_receiving_http_binary.cs index b460c5d..74a1f91 100644 --- a/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/When_receiving_http_binary.cs +++ b/src/NServiceBus.Envelope.CloudEvents.SQS.AcceptanceTests/When_receiving_http_binary.cs @@ -1,10 +1,9 @@ namespace NServiceBus.AcceptanceTests; using System.Text.Json; -using System.Threading.Tasks; -using Amazon.SQS.Model; using AcceptanceTesting; using AcceptanceTesting.Customization; +using Amazon.SQS.Model; using Configuration.AdvancedExtensibility; using EndpointTemplates; using Envelope.CloudEvents; diff --git a/src/NServiceBus.Envelope.CloudEvents.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt b/src/NServiceBus.Envelope.CloudEvents.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt index 1ce48e3..caf4256 100644 --- a/src/NServiceBus.Envelope.CloudEvents.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt +++ b/src/NServiceBus.Envelope.CloudEvents.Tests/ApprovalFiles/APIApprovals.ApproveNServiceBus.approved.txt @@ -308,8 +308,11 @@ namespace NServiceBus public static class ErrorQueueSettings { public const string SettingsKey = "errorQueue"; - public static string ErrorQueueAddress(this NServiceBus.Settings.IReadOnlySettings settings) { } - public static bool TryGetExplicitlyConfiguredErrorQueueAddress(this NServiceBus.Settings.IReadOnlySettings settings, out string errorQueue) { } + extension(NServiceBus.Settings.IReadOnlySettings settings) + { + public string ErrorQueueAddress() { } + public bool TryGetExplicitlyConfiguredErrorQueueAddress([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? errorQueue) { } + } } public class FailedConfig { @@ -463,10 +466,7 @@ namespace NServiceBus } public interface IEnvelopeHandler { - [return: System.Runtime.CompilerServices.TupleElementNames(new string[] { - "headers", - "body"})] - System.ValueTuple, System.ReadOnlyMemory>? UnwrapEnvelope(string nativeMessageId, System.Collections.Generic.IDictionary incomingHeaders, NServiceBus.Extensibility.ContextBag extensions, System.ReadOnlyMemory incomingBody); + System.Collections.Generic.Dictionary? UnwrapEnvelope(string nativeMessageId, System.Collections.Generic.IDictionary incomingHeaders, System.ReadOnlySpan incomingBody, NServiceBus.Extensibility.ContextBag extensions, System.Buffers.IBufferWriter bodyWriter); } public interface IHandleMessages { } public interface IHandleMessages : NServiceBus.IHandleMessages @@ -671,19 +671,6 @@ namespace NServiceBus public void LimitMessageProcessingConcurrencyTo(int maxConcurrency) { } } } - public abstract class MessagePropertyAccessor - { - protected MessagePropertyAccessor() { } - public abstract System.Type MessageType { get; } - public abstract object? AccessFrom(object message); - } - public abstract class MessagePropertyAccessor : NServiceBus.MessagePropertyAccessor - { - protected MessagePropertyAccessor() { } - public override sealed System.Type MessageType { get; } - protected abstract object? AccessFrom(TMessage message); - public override sealed object? AccessFrom(object message) { } - } public static class MessageSessionExtensions { public static System.Threading.Tasks.Task Publish(this NServiceBus.IMessageSession session, object message, System.Threading.CancellationToken cancellationToken = default) { } @@ -718,6 +705,8 @@ namespace NServiceBus public delegate System.Threading.Tasks.Task OnSatelliteMessage(System.IServiceProvider serviceProvider, NServiceBus.Transport.MessageContext messageContext, System.Threading.CancellationToken cancellationToken = default); public static class OpenTelemetryConfigurationExtensions { + [System.Obsolete("OpenTelemetry is now enabled by default. This method is no longer required. Will " + + "be removed in version 11.0.0.", true)] public static void EnableOpenTelemetry(this NServiceBus.EndpointConfiguration endpointConfiguration) { } } public static class OpenTelemetryExtensions @@ -784,7 +773,7 @@ namespace NServiceBus } public class RateLimitSettings { - public RateLimitSettings(System.TimeSpan? timeToWaitBetweenThrottledAttempts = default, System.Func onRateLimitStarted = null, System.Func onRateLimitEnded = null) { } + public RateLimitSettings(System.TimeSpan? timeToWaitBetweenThrottledAttempts = default, System.Func? onRateLimitStarted = null, System.Func? onRateLimitEnded = null) { } public System.Func OnRateLimitEnded { get; } public System.Func OnRateLimitStarted { get; } public System.TimeSpan TimeToWaitBetweenThrottledAttempts { get; } @@ -1148,6 +1137,7 @@ namespace NServiceBus public static NServiceBus.Serialization.SerializationExtensions Namespace(this NServiceBus.Serialization.SerializationExtensions config, string namespaceToUse) { } public static NServiceBus.Serialization.SerializationExtensions SanitizeInput(this NServiceBus.Serialization.SerializationExtensions config) { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("XmlSerializer is not supported in trimming scenarios.")] public class XmlSerializer : NServiceBus.Serialization.SerializationDefinition { public XmlSerializer() { } @@ -1445,13 +1435,9 @@ namespace NServiceBus.Features { public static void EnableFeature(this NServiceBus.Settings.SettingsHolder settings) where TFeature : NServiceBus.Features.Feature, new () { } - [System.Obsolete("It is no longer possible to enable features by default on the settings. Features " + - "can enable other features by calling EnableByDefault in the constructor. Will" + - " be removed in version 11.0.0.", true)] + [System.Obsolete(@"It is no longer possible to enable features by default on the settings. Features can enable other features by calling Enable in the constructor. Enabling a feature outside the context of another feature can be done by calling EnableFeature on the endpoint configuration or settings. Will be removed in version 11.0.0.", true)] public static NServiceBus.Settings.SettingsHolder EnableFeatureByDefault(this NServiceBus.Settings.SettingsHolder settings, System.Type featureType) { } - [System.Obsolete("It is no longer possible to enable features by default on the settings. Features " + - "can enable other features by calling EnableByDefault in the constructor. Will" + - " be removed in version 11.0.0.", true)] + [System.Obsolete(@"It is no longer possible to enable features by default on the settings. Features can enable other features by calling Enable in the constructor. Enabling a feature outside the context of another feature can be done by calling EnableFeature on the endpoint configuration or settings. Will be removed in version 11.0.0.", true)] public static NServiceBus.Settings.SettingsHolder EnableFeatureByDefault(this NServiceBus.Settings.SettingsHolder settings) where T : NServiceBus.Features.Feature { } [System.Obsolete("Use \'IsFeatureActive(this IReadOnlySettings settings)\' instead. Will be remove" + @@ -2109,6 +2095,12 @@ namespace NServiceBus.Sagas public string SagaId { get; } public void AttachNewEntity(NServiceBus.IContainSagaData sagaEntity) { } } + public abstract class CorrelationPropertyAccessor + { + protected CorrelationPropertyAccessor() { } + public abstract object? AccessFrom(NServiceBus.IContainSagaData sagaData); + public abstract void WriteTo(NServiceBus.IContainSagaData sagaData, object value); + } public interface IFinder { } [System.Obsolete(@"Saga not found handlers are no longer automatically registered during assembly scanning. Handlers are no longer global and should be registered for each saga using mapper.ConfigureNotFoundHandler(). Use 'ISagaNotFoundHandler' instead. Will be removed in version 11.0.0.", true)] public interface IHandleSagaNotFound @@ -2134,6 +2126,19 @@ namespace NServiceBus.Sagas System.Threading.Tasks.Task Save(NServiceBus.IContainSagaData sagaData, NServiceBus.Sagas.SagaCorrelationProperty correlationProperty, NServiceBus.Persistence.ISynchronizedStorageSession session, NServiceBus.Extensibility.ContextBag context, System.Threading.CancellationToken cancellationToken = default); System.Threading.Tasks.Task Update(NServiceBus.IContainSagaData sagaData, NServiceBus.Persistence.ISynchronizedStorageSession session, NServiceBus.Extensibility.ContextBag context, System.Threading.CancellationToken cancellationToken = default); } + public abstract class MessagePropertyAccessor + { + protected MessagePropertyAccessor() { } + public abstract System.Type MessageType { get; } + public abstract object? AccessFrom(object message); + } + public abstract class MessagePropertyAccessor : NServiceBus.Sagas.MessagePropertyAccessor + { + protected MessagePropertyAccessor() { } + public override sealed System.Type MessageType { get; } + protected abstract object? AccessFrom(TMessage message); + public override sealed object? AccessFrom(object message) { } + } public class SagaCorrelationProperty { public SagaCorrelationProperty(string name, object value) { } @@ -2186,13 +2191,14 @@ namespace NServiceBus.Sagas public static NServiceBus.Sagas.SagaMetadata Create(System.Type sagaType, System.Collections.Generic.IEnumerable availableTypes, NServiceBus.Conventions conventions) { } public static NServiceBus.Sagas.SagaMetadata Create() where TSaga : NServiceBus.Saga { } - public static NServiceBus.Sagas.SagaMetadata Create(System.Collections.Generic.IReadOnlyCollection associatedMessages, System.Collections.Generic.IReadOnlyCollection? propertyAccessors = null) + public static NServiceBus.Sagas.SagaMetadata Create(System.Collections.Generic.IReadOnlyCollection associatedMessages, NServiceBus.Sagas.CorrelationPropertyAccessor? correlationPropertyAccessor = null, System.Collections.Generic.IReadOnlyCollection? propertyAccessors = null) where TSaga : NServiceBus.Saga where TSagaData : class, NServiceBus.IContainSagaData, new () { } public static System.Collections.Generic.IEnumerable CreateMany(System.Collections.Generic.IEnumerable sagaTypes) { } public class CorrelationPropertyMetadata { - public CorrelationPropertyMetadata(string name, System.Type type) { } + public CorrelationPropertyMetadata(string name, System.Type type, NServiceBus.Sagas.CorrelationPropertyAccessor propertyAccessor) { } + public NServiceBus.Sagas.CorrelationPropertyAccessor Accessor { get; } public string Name { get; } public System.Type Type { get; } } @@ -2320,9 +2326,16 @@ namespace NServiceBus.Transport public NServiceBus.Settings.IReadOnlySettings? CoreSettings { get; } public System.Action CriticalErrorAction { get; } public string HostDisplayName { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "CoreSettings")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, "CoreSettings")] + public bool IsRawMode { get; } public string Name { get; } + public System.IServiceProvider? ServiceProvider { get; set; } public bool SetupInfrastructure { get; } public NServiceBus.StartupDiagnosticEntries StartupDiagnostic { get; } + [System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ServiceProvider")] + [get: System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, "ServiceProvider")] + public bool SupportsDependencyInjection { get; } } public interface IMessageDispatcher { @@ -2432,6 +2445,8 @@ namespace NServiceBus.Transport public bool SupportsPublishSubscribe { get; } public bool SupportsTTBR { get; } public virtual NServiceBus.TransportTransactionMode TransportTransactionMode { get; set; } + public void ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } + protected virtual void ConfigureServicesCore(Microsoft.Extensions.DependencyInjection.IServiceCollection services) { } protected void EnableEndpointFeature() where T : NServiceBus.Features.Feature, new () { } public abstract System.Collections.Generic.IReadOnlyCollection GetSupportedTransactionModes(); @@ -2515,10 +2530,16 @@ namespace NServiceBus.Unicast.Messages } public class MessageMetadataRegistry { + public MessageMetadataRegistry() { } + [System.Obsolete("Use \'MessageMetadataRegistry.Initialize\' instead. Will be removed in version 11.0" + + ".0.", true)] public MessageMetadataRegistry(System.Func isMessageType, bool allowDynamicTypeLoading) { } public NServiceBus.Unicast.Messages.MessageMetadata[] GetAllMessages() { } public NServiceBus.Unicast.Messages.MessageMetadata GetMessageMetadata(System.Type messageType) { } public NServiceBus.Unicast.Messages.MessageMetadata GetMessageMetadata(string messageTypeIdentifier) { } + public void Initialize(System.Func isMessageType, bool allowDynamicTypeLoading) { } + public void RegisterMessageTypeWithHierarchy(System.Type messageType, System.Collections.Generic.IEnumerable parentMessages) { } + public void RegisterMessageTypes(System.Collections.Generic.IEnumerable messageTypes) { } } } namespace NServiceBus.Unicast.Queuing diff --git a/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventAmqpBinaryEnvelopeHandlerTests.cs b/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventAmqpBinaryEnvelopeHandlerTests.cs index 79a3f7f..425c3e8 100644 --- a/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventAmqpBinaryEnvelopeHandlerTests.cs +++ b/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventAmqpBinaryEnvelopeHandlerTests.cs @@ -1,13 +1,11 @@ namespace NServiceBus.Envelope.CloudEvents.Tests; -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Diagnostics.Metrics.Testing; +using System.Buffers; using System.Text; using System.Text.Json; using Extensibility; using Fakes; -using NServiceBus; +using Microsoft.Extensions.Diagnostics.Metrics.Testing; using NUnit.Framework; [TestFixture] @@ -224,8 +222,10 @@ public void Should_not_record_metric_when_property_is_missing(string property) (Dictionary headers, ReadOnlyMemory body)? RunEnvelopHandlerTest() { + var bodyWriter = new ArrayBufferWriter(); var upperCaseHeaders = NativeHeaders.ToDictionary(k => k.Key.ToUpper(), k => k.Value); - return EnvelopeHandler.UnwrapEnvelope(NativeMessageId, upperCaseHeaders!, new ContextBag(), Body); + var headers = EnvelopeHandler.UnwrapEnvelope(NativeMessageId, upperCaseHeaders!, Body.Span, new ContextBag(), bodyWriter); + return headers == null ? null : (headers, bodyWriter.WrittenMemory); } void AssertTypicalFields((Dictionary Headers, ReadOnlyMemory Body) actual, bool shouldHaveTime = true) diff --git a/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventHttpBinaryEnvelopeHandlerTests.cs b/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventHttpBinaryEnvelopeHandlerTests.cs index a18f602..033a506 100644 --- a/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventHttpBinaryEnvelopeHandlerTests.cs +++ b/src/NServiceBus.Envelope.CloudEvents.Tests/CloudEventHttpBinaryEnvelopeHandlerTests.cs @@ -1,13 +1,11 @@ namespace NServiceBus.Envelope.CloudEvents.Tests; -using System; -using System.Collections.Generic; +using System.Buffers; using System.Text; using System.Text.Json; using Extensibility; using Fakes; using Microsoft.Extensions.Diagnostics.Metrics.Testing; -using NServiceBus; using NUnit.Framework; [TestFixture] @@ -224,8 +222,10 @@ public void Should_not_record_metric_when_property_is_missing(string property) (Dictionary headers, ReadOnlyMemory body)? RunEnvelopHandlerTest() { + var bodyWriter = new ArrayBufferWriter(); var upperCaseHeaders = NativeHeaders.ToDictionary(k => k.Key.ToUpper(), k => k.Value); - return EnvelopeHandler.UnwrapEnvelope(NativeMessageId, upperCaseHeaders!, new ContextBag(), Body); + var headers = EnvelopeHandler.UnwrapEnvelope(NativeMessageId, upperCaseHeaders!, Body.Span, new ContextBag(), bodyWriter); + return headers == null ? null : (headers, bodyWriter.WrittenMemory); } void AssertTypicalFields((Dictionary Headers, ReadOnlyMemory Body) actual, bool shouldHaveTime = true) diff --git a/src/NServiceBus.Envelope.CloudEvents.Tests/Fakes/TestMeterFactory.cs b/src/NServiceBus.Envelope.CloudEvents.Tests/Fakes/TestMeterFactory.cs index 949bf39..02cc0d1 100644 --- a/src/NServiceBus.Envelope.CloudEvents.Tests/Fakes/TestMeterFactory.cs +++ b/src/NServiceBus.Envelope.CloudEvents.Tests/Fakes/TestMeterFactory.cs @@ -1,6 +1,5 @@ namespace NServiceBus.Envelope.CloudEvents.Tests.Fakes; -using System.Collections.Generic; using System.Diagnostics.Metrics; class TestMeterFactory : IMeterFactory diff --git a/src/NServiceBus.Envelope.CloudEvents.Tests/PermissiveCloudEventJsonStructuredEnvelopeHandlerTests.cs b/src/NServiceBus.Envelope.CloudEvents.Tests/PermissiveCloudEventJsonStructuredEnvelopeHandlerTests.cs index bb97636..f59739b 100644 --- a/src/NServiceBus.Envelope.CloudEvents.Tests/PermissiveCloudEventJsonStructuredEnvelopeHandlerTests.cs +++ b/src/NServiceBus.Envelope.CloudEvents.Tests/PermissiveCloudEventJsonStructuredEnvelopeHandlerTests.cs @@ -1,13 +1,11 @@ namespace NServiceBus.Envelope.CloudEvents.Tests; -using System; -using System.Collections.Generic; +using System.Buffers; using System.Text; using System.Text.Json; using Extensibility; using Fakes; using Microsoft.Extensions.Diagnostics.Metrics.Testing; -using NServiceBus; using NUnit.Framework; [TestFixture] @@ -285,7 +283,8 @@ public void Should_record_metric_when_type_is_missing() [Test] public void Should_return_null_for_invalid_body() { - (Dictionary Headers, ReadOnlyMemory Body)? actual = EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, new ContextBag(), new ReadOnlyMemory()); + var bodyWriter = new ArrayBufferWriter(); + Dictionary? actual = EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, ReadOnlySpan.Empty, new ContextBag(), bodyWriter); Assert.That(actual, Is.Null); } @@ -293,7 +292,8 @@ public void Should_return_null_for_invalid_body() [Test] public void Should_emit_metric_for_invalid_body() { - EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, new ContextBag(), new ReadOnlyMemory()); + var bodyWriter = new ArrayBufferWriter(); + EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, ReadOnlySpan.Empty, new ContextBag(), bodyWriter); var attemptCounterSnapshot = AttemptCounter.GetMeasurementSnapshot(); var invalidMessageCounterSnapshot = InvalidMessageCounter.GetMeasurementSnapshot(); @@ -448,7 +448,9 @@ public void Should_record_metric_when_data_base64_property_is_present() var payloadWithUpperCaseKeys = Payload.ToDictionary(p => p.Key.ToUpper(), p => p.Value); string serializedBody = JsonSerializer.Serialize(payloadWithUpperCaseKeys); var fullBody = new ReadOnlyMemory(Encoding.UTF8.GetBytes(serializedBody)); - return EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, new ContextBag(), fullBody); + var bodyWriter = new ArrayBufferWriter(); + var headers = EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, fullBody.Span, new ContextBag(), bodyWriter); + return headers == null ? null : (headers, new ReadOnlyMemory(bodyWriter.WrittenSpan.ToArray())); } void AssertTypicalFields((Dictionary Headers, ReadOnlyMemory body) actual, bool shouldHaveTime = true) diff --git a/src/NServiceBus.Envelope.CloudEvents.Tests/StrictCloudEventJsonStructuredEnvelopeHandlerTests.cs b/src/NServiceBus.Envelope.CloudEvents.Tests/StrictCloudEventJsonStructuredEnvelopeHandlerTests.cs index c081ef5..2d414b8 100644 --- a/src/NServiceBus.Envelope.CloudEvents.Tests/StrictCloudEventJsonStructuredEnvelopeHandlerTests.cs +++ b/src/NServiceBus.Envelope.CloudEvents.Tests/StrictCloudEventJsonStructuredEnvelopeHandlerTests.cs @@ -1,13 +1,11 @@ namespace NServiceBus.Envelope.CloudEvents.Tests; -using System; -using System.Collections.Generic; +using System.Buffers; using System.Text; using System.Text.Json; using Extensibility; using Fakes; using Microsoft.Extensions.Diagnostics.Metrics.Testing; -using NServiceBus; using NUnit.Framework; [TestFixture] @@ -421,9 +419,10 @@ public void Should_emit_metric_for_wrong_content_type() [Test] public void Should_throw_for_invalid_body() { - Assert.Throws(() => + Assert.Throws(Is.InstanceOf(), () => { - EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, new ContextBag(), new ReadOnlyMemory()); + var bodyWriter = new ArrayBufferWriter(); + EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, ReadOnlySpan.Empty, new ContextBag(), bodyWriter); }); } @@ -432,8 +431,8 @@ public void Should_emit_metric_for_invalid_body() { try { - EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, new ContextBag(), - new ReadOnlyMemory()); + var bodyWriter = new ArrayBufferWriter(); + EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, ReadOnlySpan.Empty, new ContextBag(), bodyWriter); } catch (Exception) { @@ -464,7 +463,9 @@ public void Should_emit_metric_for_invalid_body() var payloadWithUpperCaseKeys = Payload.ToDictionary(p => p.Key.ToUpper(), p => p.Value); string serializedBody = JsonSerializer.Serialize(payloadWithUpperCaseKeys); var fullBody = new ReadOnlyMemory(Encoding.UTF8.GetBytes(serializedBody)); - return EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, new ContextBag(), fullBody); + var bodyWriter = new ArrayBufferWriter(); + var headers = EnvelopeHandler.UnwrapEnvelope(NativeMessageId, NativeHeaders, fullBody.Span, new ContextBag(), bodyWriter); + return headers == null ? null : (headers, new ReadOnlyMemory(bodyWriter.WrittenSpan.ToArray())); } void AssertTypicalFields((Dictionary Headers, ReadOnlyMemory body) actual, bool shouldHaveTime = true) diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeHandler.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeHandler.cs index 026c31e..69aec70 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeHandler.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeHandler.cs @@ -1,8 +1,6 @@ namespace NServiceBus.Envelope.CloudEvents; -using System; -using System.Collections.Generic; -using System.Linq; +using System.Buffers; using Extensibility; using Logging; @@ -24,9 +22,8 @@ class CloudEventAmqpBinaryEnvelopeHandler(CloudEventsMetrics metrics, CloudEvent { static readonly ILog Log = LogManager.GetLogger(); - public (Dictionary headers, ReadOnlyMemory body)? UnwrapEnvelope( - string nativeMessageId, IDictionary incomingHeaders, - ContextBag extensions, ReadOnlyMemory incomingBody) + public Dictionary? UnwrapEnvelope(string nativeMessageId, IDictionary incomingHeaders, + ReadOnlySpan incomingBody, ContextBag extensions, IBufferWriter bodyWriter) { metrics.RecordAttemptingToUnwrap(CloudEventsMetrics.CloudEventTypes.AMQP_BINARY); var caseInsensitiveHeaders = ToCaseInsensitiveDictionary(incomingHeaders); @@ -34,8 +31,9 @@ class CloudEventAmqpBinaryEnvelopeHandler(CloudEventsMetrics metrics, CloudEvent { return null; } - var headers = ExtractHeaders(nativeMessageId, caseInsensitiveHeaders); - return (headers, incomingBody); + + bodyWriter.Write(incomingBody); + return ExtractHeaders(nativeMessageId, caseInsensitiveHeaders); } static Dictionary ToCaseInsensitiveDictionary(IDictionary incomingHeaders) => diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeUnwrapper.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeUnwrapper.cs index e94fa8c..a12e0dd 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeUnwrapper.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventAmqpBinaryEnvelopeUnwrapper.cs @@ -5,7 +5,7 @@ namespace NServiceBus.Envelope.CloudEvents; /// /// Unwrapper for AMQP Binary cloud events envelopes. /// -public class CloudEventAmqpBinaryEnvelopeUnwrapper() : EnvelopeUnwrapper +public class CloudEventAmqpBinaryEnvelopeUnwrapper : EnvelopeUnwrapper { internal override void RegisterUnwrapper(FeatureConfigurationContext context, Action unwrapperDiagnosticWriter) { diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeHandler.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeHandler.cs index d5428b1..3935b4e 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeHandler.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeHandler.cs @@ -1,8 +1,6 @@ namespace NServiceBus.Envelope.CloudEvents; -using System; -using System.Collections.Generic; -using System.Linq; +using System.Buffers; using Extensibility; using Logging; @@ -24,9 +22,8 @@ class CloudEventHttpBinaryEnvelopeHandler(CloudEventsMetrics metrics, CloudEvent { static readonly ILog Log = LogManager.GetLogger(); - public (Dictionary headers, ReadOnlyMemory body)? UnwrapEnvelope( - string nativeMessageId, IDictionary incomingHeaders, - ContextBag extensions, ReadOnlyMemory incomingBody) + public Dictionary? UnwrapEnvelope(string nativeMessageId, IDictionary incomingHeaders, + ReadOnlySpan incomingBody, ContextBag extensions, IBufferWriter bodyWriter) { metrics.RecordAttemptingToUnwrap(CloudEventsMetrics.CloudEventTypes.HTTP_BINARY); var caseInsensitiveHeaders = ToCaseInsensitiveDictionary(incomingHeaders); @@ -34,8 +31,9 @@ class CloudEventHttpBinaryEnvelopeHandler(CloudEventsMetrics metrics, CloudEvent { return null; } - var headers = ExtractHeaders(nativeMessageId, caseInsensitiveHeaders); - return (headers, incomingBody); + + bodyWriter.Write(incomingBody); + return ExtractHeaders(nativeMessageId, caseInsensitiveHeaders); } static Dictionary ToCaseInsensitiveDictionary(IDictionary incomingHeaders) => diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeUnwrapper.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeUnwrapper.cs index 40f7adc..b60d0e4 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeUnwrapper.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventHttpBinaryEnvelopeUnwrapper.cs @@ -5,7 +5,7 @@ namespace NServiceBus.Envelope.CloudEvents; /// /// Unwrapper for HTTP Binary cloud events envelopes. /// -public class CloudEventHttpBinaryEnvelopeUnwrapper() : EnvelopeUnwrapper +public class CloudEventHttpBinaryEnvelopeUnwrapper : EnvelopeUnwrapper { internal override void RegisterUnwrapper(FeatureConfigurationContext context, Action unwrapperDiagnosticWriter) { diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventJsonStructuredEnvelopeHandler.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventJsonStructuredEnvelopeHandler.cs index 0e8fbab..4008949 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventJsonStructuredEnvelopeHandler.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventJsonStructuredEnvelopeHandler.cs @@ -1,5 +1,6 @@ namespace NServiceBus.Envelope.CloudEvents; +using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; @@ -25,43 +26,173 @@ static class CloudEventJsonStructuredConstants internal static readonly HashSet HeadersToIgnore = [DataProperty, DataBase64Property]; } +class CloudEventPropertyValue +{ + public JsonValueKind ValueKind { get; init; } + public string? StringValue { get; init; } + public ReadOnlyMemory RawJsonBytes { get; init; } + + public string GetString() => StringValue ?? throw new InvalidOperationException("Value is not a string"); + public string GetRawText() => Encoding.UTF8.GetString(RawJsonBytes.Span); +} + +class CloudEventProperties +{ + readonly Dictionary properties = new(StringComparer.OrdinalIgnoreCase); + + public void Add(string name, CloudEventPropertyValue value) => properties[name] = value; + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out CloudEventPropertyValue value) => + properties.TryGetValue(key, out value); + + public bool ContainsKey(string key) => properties.ContainsKey(key); + + public CloudEventPropertyValue this[string key] => properties[key]; + + public IEnumerable> Properties => properties; +} + class CloudEventJsonStructuredEnvelopeHandler(CloudEventsMetrics metrics, CloudEventsConfiguration config) : IEnvelopeHandler { static readonly ILog Log = LogManager.GetLogger(); - static readonly JsonSerializerOptions Options = new() { PropertyNameCaseInsensitive = true }; - public (Dictionary headers, ReadOnlyMemory body)? UnwrapEnvelope(string nativeMessageId, IDictionary incomingHeaders, ContextBag extensions, ReadOnlyMemory incomingBody) + public Dictionary? UnwrapEnvelope(string nativeMessageId, IDictionary incomingHeaders, ReadOnlySpan incomingBody, ContextBag extensions, IBufferWriter bodyWriter) { var isStrict = config.EnvelopeUnwrappers.Find().EnvelopeHandlingMode == JsonStructureEnvelopeHandlingMode.Strict; - Dictionary? receivedCloudEvent = isStrict + CloudEventProperties? receivedCloudEvent = isStrict ? StrictHandler.DeserializeOrThrow(nativeMessageId, incomingHeaders, incomingBody, metrics) : PermissiveHandler.DeserializeOrThrow(nativeMessageId, incomingBody, metrics); - return receivedCloudEvent == null - ? null - : (ExtractHeaders(nativeMessageId, incomingHeaders, receivedCloudEvent), ExtractBody(nativeMessageId, receivedCloudEvent)); + if (receivedCloudEvent == null) + { + return null; + } + + ExtractBody(nativeMessageId, receivedCloudEvent, bodyWriter); + return ExtractHeaders(nativeMessageId, incomingHeaders, receivedCloudEvent); + } + + static CloudEventProperties? ParseCloudEventJson(ReadOnlySpan json) + { + var reader = new Utf8JsonReader(json); + + if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) + { + return null; + } + + var properties = new CloudEventProperties(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + continue; + } + + var propertyName = reader.GetString(); + if (propertyName == null) + { + continue; + } + + // Normalize to lowercase for case-insensitive matching + propertyName = propertyName.ToLowerInvariant(); + + reader.Read(); + + var valueKind = reader.TokenType switch + { + JsonTokenType.String => JsonValueKind.String, + JsonTokenType.Number => JsonValueKind.Number, + JsonTokenType.True or JsonTokenType.False => JsonValueKind.True, + JsonTokenType.Null => JsonValueKind.Null, + JsonTokenType.StartObject => JsonValueKind.Object, + JsonTokenType.StartArray => JsonValueKind.Array, + JsonTokenType.None => JsonValueKind.Undefined, + JsonTokenType.Comment => JsonValueKind.Undefined, + JsonTokenType.EndObject => JsonValueKind.Undefined, + JsonTokenType.EndArray => JsonValueKind.Undefined, + JsonTokenType.PropertyName => JsonValueKind.Undefined, + _ => JsonValueKind.Undefined + }; + + CloudEventPropertyValue value = valueKind switch + { + JsonValueKind.String => new CloudEventPropertyValue + { + ValueKind = JsonValueKind.String, + StringValue = reader.GetString() + }, + JsonValueKind.Object or JsonValueKind.Array => CaptureComplexValue(ref reader, json, valueKind), + JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False or JsonValueKind.Null or JsonValueKind.Undefined => CapturePrimitiveValue(ref reader, valueKind), + _ => CapturePrimitiveValue(ref reader, valueKind) + }; + + properties.Add(propertyName, value); + } + + return properties; + } + + static CloudEventPropertyValue CaptureComplexValue(ref Utf8JsonReader reader, ReadOnlySpan json, JsonValueKind valueKind) + { + // Capture the raw JSON for complex types + var startPosition = reader.TokenStartIndex; + var depth = reader.CurrentDepth; + + // Skip the entire object/array + while (reader.Read() && reader.CurrentDepth > depth) + { + } + + var length = (int)(reader.TokenStartIndex + reader.ValueSpan.Length - startPosition); + var rawJson = json.Slice((int)startPosition, length); + + return new CloudEventPropertyValue + { + ValueKind = valueKind, + RawJsonBytes = rawJson.ToArray() + }; + } + + static CloudEventPropertyValue CapturePrimitiveValue(ref Utf8JsonReader reader, JsonValueKind valueKind) + { + // For primitive types, capture as both string and raw JSON + var rawJson = reader.ValueSpan; + return new CloudEventPropertyValue + { + ValueKind = valueKind, + StringValue = valueKind == JsonValueKind.Number ? reader.GetDouble().ToString() : reader.GetString(), + RawJsonBytes = rawJson.ToArray() + }; } Dictionary ExtractHeaders(string nativeMessageId, IDictionary existingHeaders, - Dictionary receivedCloudEvent) + CloudEventProperties receivedCloudEvent) { var headersCopy = existingHeaders.ToDictionary(k => k.Key, k => k.Value); - foreach (var kvp in receivedCloudEvent) + foreach (var kvp in receivedCloudEvent.Properties) { if ( CloudEventJsonStructuredConstants.HeadersToIgnore.Contains(kvp.Key) - || kvp.Value.Value.ValueKind == JsonValueKind.Undefined - || kvp.Value.Value.ValueKind == JsonValueKind.Null + || kvp.Value.ValueKind == JsonValueKind.Undefined + || kvp.Value.ValueKind == JsonValueKind.Null ) { continue; } - headersCopy[kvp.Key] = kvp.Value.Value.ValueKind == JsonValueKind.String - ? kvp.Value.Value.GetString()! - : kvp.Value.Value.GetRawText(); + headersCopy[kvp.Key] = kvp.Value.ValueKind == JsonValueKind.String + ? kvp.Value.GetString() + : kvp.Value.GetRawText(); if (Log.IsDebugEnabled) { @@ -130,15 +261,15 @@ Dictionary ExtractHeaders(string nativeMessageId, IDictionary receivedCloudEvent) + string ExtractType(CloudEventProperties receivedCloudEvent) { - var cloudEventType = receivedCloudEvent[CloudEventJsonStructuredConstants.TypeProperty].Value.GetString()!; + var cloudEventType = receivedCloudEvent[CloudEventJsonStructuredConstants.TypeProperty].GetString(); return config.TypeMappings.TryGetValue(cloudEventType, out var typeMapping) ? string.Join(',', typeMapping) : cloudEventType; } - static ReadOnlyMemory ExtractBody(string nativeMessageId, Dictionary receivedCloudEvent) + static void ExtractBody(string nativeMessageId, CloudEventProperties receivedCloudEvent, IBufferWriter bodyWriter) { if (TryGetHeader(receivedCloudEvent, CloudEventJsonStructuredConstants.DataBase64Property, out var base64Body)) { @@ -146,7 +277,9 @@ static ReadOnlyMemory ExtractBody(string nativeMessageId, Dictionary(Convert.FromBase64String(base64Body)); + var bytes = Convert.FromBase64String(base64Body); + bodyWriter.Write(bytes); + return; } if (receivedCloudEvent.TryGetValue(CloudEventJsonStructuredConstants.DataProperty, out var data)) @@ -157,24 +290,25 @@ static ReadOnlyMemory ExtractBody(string nativeMessageId, Dictionary(Encoding.UTF8.GetBytes( - data.Value.GetString()!)); + var bytes = Encoding.UTF8.GetBytes(data.GetString()); + bodyWriter.Write(bytes); + return; } if (Log.IsDebugEnabled) { Log.DebugFormat("Passing inner body as JSON for message {0}", nativeMessageId); } - if (data.Value.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null) + if (data.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null) { - return new ReadOnlyMemory(Encoding.UTF8.GetBytes( - data.Value.GetRawText())); + bodyWriter.Write(data.RawJsonBytes.Span); + return; } } @@ -182,16 +316,15 @@ static ReadOnlyMemory ExtractBody(string nativeMessageId, Dictionary(); } - static bool TryGetHeader(Dictionary receivedCloudEvent, string header, [MaybeNullWhen(false)] out string result) + static bool TryGetHeader(CloudEventProperties receivedCloudEvent, string header, [MaybeNullWhen(false)] out string result) { if (receivedCloudEvent.TryGetValue(header, out var value) - && value.Value.ValueKind != JsonValueKind.Undefined - && value.Value.ValueKind != JsonValueKind.Null) + && value.ValueKind != JsonValueKind.Undefined + && value.ValueKind != JsonValueKind.Null) { - result = value.Value.GetString()!; + result = value.GetString(); return true; } @@ -199,11 +332,6 @@ static bool TryGetHeader(Dictionary receivedCloudEvent, st return false; } - static Dictionary ToCaseInsensitiveDictionary(JsonDocument receivedCloudEvent) => - receivedCloudEvent.RootElement.EnumerateObject() - .ToDictionary(p => p.Name.ToLowerInvariant(), p => p, - StringComparer.OrdinalIgnoreCase); - static class StrictHandler { static readonly string[] RequiredProperties = [ @@ -212,9 +340,9 @@ static class StrictHandler CloudEventJsonStructuredConstants.TypeProperty ]; - internal static Dictionary? DeserializeOrThrow(string nativeMessageId, + internal static CloudEventProperties? DeserializeOrThrow(string nativeMessageId, IDictionary incomingHeaders, - ReadOnlyMemory body, CloudEventsMetrics metrics) + ReadOnlySpan body, CloudEventsMetrics metrics) { if (!HasCorrectContentTypeHeader(incomingHeaders)) { @@ -232,10 +360,10 @@ static class StrictHandler } metrics.RecordAttemptingToUnwrap(CloudEventsMetrics.CloudEventTypes.JSON_STRUCTURED_STRICT); - JsonDocument? receivedCloudEvent; + CloudEventProperties? receivedCloudEvent; try { - receivedCloudEvent = JsonSerializer.Deserialize(body.Span, Options); + receivedCloudEvent = ParseCloudEventJson(body); } catch (Exception e) { @@ -262,9 +390,8 @@ static class StrictHandler Log.DebugFormat("Message {0} has been deserialized correctly", nativeMessageId); } - Dictionary caseInsensitiveProperties = ToCaseInsensitiveDictionary(receivedCloudEvent); - ThrowIfInvalidCloudEventAndRecordMetrics(nativeMessageId, caseInsensitiveProperties, metrics); - return caseInsensitiveProperties; + ThrowIfInvalidCloudEventAndRecordMetrics(nativeMessageId, receivedCloudEvent, metrics); + return receivedCloudEvent; } static bool HasCorrectContentTypeHeader(IDictionary incomingHeaders) => @@ -272,11 +399,11 @@ static bool HasCorrectContentTypeHeader(IDictionary incomingHead (value == CloudEventJsonStructuredConstants.SupportedContentType || value.Contains(CloudEventJsonStructuredConstants.SupportedContentType)); static void ThrowIfInvalidCloudEventAndRecordMetrics(string nativeMessageId, - Dictionary receivedCloudEvent, CloudEventsMetrics metrics) + CloudEventProperties receivedCloudEvent, CloudEventsMetrics metrics) { foreach (var property in RequiredProperties) { - if (!receivedCloudEvent.TryGetValue(property, out _)) + if (!receivedCloudEvent.ContainsKey(property)) { if (Log.IsWarnEnabled) { @@ -287,8 +414,8 @@ static void ThrowIfInvalidCloudEventAndRecordMetrics(string nativeMessageId, } } - if (!receivedCloudEvent.TryGetValue(CloudEventJsonStructuredConstants.DataBase64Property, out _) && - !receivedCloudEvent.TryGetValue(CloudEventJsonStructuredConstants.DataProperty, out _)) + if (!receivedCloudEvent.ContainsKey(CloudEventJsonStructuredConstants.DataBase64Property) && + !receivedCloudEvent.ContainsKey(CloudEventJsonStructuredConstants.DataProperty)) { if (Log.IsWarnEnabled) { @@ -306,7 +433,7 @@ static void ThrowIfInvalidCloudEventAndRecordMetrics(string nativeMessageId, if (receivedCloudEvent.TryGetValue(CloudEventJsonStructuredConstants.VersionProperty, out var version)) { - var versionValue = version.Value.GetString(); + var versionValue = version.GetString(); if (versionValue != CloudEventJsonStructuredConstants.SupportedVersion) { @@ -338,15 +465,15 @@ static void ThrowIfInvalidCloudEventAndRecordMetrics(string nativeMessageId, static class PermissiveHandler { - internal static Dictionary? DeserializeOrThrow(string nativeMessageId, - ReadOnlyMemory body, CloudEventsMetrics metrics) + internal static CloudEventProperties? DeserializeOrThrow(string nativeMessageId, + ReadOnlySpan body, CloudEventsMetrics metrics) { metrics.RecordAttemptingToUnwrap(CloudEventsMetrics.CloudEventTypes.JSON_STRUCTURED_PERMISSIVE); - JsonDocument? receivedCloudEvent; + CloudEventProperties? receivedCloudEvent; try { - receivedCloudEvent = JsonSerializer.Deserialize(body.Span, Options); + receivedCloudEvent = ParseCloudEventJson(body); } catch (Exception e) { @@ -367,23 +494,21 @@ static class PermissiveHandler return null; } - Dictionary caseInsensitiveProperties = ToCaseInsensitiveDictionary(receivedCloudEvent); - - if (!caseInsensitiveProperties.TryGetValue(CloudEventJsonStructuredConstants.TypeProperty, out _)) + if (!receivedCloudEvent.ContainsKey(CloudEventJsonStructuredConstants.TypeProperty)) { if (Log.IsDebugEnabled) { - Log.DebugFormat("No data field for the message {0}", nativeMessageId); + Log.DebugFormat("No type field for the message {0}", nativeMessageId); } return null; } - RecordMetrics(nativeMessageId, caseInsensitiveProperties, metrics); + RecordMetrics(nativeMessageId, receivedCloudEvent, metrics); - return caseInsensitiveProperties; + return receivedCloudEvent; } - static void RecordMetrics(string nativeMessageId, Dictionary receivedCloudEvent, + static void RecordMetrics(string nativeMessageId, CloudEventProperties receivedCloudEvent, CloudEventsMetrics metrics) { if (Log.IsDebugEnabled) @@ -394,7 +519,7 @@ static void RecordMetrics(string nativeMessageId, Dictionary /// Unwrapper for JSON Structured cloud events envelopes. /// -public class CloudEventJsonStructuredEnvelopeUnwrapper() : EnvelopeUnwrapper +public class CloudEventJsonStructuredEnvelopeUnwrapper : EnvelopeUnwrapper { internal override void RegisterUnwrapper(FeatureConfigurationContext context, Action unwrapperDiagnosticWriter) { @@ -18,7 +18,8 @@ internal override void RegisterUnwrapper(FeatureConfigurationContext context, Ac } /// - /// Determines the envelope handling behavior. In strict mode the unwrapper expects the correct Content-Type header. In permissive mode + /// Determines the envelope handling behavior. In strict mode the unwrapper expects the correct + /// Content-Type header. In permissive mode it always tries to parse the incoming envelope. /// public JsonStructureEnvelopeHandlingMode EnvelopeHandlingMode { get; set; } = JsonStructureEnvelopeHandlingMode.Strict; } \ No newline at end of file diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventsConfiguration.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventsConfiguration.cs index 1259c32..3d9b71d 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventsConfiguration.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventsConfiguration.cs @@ -8,7 +8,7 @@ public class CloudEventsConfiguration /// /// Specify type mappings. Allows the user to map string values from the `type` property in incoming cloud events to a type string that NServiceBus expects. /// - public Dictionary TypeMappings { get; } = []; + public Dictionary TypeMappings { get; } = new([], StringComparer.OrdinalIgnoreCase); /// /// The envelope unwrappers to use to handle incoming message envelops. diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventsFeature.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventsFeature.cs index 9585d34..7f3d25a 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventsFeature.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventsFeature.cs @@ -3,7 +3,6 @@ namespace NServiceBus.Envelope.CloudEvents; using System.Diagnostics.Metrics; using Features; using Microsoft.Extensions.DependencyInjection; -using NServiceBus; class CloudEventsFeature : Feature { diff --git a/src/NServiceBus.Envelope.CloudEvents/CloudEventsMetrics.cs b/src/NServiceBus.Envelope.CloudEvents/CloudEventsMetrics.cs index 61fd62a..8c62772 100644 --- a/src/NServiceBus.Envelope.CloudEvents/CloudEventsMetrics.cs +++ b/src/NServiceBus.Envelope.CloudEvents/CloudEventsMetrics.cs @@ -1,6 +1,5 @@ namespace NServiceBus.Envelope.CloudEvents; -using System; using System.Diagnostics; using System.Diagnostics.Metrics; diff --git a/src/NServiceBus.Envelope.CloudEvents/NServiceBus.Envelope.CloudEvents.csproj b/src/NServiceBus.Envelope.CloudEvents/NServiceBus.Envelope.CloudEvents.csproj index cd51163..b6a112c 100644 --- a/src/NServiceBus.Envelope.CloudEvents/NServiceBus.Envelope.CloudEvents.csproj +++ b/src/NServiceBus.Envelope.CloudEvents/NServiceBus.Envelope.CloudEvents.csproj @@ -7,7 +7,7 @@ - +