From fdc9bde97c54e7d8f20c45861aa592eb882b213d Mon Sep 17 00:00:00 2001 From: Erwin Date: Fri, 10 Apr 2026 12:02:15 +0200 Subject: [PATCH 1/3] flatten logic Signed-off-by: Erwin --- .../JsonEventFormatter.cs | 43 ++++++++----------- .../JsonEventFormatter.cs | 32 +++++++------- 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs b/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs index 1737c66..8718d27 100644 --- a/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs +++ b/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs @@ -521,33 +521,28 @@ protected virtual void EncodeStructuredModeData(CloudEvent cloudEvent, JsonWrite { writer.WritePropertyName(DataBase64PropertyName); writer.WriteValue(Convert.ToBase64String(binary)); + return; } - else + + // Throwing exception only happens in a derived class which overrides GetOrInferDataContentType further... + // This class infers application/json for anything other than byte arrays. + string? dataContentTypeText = GetOrInferDataContentType(cloudEvent) ?? throw new ArgumentException("Data content type cannot be inferred"); + + var dataContentType = new ContentType(dataContentTypeText); + if (IsJsonMediaType(dataContentType.MediaType)) { - string? dataContentTypeText = GetOrInferDataContentType(cloudEvent); - // This would only happen in a derived class which overrides GetOrInferDataContentType further... - // This class infers application/json for anything other than byte arrays. - if (dataContentTypeText is null) - { - throw new ArgumentException("Data content type cannot be inferred"); - } - ContentType dataContentType = new ContentType(dataContentTypeText); - if (IsJsonMediaType(dataContentType.MediaType)) - { - writer.WritePropertyName(DataPropertyName); - Serializer.Serialize(writer, cloudEvent.Data); - } - else if (cloudEvent.Data is string text && dataContentType.MediaType.StartsWith("text/")) - { - writer.WritePropertyName(DataPropertyName); - writer.WriteValue(text); - } - else - { - // We assume CloudEvent.Data is not null due to the way this is called. - throw new ArgumentException($"{nameof(JsonEventFormatter)} cannot serialize data of type {cloudEvent.Data!.GetType()} with content type '{cloudEvent.DataContentType}'"); - } + writer.WritePropertyName(DataPropertyName); + Serializer.Serialize(writer, cloudEvent.Data); + return; + } + if (cloudEvent.Data is string text && dataContentType.MediaType.StartsWith("text/")) + { + writer.WritePropertyName(DataPropertyName); + writer.WriteValue(text); + return; } + // We assume CloudEvent.Data is not null due to the way this is called. + throw new ArgumentException($"{nameof(JsonEventFormatter)} cannot serialize data of type {cloudEvent.Data!.GetType()} with content type '{cloudEvent.DataContentType}'"); } /// diff --git a/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs b/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs index e170bd2..a605934 100644 --- a/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs +++ b/src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs @@ -540,26 +540,24 @@ protected virtual void EncodeStructuredModeData(CloudEvent cloudEvent, Utf8JsonW { writer.WritePropertyName(DataBase64PropertyName); writer.WriteStringValue(Convert.ToBase64String(binary)); + return; } - else + + var dataContentType = new ContentType(cloudEvent.DataContentType ?? JsonMediaType); + if (IsJsonMediaType(dataContentType.MediaType)) { - ContentType dataContentType = new ContentType(cloudEvent.DataContentType ?? JsonMediaType); - if (IsJsonMediaType(dataContentType.MediaType)) - { - writer.WritePropertyName(DataPropertyName); - JsonSerializer.Serialize(writer, cloudEvent.Data, SerializerOptions); - } - else if (cloudEvent.Data is string text && dataContentType.MediaType.StartsWith("text/")) - { - writer.WritePropertyName(DataPropertyName); - writer.WriteStringValue(text); - } - else - { - // We assume CloudEvent.Data is not null due to the way this is called. - throw new ArgumentException($"{nameof(JsonEventFormatter)} cannot serialize data of type {cloudEvent.Data!.GetType()} with content type '{cloudEvent.DataContentType}'"); - } + writer.WritePropertyName(DataPropertyName); + JsonSerializer.Serialize(writer, cloudEvent.Data, SerializerOptions); + return; + } + if (cloudEvent.Data is string text && dataContentType.MediaType.StartsWith("text/")) + { + writer.WritePropertyName(DataPropertyName); + writer.WriteStringValue(text); + return; } + // We assume CloudEvent.Data is not null due to the way this is called. + throw new ArgumentException($"{nameof(JsonEventFormatter)} cannot serialize data of type {cloudEvent.Data!.GetType()} with content type '{cloudEvent.DataContentType}'"); } /// From de883104b08de47c5729b6c08d535903019d5654 Mon Sep 17 00:00:00 2001 From: Erwin Date: Fri, 10 Apr 2026 12:39:58 +0200 Subject: [PATCH 2/3] style improvements Signed-off-by: Erwin --- .../HttpRequestExtensions.cs | 5 ----- .../AvroEventFormatter.cs | 17 +++++++---------- .../KafkaExtensions.cs | 2 +- src/CloudNative.CloudEvents/CloudEvent.cs | 2 +- .../Http/HttpClientExtensions.cs | 8 +++----- .../Http/HttpListenerExtensions.cs | 8 +++----- 6 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtensions.cs b/src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtensions.cs index 90ffc4e..634d958 100644 --- a/src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtensions.cs +++ b/src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtensions.cs @@ -85,11 +85,6 @@ public static async Task ToCloudEventAsync( var version = CloudEventsSpecVersion.FromVersionId(versionId.FirstOrDefault()) ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(httpRequest)); - if (version is null) - { - throw new ArgumentException($"Unsupported CloudEvents spec version '{versionId.First()}'"); - } - var cloudEvent = new CloudEvent(version, extensionAttributes); foreach (var header in headers) { diff --git a/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs b/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs index 46f104c..70d4805 100644 --- a/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs +++ b/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs @@ -95,15 +95,12 @@ private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable recordAttributes = (IDictionary) attrObj; if (!recordAttributes.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var versionId) || - !(versionId is string versionIdString)) + versionId is not string versionIdString) { throw new ArgumentException("Specification version attribute is missing"); } - CloudEventsSpecVersion? version = CloudEventsSpecVersion.FromVersionId(versionIdString); - if (version is null) - { - throw new ArgumentException($"Unsupported CloudEvents spec version '{versionIdString}'"); - } + var version = CloudEventsSpecVersion.FromVersionId(versionIdString) + ?? throw new ArgumentException($"Unsupported CloudEvents spec version '{versionIdString}'"); var cloudEvent = new CloudEvent(version, extensionAttributes); cloudEvent.Data = record.TryGetValue(DataName, out var data) ? data : null; @@ -125,13 +122,13 @@ private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable EncodeStructuredModeMessage(CloudEvent clou var attribute = keyValuePair.Key; var value = keyValuePair.Value; // TODO: Create a mapping method in each direction, to have this logic more clearly separated. - var avroValue = value is bool || value is int || value is byte[] || value is string + var avroValue = value is bool or int or byte[] or string ? value : attribute.Format(value); recordAttributes[attribute.Name] = avroValue; diff --git a/src/CloudNative.CloudEvents.Kafka/KafkaExtensions.cs b/src/CloudNative.CloudEvents.Kafka/KafkaExtensions.cs index 9a34fae..7bdf3f3 100644 --- a/src/CloudNative.CloudEvents.Kafka/KafkaExtensions.cs +++ b/src/CloudNative.CloudEvents.Kafka/KafkaExtensions.cs @@ -77,7 +77,7 @@ public static CloudEvent ToCloudEvent(this Message message, else { // Binary mode - if (!(GetHeaderValue(message, SpecVersionKafkaHeader) is string versionId)) + if (GetHeaderValue(message, SpecVersionKafkaHeader) is not string versionId) { throw new ArgumentException("Request is not a CloudEvent"); } diff --git a/src/CloudNative.CloudEvents/CloudEvent.cs b/src/CloudNative.CloudEvents/CloudEvent.cs index d0a72e2..6408052 100644 --- a/src/CloudNative.CloudEvents/CloudEvent.cs +++ b/src/CloudNative.CloudEvents/CloudEvent.cs @@ -195,7 +195,7 @@ public object? this[string attributeName] // (It's a simple way of populating extensions after the fact...) if (knownAttribute is null) { - Validation.CheckArgument(value is null || value is string, + Validation.CheckArgument(value is null or string, nameof(value), "Cannot assign value of type {0} to unknown attribute '{1}'", value?.GetType(), attributeName); knownAttribute = CloudEventAttribute.CreateExtension(attributeName, CloudEventAttributeType.String); diff --git a/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs b/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs index 4701312..78a07e9 100644 --- a/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs +++ b/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs @@ -143,11 +143,9 @@ private static async Task ToCloudEventInternalAsync(HttpHeaders head } else { - string? versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content?.Headers); - if (versionId is null) - { - throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(paramName)); - } + var versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content?.Headers) + ?? throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(paramName)); + var version = CloudEventsSpecVersion.FromVersionId(versionId) ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", paramName); diff --git a/src/CloudNative.CloudEvents/Http/HttpListenerExtensions.cs b/src/CloudNative.CloudEvents/Http/HttpListenerExtensions.cs index d2e5686..5e9d6c6 100644 --- a/src/CloudNative.CloudEvents/Http/HttpListenerExtensions.cs +++ b/src/CloudNative.CloudEvents/Http/HttpListenerExtensions.cs @@ -179,11 +179,9 @@ private static async Task ToCloudEventAsyncImpl(HttpListenerRequest } else { - string? versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader]; - if (versionId is null) - { - throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(httpListenerRequest)); - } + var versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader] + ?? throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(httpListenerRequest)); + var version = CloudEventsSpecVersion.FromVersionId(versionId) ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(httpListenerRequest)); From 05eaedc6b0b53c75ebc6f713c39a77e6f1ce90ac Mon Sep 17 00:00:00 2001 From: Erwin Date: Fri, 10 Apr 2026 14:27:10 +0200 Subject: [PATCH 3/3] EnforceCodeStyleInBuild and address some categories, mainly performance Signed-off-by: Erwin --- .editorconfig | 8 ++++- samples/HttpSend/Program.cs | 2 +- .../AvroEventFormatter.cs | 2 +- .../GlobalSuppressions.cs | 6 ++++ .../JsonEventFormatter.cs | 4 +-- .../JsonEventFormatter.cs | 4 +-- .../CloudEventAttribute.cs | 6 ++-- .../Extensions/Sampling.cs | 2 +- .../GlobalSuppressions.cs | 9 ++++++ src/Directory.Build.props | 1 + .../Amqp/AmqpTest.cs | 2 +- .../AspNetCore/HttpRequestExtensionsTest.cs | 8 ++--- .../Helpers/FakeGenericRecordSerializer.cs | 6 ++-- .../CloudEventFormatterTest.cs | 2 +- .../ConformanceTestData/SampleBatches.cs | 29 ++++++++++--------- .../ConformanceTestData/TestDataProvider.cs | 2 +- .../Kafka/KafkaTest.cs | 6 ++-- .../NewtonsoftJson/AttributedModel.cs | 2 +- .../GenericJsonEventFormatterTest.cs | 4 +-- .../NewtonsoftJson/JTokenAsserter.cs | 2 +- .../NewtonsoftJson/JsonEventFormatterTest.cs | 8 ++--- .../SpecializedFormatterTest.cs | 2 +- .../SpecializedJsonReaderTest.cs | 6 ++-- .../Protobuf/ProtobufEventFormatterTest.cs | 2 +- .../SystemTextJson/AttributedModel.cs | 2 +- .../GenericJsonEventFormatterTest.cs | 4 +-- .../SystemTextJson/JsonElementAsserter.cs | 2 +- .../SystemTextJson/JsonEventFormatterTest.cs | 10 +++---- .../SpecializedFormatterTest.cs | 2 +- .../TestHelpers.cs | 4 +-- 30 files changed, 86 insertions(+), 63 deletions(-) create mode 100644 src/CloudNative.CloudEvents.NewtonsoftJson/GlobalSuppressions.cs create mode 100644 src/CloudNative.CloudEvents/GlobalSuppressions.cs diff --git a/.editorconfig b/.editorconfig index 8c43c5f..22285ba 100644 --- a/.editorconfig +++ b/.editorconfig @@ -29,6 +29,12 @@ indent_size = 2 # Organize usings dotnet_sort_system_directives_first = false +# Analyzer categories +dotnet_analyzer_diagnostic.category-Maintainability.severity = warning +dotnet_analyzer_diagnostic.category-Security.severity = warning +dotnet_analyzer_diagnostic.category-Interoperability.severity = warning +dotnet_analyzer_diagnostic.category-Performance.severity = warning + # this. preferences dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion @@ -194,7 +200,7 @@ csharp_space_between_square_brackets = false csharp_preserve_single_line_statements = true csharp_preserve_single_line_blocks = true -csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_namespace_declarations = file_scoped:warning csharp_style_prefer_null_check_over_type_check = true:suggestion csharp_style_prefer_local_over_anonymous_function = true:suggestion csharp_style_prefer_tuple_swap = true:suggestion diff --git a/samples/HttpSend/Program.cs b/samples/HttpSend/Program.cs index d39aa99..064d511 100644 --- a/samples/HttpSend/Program.cs +++ b/samples/HttpSend/Program.cs @@ -16,7 +16,7 @@ namespace HttpSend; // This application uses the McMaster.Extensions.CommandLineUtils library for parsing the command // line and calling the application code. The [Option] attributes designate the parameters. -internal class Program +internal sealed class Program { [Option(Description = "CloudEvents 'source' (default: urn:example-com:mysource:abc)", LongName = "source", ShortName = "s")] private string Source { get; } = "urn:example-com:mysource:abc"; diff --git a/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs b/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs index 70d4805..ea49f0a 100644 --- a/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs +++ b/src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs @@ -86,7 +86,7 @@ public override IReadOnlyList DecodeBatchModeMessage(ReadOnlyMemory< public override ReadOnlyMemory EncodeBatchModeMessage(IEnumerable cloudEvent, out ContentType contentType) => throw new NotSupportedException("The Avro event formatter does not support batch content mode"); - private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable? extensionAttributes) + private static CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable? extensionAttributes) { if (!record.TryGetValue(AttributeName, out var attrObj)) { diff --git a/src/CloudNative.CloudEvents.NewtonsoftJson/GlobalSuppressions.cs b/src/CloudNative.CloudEvents.NewtonsoftJson/GlobalSuppressions.cs new file mode 100644 index 0000000..ff774f8 --- /dev/null +++ b/src/CloudNative.CloudEvents.NewtonsoftJson/GlobalSuppressions.cs @@ -0,0 +1,6 @@ +// 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", "CA1859: Use concrete types when possible for improved performance", Justification = "IReadOnlyList is part of the contract")] diff --git a/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs b/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs index 8718d27..212cd84 100644 --- a/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs +++ b/src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs @@ -219,7 +219,7 @@ private CloudEvent DecodeJObject(JObject jObject, IEnumerable public class CloudEventAttribute { - private static readonly IList ReservedNames = new List - { + private static readonly List ReservedNames = new() + { CloudEventsSpecVersion.SpecVersionAttributeName, "data" }; @@ -138,7 +138,7 @@ public object Parse(string text) } catch (Exception e) { - throw new ArgumentException($"Text for attribute '{Name}' is invalid: {e.Message}", nameof(value), e); + throw new ArgumentException($"Text for attribute '{Name}' is invalid: {e.Message}", nameof(text), e); } return Validate(value); } diff --git a/src/CloudNative.CloudEvents/Extensions/Sampling.cs b/src/CloudNative.CloudEvents/Extensions/Sampling.cs index b7758a5..59cece6 100644 --- a/src/CloudNative.CloudEvents/Extensions/Sampling.cs +++ b/src/CloudNative.CloudEvents/Extensions/Sampling.cs @@ -53,7 +53,7 @@ private static void PositiveInteger(object value) { if ((int) value <= 0) { - throw new ArgumentOutOfRangeException("Sampled rate must be positive."); + throw new ArgumentOutOfRangeException(nameof(value), "Sampled rate must be positive."); } } } diff --git a/src/CloudNative.CloudEvents/GlobalSuppressions.cs b/src/CloudNative.CloudEvents/GlobalSuppressions.cs new file mode 100644 index 0000000..a5bb962 --- /dev/null +++ b/src/CloudNative.CloudEvents/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// 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", "CA1835:Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'", Justification = "Should be rewritten", Scope = "member", Target = "~M:CloudNative.CloudEvents.Core.BinaryDataUtilities.CopyToStreamAsync(System.ReadOnlyMemory{System.Byte},System.IO.Stream)~System.Threading.Tasks.Task")] +[assembly: SuppressMessage("Performance", "CA1866:Use char overload", Justification = "Not supported for netstandard2.0", Scope = "member", Target = "~M:CloudNative.CloudEvents.CloudEventAttributeType.UriType.ParseImpl(System.String)~System.Uri")] diff --git a/src/Directory.Build.props b/src/Directory.Build.props index f102761..dfdf436 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -25,6 +25,7 @@ $(RepoRoot)/CloudEventsSdk.snk True True + true True 10.0 diff --git a/test/CloudNative.CloudEvents.UnitTests/Amqp/AmqpTest.cs b/test/CloudNative.CloudEvents.UnitTests/Amqp/AmqpTest.cs index b9034cb..04a8d52 100644 --- a/test/CloudNative.CloudEvents.UnitTests/Amqp/AmqpTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/Amqp/AmqpTest.cs @@ -115,7 +115,7 @@ public void ColonPrefix() AssertDecodeThenEqual(cloudEvent, message); } - private void AssertDecodeThenEqual(CloudEvent cloudEvent, Message message) + private static void AssertDecodeThenEqual(CloudEvent cloudEvent, Message message) { var encodedAmqpMessage = message.Encode(); diff --git a/test/CloudNative.CloudEvents.UnitTests/AspNetCore/HttpRequestExtensionsTest.cs b/test/CloudNative.CloudEvents.UnitTests/AspNetCore/HttpRequestExtensionsTest.cs index fce0a2d..88fb3fd 100644 --- a/test/CloudNative.CloudEvents.UnitTests/AspNetCore/HttpRequestExtensionsTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/AspNetCore/HttpRequestExtensionsTest.cs @@ -61,7 +61,7 @@ public void IsCloudEvent_True(string description, string contentType, IDictionar // Really only present for display purposes. Assert.NotNull(description); - var request = CreateRequest(new byte[0], new ContentType(contentType)); + var request = CreateRequest(Array.Empty(), new ContentType(contentType)); CopyHeaders(headers, request); Assert.True(request.IsCloudEvent()); } @@ -74,7 +74,7 @@ public void IsCloudEvent_False(string description, string contentType, IDictiona // Really only present for display purposes. Assert.NotNull(description); - var request = CreateRequest(new byte[0], new ContentType(contentType)); + var request = CreateRequest(Array.Empty(), new ContentType(contentType)); CopyHeaders(headers, request); Assert.False(request.IsCloudEvent()); } @@ -86,7 +86,7 @@ public void IsCloudEventBatch_True(string description, string contentType, IDict // Really only present for display purposes. Assert.NotNull(description); - var request = CreateRequest(new byte[0], new ContentType(contentType)); + var request = CreateRequest(Array.Empty(), new ContentType(contentType)); CopyHeaders(headers, request); Assert.True(request.IsCloudEventBatch()); } @@ -99,7 +99,7 @@ public void IsCloudEventBatch_False(string description, string contentType, IDic // Really only present for display purposes. Assert.NotNull(description); - var request = CreateRequest(new byte[0], new ContentType(contentType)); + var request = CreateRequest(Array.Empty(), new ContentType(contentType)); CopyHeaders(headers, request); Assert.False(request.IsCloudEventBatch()); } diff --git a/test/CloudNative.CloudEvents.UnitTests/Avro/Helpers/FakeGenericRecordSerializer.cs b/test/CloudNative.CloudEvents.UnitTests/Avro/Helpers/FakeGenericRecordSerializer.cs index 9bbf26e..f9d32d5 100644 --- a/test/CloudNative.CloudEvents.UnitTests/Avro/Helpers/FakeGenericRecordSerializer.cs +++ b/test/CloudNative.CloudEvents.UnitTests/Avro/Helpers/FakeGenericRecordSerializer.cs @@ -10,12 +10,12 @@ namespace CloudNative.CloudEvents.UnitTests.Avro.Helpers; -internal class FakeGenericRecordSerializer : IGenericRecordSerializer +internal sealed class FakeGenericRecordSerializer : IGenericRecordSerializer { public byte[]? SerializeResponse { get; private set; } public GenericRecord DeserializeResponse { get; private set; } - public int DeserializeCalls { get; private set; } = 0; - public int SerializeCalls { get; private set; } = 0; + public int DeserializeCalls { get; private set; } + public int SerializeCalls { get; private set; } public FakeGenericRecordSerializer() { diff --git a/test/CloudNative.CloudEvents.UnitTests/CloudEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/CloudEventFormatterTest.cs index 7fe11ab..3384a31 100644 --- a/test/CloudNative.CloudEvents.UnitTests/CloudEventFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/CloudEventFormatterTest.cs @@ -50,7 +50,7 @@ public void GetOrInferDataContentType_DataButNoContentType_DefaultInferDataConte Assert.Null(formatter.GetOrInferDataContentType(cloudEvent)); } - private class ContentTypeInferringFormatter : ThrowingEventFormatter + private sealed class ContentTypeInferringFormatter : ThrowingEventFormatter { protected override string? InferDataContentType(object data) => $"test/{data}"; } diff --git a/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/SampleBatches.cs b/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/SampleBatches.cs index 8bb21fb..708813e 100644 --- a/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/SampleBatches.cs +++ b/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/SampleBatches.cs @@ -5,35 +5,36 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace CloudNative.CloudEvents.UnitTests.ConformanceTestData; public static class SampleBatches { - private static readonly ConcurrentDictionary> batchesById = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> batchesById = new ConcurrentDictionary>(); - private static readonly IReadOnlyList empty = Register("empty"); - private static readonly IReadOnlyList minimal = Register("minimal", SampleEvents.Minimal); - private static readonly IReadOnlyList minimal2 = Register("minimal2", SampleEvents.Minimal, SampleEvents.Minimal); - private static readonly IReadOnlyList minimalAndAllCore = Register("minimalAndAllCore", SampleEvents.Minimal, SampleEvents.AllCore); - private static readonly IReadOnlyList minimalAndAllExtensionTypes = + private static readonly ReadOnlyCollection empty = Register("empty"); + private static readonly ReadOnlyCollection minimal = Register("minimal", SampleEvents.Minimal); + private static readonly ReadOnlyCollection minimal2 = Register("minimal2", SampleEvents.Minimal, SampleEvents.Minimal); + private static readonly ReadOnlyCollection minimalAndAllCore = Register("minimalAndAllCore", SampleEvents.Minimal, SampleEvents.AllCore); + private static readonly ReadOnlyCollection minimalAndAllExtensionTypes = Register("minimalAndAllExtensionTypes", SampleEvents.Minimal, SampleEvents.AllExtensionTypes); - internal static IReadOnlyList Empty => Clone(empty); - internal static IReadOnlyList Minimal => Clone(minimal); - internal static IReadOnlyList Minimal2 => Clone(minimal2); - internal static IReadOnlyList MinimalAndAllCore => Clone(minimalAndAllCore); - internal static IReadOnlyList MinimalAndAllExtensionTypes => Clone(minimalAndAllExtensionTypes); + internal static ReadOnlyCollection Empty => Clone(empty); + internal static ReadOnlyCollection Minimal => Clone(minimal); + internal static ReadOnlyCollection Minimal2 => Clone(minimal2); + internal static ReadOnlyCollection MinimalAndAllCore => Clone(minimalAndAllCore); + internal static ReadOnlyCollection MinimalAndAllExtensionTypes => Clone(minimalAndAllExtensionTypes); - internal static IReadOnlyList FromId(string id) => batchesById.TryGetValue(id, out var batch) + internal static ReadOnlyCollection FromId(string id) => batchesById.TryGetValue(id, out var batch) ? Clone(batch) : throw new ArgumentException($"No such sample batch: '{id}'"); - private static IReadOnlyList Clone(IReadOnlyList cloudEvents) => + private static ReadOnlyCollection Clone(ReadOnlyCollection cloudEvents) => cloudEvents.Select(SampleEvents.Clone).ToList().AsReadOnly(); - private static IReadOnlyList Register(string id, params CloudEvent[] cloudEvents) + private static ReadOnlyCollection Register(string id, params CloudEvent[] cloudEvents) { var list = new List(cloudEvents).AsReadOnly(); batchesById[id] = list; diff --git a/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/TestDataProvider.cs b/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/TestDataProvider.cs index a33ef69..b2af9d0 100644 --- a/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/TestDataProvider.cs +++ b/test/CloudNative.CloudEvents.UnitTests/ConformanceTestData/TestDataProvider.cs @@ -9,7 +9,7 @@ namespace CloudNative.CloudEvents.UnitTests.ConformanceTestData; -internal class TestDataProvider +internal sealed class TestDataProvider { private static readonly string ConformanceTestDataRoot = Path.Combine(FindRepoRoot(), "conformance", "format"); diff --git a/test/CloudNative.CloudEvents.UnitTests/Kafka/KafkaTest.cs b/test/CloudNative.CloudEvents.UnitTests/Kafka/KafkaTest.cs index 6ccb7a3..bdea690 100644 --- a/test/CloudNative.CloudEvents.UnitTests/Kafka/KafkaTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/Kafka/KafkaTest.cs @@ -194,7 +194,7 @@ public void ContentTypeCanBeInferredByFormatter() Assert.Equal("application/json", contentTypeValue); } - private class HeadersConverter : JsonConverter + private sealed class HeadersConverter : JsonConverter { public override bool CanConvert(Type objectType) { @@ -226,9 +226,9 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer } } - private class HeaderConverter : JsonConverter + private sealed class HeaderConverter : JsonConverter { - private class HeaderContainer + private sealed class HeaderContainer { public string? Key { get; set; } public byte[]? Value { get; set; } diff --git a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/AttributedModel.cs b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/AttributedModel.cs index 0ffff89..1eebeba 100644 --- a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/AttributedModel.cs +++ b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/AttributedModel.cs @@ -7,7 +7,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson.UnitTests; [CloudEventFormatter(typeof(JsonEventFormatter))] -internal class AttributedModel +internal sealed class AttributedModel { public const string JsonPropertyName = "customattribute"; diff --git a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/GenericJsonEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/GenericJsonEventFormatterTest.cs index f079403..8f62bc1 100644 --- a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/GenericJsonEventFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/GenericJsonEventFormatterTest.cs @@ -85,7 +85,7 @@ public void DecodeBinaryEventModeData_NoData() { var formatter = CreateFormatter(); var cloudEvent = new CloudEvent { Data = "original" }; - formatter.DecodeBinaryModeEventData(new byte[0], cloudEvent); + formatter.DecodeBinaryModeEventData(Array.Empty(), cloudEvent); Assert.Null(cloudEvent.Data); } @@ -167,7 +167,7 @@ private static CloudEventFormatter CreateFormatter() return formatter!; } - private class OtherModelClass + private sealed class OtherModelClass { public string? Text { get; set; } } diff --git a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JTokenAsserter.cs b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JTokenAsserter.cs index 1ca456a..9009db0 100644 --- a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JTokenAsserter.cs +++ b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JTokenAsserter.cs @@ -10,7 +10,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson.UnitTests; -internal class JTokenAsserter : IEnumerable +internal sealed class JTokenAsserter : IEnumerable { private readonly List<(string name, JTokenType type, object? value)> expectations = new List<(string, JTokenType, object?)>(); diff --git a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JsonEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JsonEventFormatterTest.cs index 25d05d0..02829b5 100644 --- a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JsonEventFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/JsonEventFormatterTest.cs @@ -435,7 +435,7 @@ public void EncodeBinaryModeEventData_NoContentType_LeavesBinaryData() [Fact] public void EncodeBatchModeMessage_Empty() { - var cloudEvents = new CloudEvent[0]; + var cloudEvents = Array.Empty(); var formatter = new JsonEventFormatter(); var bytes = formatter.EncodeBatchModeMessage(cloudEvents, out var contentType); Assert.Equal("application/cloudevents-batch+json; charset=utf-8", contentType.ToString()); @@ -835,14 +835,14 @@ public void DecodeStructuredModeMessage_NullDataIgnored() [Fact] public void DecodeBinaryModeEventData_EmptyData_JsonContentType() { - var data = DecodeBinaryModeEventData(new byte[0], "application/json"); + var data = DecodeBinaryModeEventData([], "application/json"); Assert.Null(data); } [Fact] public void DecodeBinaryModeEventData_EmptyData_TextContentType() { - var data = DecodeBinaryModeEventData(new byte[0], "text/plain"); + var data = DecodeBinaryModeEventData([], "text/plain"); var text = Assert.IsType(data); Assert.Equal("", text); } @@ -850,7 +850,7 @@ public void DecodeBinaryModeEventData_EmptyData_TextContentType() [Fact] public void DecodeBinaryModeEventData_EmptyData_OtherContentType() { - var data = DecodeBinaryModeEventData(new byte[0], "application/binary"); + var data = DecodeBinaryModeEventData([], "application/binary"); var byteArray = Assert.IsType(data); Assert.Empty(byteArray); } diff --git a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedFormatterTest.cs index 0b674f6..10634cb 100644 --- a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedFormatterTest.cs @@ -152,7 +152,7 @@ private static CloudEvent DecodeStructuredModeMessage(JObject obj) /// - Content type of "text/binary" is encoded in base64 /// - Guid with a content type of "application/guid" is encoded as a string with content "guid:base64-data" /// - private class SpecializedFormatter : JsonEventFormatter + private sealed class SpecializedFormatter : JsonEventFormatter { protected override void DecodeStructuredModeDataBase64Property(JToken dataBase64Token, CloudEvent cloudEvent) { diff --git a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedJsonReaderTest.cs b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedJsonReaderTest.cs index 48b8d4d..af14baa 100644 --- a/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedJsonReaderTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/NewtonsoftJson/SpecializedJsonReaderTest.cs @@ -58,7 +58,7 @@ public void Specialization_WithPropertyNameTable() Assert.Same(property1.Name, property2.Name); } - private Stream CreateJsonStream() + private static MemoryStream CreateJsonStream() { var cloudEvent = new CloudEvent { @@ -68,13 +68,13 @@ private Stream CreateJsonStream() return BinaryDataUtilities.AsStream(bytes); } - private class CreateJsonReaderExposingFormatter : JsonEventFormatter + private sealed class CreateJsonReaderExposingFormatter : JsonEventFormatter { public JsonReader CreateJsonReaderPublic(Stream stream, Encoding? encoding) => base.CreateJsonReader(stream, encoding); } - private class PropertyNameTableFormatter : JsonEventFormatter + private sealed class PropertyNameTableFormatter : JsonEventFormatter { private readonly DefaultJsonNameTable table; diff --git a/test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs index 3f27dcd..8c4af92 100644 --- a/test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs @@ -294,7 +294,7 @@ public void DecodeStructuredModeMessage_Minimal() public void EncodeBatchModeMessage_Empty() { var formatter = new ProtobufEventFormatter(); - var bytes = formatter.EncodeBatchModeMessage(new CloudEvent[0], out var contentType); + var bytes = formatter.EncodeBatchModeMessage([], out var contentType); Assert.Equal("application/cloudevents-batch+protobuf; charset=utf-8", contentType.ToString()); var batch = V1.CloudEventBatch.Parser.ParseFrom(bytes.ToArray()); Assert.Empty(batch.Events); diff --git a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/AttributedModel.cs b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/AttributedModel.cs index 4542529..66e0868 100644 --- a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/AttributedModel.cs +++ b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/AttributedModel.cs @@ -7,7 +7,7 @@ namespace CloudNative.CloudEvents.SystemTextJson.UnitTests; [CloudEventFormatter(typeof(JsonEventFormatter))] -internal class AttributedModel +internal sealed class AttributedModel { public const string JsonPropertyName = "customattribute"; diff --git a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/GenericJsonEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/GenericJsonEventFormatterTest.cs index 19f2179..6b69163 100644 --- a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/GenericJsonEventFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/GenericJsonEventFormatterTest.cs @@ -103,7 +103,7 @@ public void DecodeBinaryEventModeData_NoData() { var formatter = CreateFormatter(); var cloudEvent = new CloudEvent { Data = "original" }; - formatter.DecodeBinaryModeEventData(new byte[0], cloudEvent); + formatter.DecodeBinaryModeEventData(Array.Empty(), cloudEvent); Assert.Null(cloudEvent.Data); } @@ -184,7 +184,7 @@ private static CloudEventFormatter CreateFormatter() return formatter!; } - private class OtherModelClass + private sealed class OtherModelClass { public string? Text { get; set; } } diff --git a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonElementAsserter.cs b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonElementAsserter.cs index 745a5a4..4fb4590 100644 --- a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonElementAsserter.cs +++ b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonElementAsserter.cs @@ -11,7 +11,7 @@ namespace CloudNative.CloudEvents.SystemTextJson.UnitTests; -internal class JsonElementAsserter : IEnumerable +internal sealed class JsonElementAsserter : IEnumerable { private readonly List<(string name, JsonValueKind type, object? value)> expectations = new List<(string, JsonValueKind, object?)>(); diff --git a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs index fed2712..587d6fc 100644 --- a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/JsonEventFormatterTest.cs @@ -443,7 +443,7 @@ public void EncodeBinaryModeEventData_NoContentType_LeavesBinaryData() [Fact] public void EncodeBatchModeMessage_Empty() { - var cloudEvents = new CloudEvent[0]; + var cloudEvents = Array.Empty(); var formatter = new JsonEventFormatter(); var bytes = formatter.EncodeBatchModeMessage(cloudEvents, out var contentType); Assert.Equal("application/cloudevents-batch+json; charset=utf-8", contentType.ToString()); @@ -855,14 +855,14 @@ public void DecodeStructuredModeMessage_NullDataIgnored() [Fact] public void DecodeBinaryModeEventData_EmptyData_JsonContentType() { - var data = DecodeBinaryModeEventData(new byte[0], "application/json"); + var data = DecodeBinaryModeEventData([], "application/json"); Assert.Null(data); } [Fact] public void DecodeBinaryModeEventData_EmptyData_TextContentType() { - var data = DecodeBinaryModeEventData(new byte[0], "text/plain"); + var data = DecodeBinaryModeEventData([], "text/plain"); var text = Assert.IsType(data); Assert.Equal("", text); } @@ -870,7 +870,7 @@ public void DecodeBinaryModeEventData_EmptyData_TextContentType() [Fact] public void DecodeBinaryModeEventData_EmptyData_OtherContentType() { - var data = DecodeBinaryModeEventData(new byte[0], "application/binary"); + var data = DecodeBinaryModeEventData([], "application/binary"); var byteArray = Assert.IsType(data); Assert.Empty(byteArray); } @@ -1218,7 +1218,7 @@ private static IReadOnlyList DecodeBatchModeMessage(Newtonsoft.Json. return formatter.DecodeBatchModeMessage(bytes, s_jsonCloudEventBatchContentType, null); } - private class YearMonthDayConverter : JsonConverter + private sealed class YearMonthDayConverter : JsonConverter { public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => DateTime.ParseExact(reader.GetString()!, "yyyy-MM-dd", CultureInfo.InvariantCulture); diff --git a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/SpecializedFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/SpecializedFormatterTest.cs index 4d98e2e..988659a 100644 --- a/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/SpecializedFormatterTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/SystemTextJson/SpecializedFormatterTest.cs @@ -152,7 +152,7 @@ private static CloudEvent DecodeStructuredModeMessage(JObject obj) /// - Content type of "text/binary" is encoded in base64 /// - Guid with a content type of "application/guid" is encoded as a string with content "guid:base64-data" /// - private class SpecializedFormatter : JsonEventFormatter + private sealed class SpecializedFormatter : JsonEventFormatter { protected override void DecodeStructuredModeDataBase64Property(JsonElement dataBase64Element, CloudEvent cloudEvent) { diff --git a/test/CloudNative.CloudEvents.UnitTests/TestHelpers.cs b/test/CloudNative.CloudEvents.UnitTests/TestHelpers.cs index 36b49ae..a790b42 100644 --- a/test/CloudNative.CloudEvents.UnitTests/TestHelpers.cs +++ b/test/CloudNative.CloudEvents.UnitTests/TestHelpers.cs @@ -20,7 +20,7 @@ internal static class TestHelpers internal static IEqualityComparer InstantOnlyTimestampComparer => EqualityComparer.Default; internal static IEqualityComparer StrictTimestampComparer => StrictTimestampComparerImpl.Instance; - internal static CloudEventAttribute[] EmptyExtensionArray { get; } = new CloudEventAttribute[0]; + internal static CloudEventAttribute[] EmptyExtensionArray { get; } = []; internal static IEnumerable EmptyExtensionSequence { get; } = new List().AsReadOnly(); /// @@ -217,7 +217,7 @@ internal static MemoryStream LoadResource(string resource) return output; } - private class StrictTimestampComparerImpl : IEqualityComparer + private sealed class StrictTimestampComparerImpl : IEqualityComparer { internal static StrictTimestampComparerImpl Instance { get; } = new StrictTimestampComparerImpl();