chore: Fix null reference complaints under .NET 8.0

Signed-off-by: Jon Skeet <jonskeet@google.com>
This commit is contained in:
Jon Skeet 2023-11-15 10:25:16 +00:00
parent 08264ad333
commit ad3a03d68f
9 changed files with 51 additions and 31 deletions

View File

@ -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.
@ -8,6 +8,7 @@ using Amqp.Types;
using CloudNative.CloudEvents.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Mime;
@ -145,7 +146,7 @@ namespace CloudNative.CloudEvents.Amqp
}
}
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);
@ -249,4 +250,4 @@ namespace CloudNative.CloudEvents.Amqp
return applicationProperties;
}
}
}
}

View File

@ -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>

View File

@ -185,9 +185,9 @@ namespace CloudNative.CloudEvents.Avro
// 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());
}
}
}
}

View File

@ -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.
@ -345,7 +345,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
{
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>
@ -498,7 +498,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
/// </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"/>.
@ -524,7 +524,14 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
}
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);
@ -710,4 +717,4 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
protected override void DecodeStructuredModeDataBase64Property(JToken dataBase64Token, CloudEvent cloudEvent) =>
throw new ArgumentException($"Data unexpectedly represented using '{DataBase64PropertyName}' within structured mode CloudEvent.");
}
}
}

View File

@ -56,7 +56,7 @@ namespace CloudNative.CloudEvents
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);

View File

@ -65,7 +65,7 @@ namespace CloudNative.CloudEvents.Core
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>
@ -79,7 +79,7 @@ namespace CloudNative.CloudEvents.Core
// 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>
@ -92,7 +92,7 @@ namespace CloudNative.CloudEvents.Core
{
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>
@ -108,13 +108,14 @@ namespace CloudNative.CloudEvents.Core
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());
}

View File

@ -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;
@ -57,7 +58,7 @@ namespace CloudNative.CloudEvents.Core
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;
}
@ -76,7 +77,7 @@ namespace CloudNative.CloudEvents.Core
/// </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);
@ -86,7 +87,7 @@ namespace CloudNative.CloudEvents.Core
/// </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);
}
}

View File

@ -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;
@ -130,7 +131,7 @@ namespace CloudNative.CloudEvents.Http
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));
@ -142,7 +143,7 @@ namespace CloudNative.CloudEvents.Http
}
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));
@ -151,7 +152,8 @@ namespace CloudNative.CloudEvents.Http
?? 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)
@ -231,7 +233,7 @@ namespace CloudNative.CloudEvents.Http
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));
@ -331,16 +333,16 @@ namespace CloudNative.CloudEvents.Http
}
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) =>
@ -348,4 +350,4 @@ namespace CloudNative.CloudEvents.Http
? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First()
: null;
}
}
}

View File

@ -179,7 +179,7 @@ namespace CloudNative.CloudEvents.Http
}
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));
@ -191,12 +191,18 @@ namespace CloudNative.CloudEvents.Http
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);
}