Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion samples/HttpSend/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,6 @@ public static async Task<CloudEvent> 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)
{
Expand Down
19 changes: 8 additions & 11 deletions src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public override IReadOnlyList<CloudEvent> DecodeBatchModeMessage(ReadOnlyMemory<
public override ReadOnlyMemory<byte> EncodeBatchModeMessage(IEnumerable<CloudEvent> cloudEvent, out ContentType contentType) =>
throw new NotSupportedException("The Avro event formatter does not support batch content mode");

private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable<CloudEventAttribute>? extensionAttributes)
private static CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable<CloudEventAttribute>? extensionAttributes)
{
if (!record.TryGetValue(AttributeName, out var attrObj))
{
Expand All @@ -95,15 +95,12 @@ private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable<CloudEv
IDictionary<string, object> recordAttributes = (IDictionary<string, object>) 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;
Expand All @@ -125,13 +122,13 @@ private CloudEvent DecodeGenericRecord(GenericRecord record, IEnumerable<CloudEv
// The Avro schema allows the value to be a Boolean, integer, string or bytes.
// Timestamps and URIs are represented as strings, so we just use SetAttributeFromString to handle those.
// TODO: This does mean that any extensions of these types must have been registered beforehand.
if (value is bool || value is int || value is byte[])
if (value is bool or int or byte[])
{
cloudEvent[key] = value;
}
else if (value is string)
else if (value is string v)
{
cloudEvent.SetAttributeFromString(key, (string) value);
cloudEvent.SetAttributeFromString(key, v);
}
else
{
Expand Down Expand Up @@ -160,7 +157,7 @@ public override ReadOnlyMemory<byte> 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;
Expand Down
2 changes: 1 addition & 1 deletion src/CloudNative.CloudEvents.Kafka/KafkaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static CloudEvent ToCloudEvent(this Message<string?, byte[]> 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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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")]
47 changes: 21 additions & 26 deletions src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ private CloudEvent DecodeJObject(JObject jObject, IEnumerable<CloudEventAttribut
return Validation.CheckCloudEventArgument(cloudEvent, paramName);
}

private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JObject jObject)
private static void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JObject jObject)
{
foreach (var keyValuePair in jObject)
{
Expand Down Expand Up @@ -264,7 +264,7 @@ private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JObjec
}
}

private void ValidateTokenTypeForAttribute(CloudEventAttribute? attribute, JTokenType tokenType)
private static void ValidateTokenTypeForAttribute(CloudEventAttribute? attribute, JTokenType tokenType)
{
// We can't validate unknown attributes, don't check for extension attributes,
// and null values will be ignored anyway.
Expand Down Expand Up @@ -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}'");
}

/// <inheritdoc />
Expand Down
36 changes: 17 additions & 19 deletions src/CloudNative.CloudEvents.SystemTextJson/JsonEventFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ private CloudEvent DecodeJsonElement(JsonElement element, IEnumerable<CloudEvent
return Validation.CheckCloudEventArgument(cloudEvent, paramName);
}

private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JsonElement element)
private static void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JsonElement element)
{
foreach (var jsonProperty in element.EnumerateObject())
{
Expand Down Expand Up @@ -274,7 +274,7 @@ private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JsonEl
}
}

private void ValidateTokenTypeForAttribute(CloudEventAttribute? attribute, JsonValueKind valueKind)
private static void ValidateTokenTypeForAttribute(CloudEventAttribute? attribute, JsonValueKind valueKind)
{
// We can't validate unknown attributes, don't check for extension attributes,
// and null values will be ignored anyway.
Expand Down Expand Up @@ -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}'");
}

/// <inheritdoc />
Expand Down
2 changes: 1 addition & 1 deletion src/CloudNative.CloudEvents/CloudEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/CloudNative.CloudEvents/CloudEventAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ namespace CloudNative.CloudEvents;
/// </summary>
public class CloudEventAttribute
{
private static readonly IList<string> ReservedNames = new List<string>
{
private static readonly List<string> ReservedNames = new()
{
CloudEventsSpecVersion.SpecVersionAttributeName,
"data"
};
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/CloudNative.CloudEvents/Extensions/Sampling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
}
9 changes: 9 additions & 0 deletions src/CloudNative.CloudEvents/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -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")]
8 changes: 3 additions & 5 deletions src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,9 @@ private static async Task<CloudEvent> 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);

Expand Down
8 changes: 3 additions & 5 deletions src/CloudNative.CloudEvents/Http/HttpListenerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,9 @@ private static async Task<CloudEvent> 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));

Expand Down
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<AssemblyOriginatorKeyFile>$(RepoRoot)/CloudEventsSdk.snk</AssemblyOriginatorKeyFile>
<SignAssembly>True</SignAssembly>
<Deterministic>True</Deterministic>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<!-- Keep C# 10 explicit while supporting netstandard2.0/netstandard2.1: older defaults break nullable and file-scoped namespaces. -->
<LangVersion>10.0</LangVersion>
Expand Down
2 changes: 1 addition & 1 deletion test/CloudNative.CloudEvents.UnitTests/Amqp/AmqpTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte>(), new ContentType(contentType));
CopyHeaders(headers, request);
Assert.True(request.IsCloudEvent());
}
Expand All @@ -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<byte>(), new ContentType(contentType));
CopyHeaders(headers, request);
Assert.False(request.IsCloudEvent());
}
Expand All @@ -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<byte>(), new ContentType(contentType));
CopyHeaders(headers, request);
Assert.True(request.IsCloudEventBatch());
}
Expand All @@ -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<byte>(), new ContentType(contentType));
CopyHeaders(headers, request);
Assert.False(request.IsCloudEventBatch());
}
Expand Down
Loading