Skip to content

Commit

Permalink
chore: Fix null reference complaints under .NET 8.0
Browse files Browse the repository at this point in the history
Signed-off-by: Jon Skeet <[email protected]>
  • Loading branch information
jskeet committed Nov 15, 2023
1 parent 08264ad commit ad3a03d
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 31 deletions.
7 changes: 4 additions & 3 deletions src/CloudNative.CloudEvents.Amqp/AmqpExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Cloud Native Foundation.
// Copyright (c) Cloud Native Foundation.
// Licensed under the Apache 2.0 license.
// See LICENSE file in the project root for full license information.

Expand All @@ -8,6 +8,7 @@
using CloudNative.CloudEvents.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Mime;

Expand Down Expand Up @@ -145,7 +146,7 @@ public static CloudEvent ToCloudEvent(
}
}

private static bool HasCloudEventsContentType(Message message, out string? contentType)
private static bool HasCloudEventsContentType(Message message, [NotNullWhen(true)] out string? contentType)
{
contentType = message.Properties.ContentType?.ToString();
return MimeUtilities.IsCloudEventsContentType(contentType);
Expand Down Expand Up @@ -249,4 +250,4 @@ private static ApplicationProperties MapHeaders(CloudEvent cloudEvent, string pr
return applicationProperties;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<PackageReference Include="AMQPNetLite" Version="2.4.2" />
<PackageReference Include="AMQPNetLite.Serialization" Version="2.4.2" />
<ProjectReference Include="..\CloudNative.CloudEvents\CloudNative.CloudEvents.csproj" />
<!-- Source-only package with nullable reference annotations. -->
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="All" />
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions src/CloudNative.CloudEvents.Avro/AvroEventFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ private static RecordSchema ParseEmbeddedSchema()
// will fail and that's okay since the type is useless without the proper schema.
using var sr = new StreamReader(typeof(AvroEventFormatter)
.Assembly
.GetManifestResourceStream("CloudNative.CloudEvents.Avro.AvroSchema.json"));
.GetManifestResourceStream("CloudNative.CloudEvents.Avro.AvroSchema.json")!);

return (RecordSchema) Schema.Parse(sr.ReadToEnd());
}
}
}
}
17 changes: 12 additions & 5 deletions src/CloudNative.CloudEvents.NewtonsoftJson/JsonEventFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Cloud Native Foundation.
// Copyright (c) Cloud Native Foundation.
// Licensed under the Apache 2.0 license.
// See LICENSE file in the project root for full license information.

Expand Down Expand Up @@ -345,7 +345,7 @@ protected virtual void DecodeStructuredModeDataBase64Property(JToken dataBase64T
{
throw new ArgumentException($"Structured mode property '{DataBase64PropertyName}' must be a string, when present.");
}
cloudEvent.Data = Convert.FromBase64String((string?) dataBase64Token);
cloudEvent.Data = Convert.FromBase64String((string) dataBase64Token!);
}

/// <summary>
Expand Down Expand Up @@ -498,7 +498,7 @@ private void WriteCloudEventForBatchOrStructuredMode(JsonWriter writer, CloudEve
/// </summary>
/// <param name="data">The CloudEvent to infer the data content from. Must not be null.</param>
/// <returns>The inferred data content type, or null if no inference is performed.</returns>
protected override string? InferDataContentType(object data) => data is byte[]? null : JsonMediaType;
protected override string? InferDataContentType(object data) => data is byte[] ? null : JsonMediaType;

/// <summary>
/// Encodes structured mode data within a CloudEvent, writing it to the specified <see cref="JsonWriter"/>.
Expand All @@ -524,7 +524,14 @@ protected virtual void EncodeStructuredModeData(CloudEvent cloudEvent, JsonWrite
}
else
{
ContentType dataContentType = new ContentType(GetOrInferDataContentType(cloudEvent));
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);
Expand Down Expand Up @@ -710,4 +717,4 @@ protected override void DecodeStructuredModeDataProperty(JToken dataToken, Cloud
protected override void DecodeStructuredModeDataBase64Property(JToken dataBase64Token, CloudEvent cloudEvent) =>
throw new ArgumentException($"Data unexpectedly represented using '{DataBase64PropertyName}' within structured mode CloudEvent.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public CloudEventFormatterAttribute(Type formatterType) =>
throw new ArgumentException($"The {nameof(CloudEventFormatterAttribute)} on type {targetType} has no converter type specified.", nameof(targetType));
}

object instance;
object? instance;
try
{
instance = Activator.CreateInstance(formatterType);
Expand Down
11 changes: 6 additions & 5 deletions src/CloudNative.CloudEvents/Core/BinaryDataUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public static ReadOnlyMemory<byte> ToReadOnlyMemory(Stream stream)
public static MemoryStream AsStream(ReadOnlyMemory<byte> memory)
{
var segment = GetArraySegment(memory);
return new MemoryStream(segment.Array, segment.Offset, segment.Count, false);
return new MemoryStream(segment.Array!, segment.Offset, segment.Count, false);
}

/// <summary>
Expand All @@ -79,7 +79,7 @@ public static string GetString(ReadOnlyMemory<byte> memory, Encoding encoding)

// TODO: If we introduce an additional netstandard2.1 target, we can use encoding.GetString(memory.Span)
var segment = GetArraySegment(memory);
return encoding.GetString(segment.Array, segment.Offset, segment.Count);
return encoding.GetString(segment.Array!, segment.Offset, segment.Count);
}

/// <summary>
Expand All @@ -92,7 +92,7 @@ public static async Task CopyToStreamAsync(ReadOnlyMemory<byte> source, Stream d
{
Validation.CheckNotNull(destination, nameof(destination));
var segment = GetArraySegment(source);
await destination.WriteAsync(segment.Array, segment.Offset, segment.Count).ConfigureAwait(false);
await destination.WriteAsync(segment.Array!, segment.Offset, segment.Count).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -108,13 +108,14 @@ public static byte[] AsArray(ReadOnlyMemory<byte> memory)
var segment = GetArraySegment(memory);
// We probably don't actually need to check the offset: if the count is the same as the length,
// I can't see how the offset can be non-zero. But it doesn't *hurt* as a check.
return segment.Offset == 0 && segment.Count == segment.Array.Length
return segment.Offset == 0 && segment.Count == segment.Array!.Length
? segment.Array
: memory.ToArray();
}

// Note: when this returns, the Array property of the returned segment is guaranteed not to be null.
private static ArraySegment<byte> GetArraySegment(ReadOnlyMemory<byte> memory) =>
MemoryMarshal.TryGetArray(memory, out var segment)
MemoryMarshal.TryGetArray(memory, out var segment) && segment.Array is not null
? segment
: new ArraySegment<byte>(memory.ToArray());
}
Expand Down
9 changes: 5 additions & 4 deletions src/CloudNative.CloudEvents/Core/MimeUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright 2021 Cloud Native Foundation.
// Copyright 2021 Cloud Native Foundation.
// Licensed under the Apache 2.0 license.
// See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
Expand Down Expand Up @@ -57,7 +58,7 @@ public static Encoding GetEncoding(ContentType? contentType) =>
var header = new MediaTypeHeaderValue(contentType.MediaType);
foreach (string parameterName in contentType.Parameters.Keys)
{
header.Parameters.Add(new NameValueHeaderValue(parameterName, contentType.Parameters[parameterName].ToString()));
header.Parameters.Add(new NameValueHeaderValue(parameterName, contentType.Parameters[parameterName]));
}
return header;
}
Expand All @@ -76,7 +77,7 @@ public static Encoding GetEncoding(ContentType? contentType) =>
/// </summary>
/// <param name="contentType">The content type to check. May be null, in which case the result is false.</param>
/// <returns>true if the given content type denotes a (non-batch) CloudEvent; false otherwise</returns>
public static bool IsCloudEventsContentType(string? contentType) =>
public static bool IsCloudEventsContentType([NotNullWhen(true)] string? contentType) =>
contentType is string &&
contentType.StartsWith(MediaType, StringComparison.InvariantCultureIgnoreCase) &&
!contentType.StartsWith(BatchMediaType, StringComparison.InvariantCultureIgnoreCase);
Expand All @@ -86,7 +87,7 @@ contentType is string &&
/// </summary>
/// <param name="contentType">The content type to check. May be null, in which case the result is false.</param>
/// <returns>true if the given content type represents a CloudEvent batch; false otherwise</returns>
public static bool IsCloudEventsBatchContentType(string? contentType) =>
public static bool IsCloudEventsBatchContentType([NotNullWhen(true)] string? contentType) =>
contentType is string && contentType.StartsWith(BatchMediaType, StringComparison.InvariantCultureIgnoreCase);
}
}
20 changes: 11 additions & 9 deletions src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright (c) Cloud Native Foundation.
// Copyright (c) Cloud Native Foundation.
// Licensed under the Apache 2.0 license.
// See LICENSE file in the project root for full license information.

using CloudNative.CloudEvents.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
Expand Down Expand Up @@ -130,7 +131,7 @@ public static Task<CloudEvent> ToCloudEventAsync(
return ToCloudEventInternalAsync(httpRequestMessage.Headers, httpRequestMessage.Content, formatter, extensionAttributes, nameof(httpRequestMessage));
}

private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content,
private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent? content,
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
{
Validation.CheckNotNull(formatter, nameof(formatter));
Expand All @@ -142,7 +143,7 @@ private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders head
}
else
{
string? versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content.Headers);
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));
Expand All @@ -151,7 +152,8 @@ private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders head
?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", paramName);

var cloudEvent = new CloudEvent(version, extensionAttributes);
foreach (var header in headers.Concat(content.Headers))
var allHeaders = content is null ? headers : headers.Concat(content.Headers);
foreach (var header in allHeaders)
{
string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key);
if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
Expand Down Expand Up @@ -231,7 +233,7 @@ public static Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchAsync(
return ToCloudEventBatchInternalAsync(httpRequestMessage.Content, formatter, extensionAttributes, nameof(httpRequestMessage));
}

private static async Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchInternalAsync(HttpContent content,
private static async Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchInternalAsync(HttpContent? content,
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
{
Validation.CheckNotNull(formatter, nameof(formatter));
Expand Down Expand Up @@ -331,21 +333,21 @@ public static HttpContent ToHttpContent(this IReadOnlyList<CloudEvent> cloudEven
}

private static ByteArrayContent ToByteArrayContent(ReadOnlyMemory<byte> content) =>
MemoryMarshal.TryGetArray(content, out var segment)
MemoryMarshal.TryGetArray(content, out var segment) && segment.Array is not null
? new ByteArrayContent(segment.Array, segment.Offset, segment.Count)
// TODO: Just throw?
: new ByteArrayContent(content.ToArray());

// TODO: This would include "application/cloudeventsarerubbish" for example...
private static bool HasCloudEventsContentType(HttpContent content) =>
private static bool HasCloudEventsContentType([NotNullWhen(true)] HttpContent? content) =>
MimeUtilities.IsCloudEventsContentType(content?.Headers?.ContentType?.MediaType);

private static bool HasCloudEventsBatchContentType(HttpContent content) =>
private static bool HasCloudEventsBatchContentType([NotNullWhen(true)] HttpContent? content) =>
MimeUtilities.IsCloudEventsBatchContentType(content?.Headers?.ContentType?.MediaType);

private static string? MaybeGetVersionId(HttpHeaders? headers) =>
headers is not null && headers.Contains(HttpUtilities.SpecVersionHttpHeader)
? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First()
: null;
}
}
}
10 changes: 8 additions & 2 deletions src/CloudNative.CloudEvents/Http/HttpListenerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private static async Task<CloudEvent> ToCloudEventAsyncImpl(HttpListenerRequest
}
else
{
string versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader];
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));
Expand All @@ -191,12 +191,18 @@ private static async Task<CloudEvent> ToCloudEventAsyncImpl(HttpListenerRequest
var headers = httpListenerRequest.Headers;
foreach (var key in headers.AllKeys)
{
// It would be highly unusual for either the key or the value to be null, but
// the contract claims it's possible. Skip it if so.
if (key is null || headers[key] is not string headerValue)
{
continue;
}
string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key);
if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
{
continue;
}
string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]);
string attributeValue = HttpUtilities.DecodeHeaderValue(headerValue);
cloudEvent.SetAttributeFromString(attributeName, attributeValue);
}

Expand Down

0 comments on commit ad3a03d

Please sign in to comment.