Skip to content

Fix NRT errors and enable TreatWarningsAsErrors#72

Merged
niemyjski merged 11 commits intomainfrom
nrt/treat-warnings-as-errors
Apr 11, 2026
Merged

Fix NRT errors and enable TreatWarningsAsErrors#72
niemyjski merged 11 commits intomainfrom
nrt/treat-warnings-as-errors

Conversation

@niemyjski
Copy link
Copy Markdown
Member

Summary

  • Replaced ineffective <WarningsAsErrors>true\ with correct <TreatWarningsAsErrors>true\
  • Fixed nullable reference warnings in queue implementation and tests

Changes

  • build/common.props: \WarningsAsErrors\ → \TreatWarningsAsErrors\
  • AzureStorageQueue.cs: Null guard after envelope deserialization
  • Tests: Assert.NotNull guards before Value dereference
  • Samples: Conditional Properties assignment

Test plan

  • \dotnet build Foundatio.All.slnx\ — 0 warnings, 0 errors

- Replace ineffective WarningsAsErrors=true with TreatWarningsAsErrors=true
- Add null guard after deserializing queue message envelope
- Fix nullable Properties assignment in sample
- Add Assert.NotNull for queue entry Value in tests
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens build-time correctness by enabling nullable reference type analysis and treating warnings as errors, then updates Azure Storage queue/file storage code plus tests/samples to satisfy the new nullability requirements.

Changes:

  • Enable <Nullable>enable</Nullable> and enforce <TreatWarningsAsErrors>true</TreatWarningsAsErrors> at the common build props level.
  • Align Azure Storage queue/file storage APIs and internal models with nullable annotations (including safer handling around message envelope deserialization).
  • Update queue/storage tests and sample apps to avoid nullable dereferences and to set optional properties conditionally.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/Foundatio.AzureStorage.Tests/Storage/AzureStorageTests.cs Adjusts connection string nullability and suppresses NRT warnings when returning null storage in test setup.
tests/Foundatio.AzureStorage.Tests/Queues/LegacyAzureStorageQueueTests.cs Makes optional parameters nullable and avoids passing nulls into option builder methods.
tests/Foundatio.AzureStorage.Tests/Queues/AzureStorageQueueTests.cs Adds null guards in tests and minor connection string typing tweaks.
tests/Directory.Build.props Bumps Foundatio.TestHarness package version.
src/Foundatio.AzureStorage/Storage/AzureFileStorageOptions.cs Initializes required options and marks optional retry configuration nullable.
src/Foundatio.AzureStorage/Storage/AzureFileStorage.cs Updates return types/parameters for nullability (e.g., file-not-found returning null) and propagates nullable searchPattern handling.
src/Foundatio.AzureStorage/Queues/AzureStorageQueueOptions.cs Initializes required options and marks optional retry configuration nullable.
src/Foundatio.AzureStorage/Queues/AzureStorageQueueEntry.cs Makes correlationId/properties nullable in the entry constructor to match actual usage.
src/Foundatio.AzureStorage/Queues/AzureStorageQueue.cs Makes enqueue/dequeue return types nullable and adds a null guard/fallback around envelope deserialization.
src/Foundatio.AzureStorage/Foundatio.AzureStorage.csproj Bumps Foundatio package version.
samples/Foundatio.AzureStorage.Enqueue/Program.cs Adjusts nullability at call sites and sets QueueEntryOptions.Properties conditionally.
samples/Foundatio.AzureStorage.Dequeue/Program.cs Adjusts nullability at call sites for queue name.
build/common.props Enables nullable, enforces warnings-as-errors, and updates SourceLink package version.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Member Author

@niemyjski niemyjski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot review

entry.Value can be null for queue entries; use null-conditional access.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Foundatio.AzureStorage/Queues/AzureStorageQueue.cs
Comment thread src/Foundatio.AzureStorage/Queues/AzureStorageQueue.cs Outdated
Comment thread build/common.props Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- ConnectionString properties: null! -> string? (validated at construction)
- SearchCriteria.Prefix: null! -> String.Empty
- QueueMessageEnvelope.Data: null! -> required modifier
- Test GetQueue/GetStorage: return null! -> nullable return type + null
- Samples: string.Empty/string.Join -> String.Empty/String.Join
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Foundatio.AzureStorage/Storage/AzureFileStorageOptions.cs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Foundatio.AzureStorage/Queues/AzureStorageQueue.cs Outdated
Comment thread src/Foundatio.AzureStorage/Queues/AzureStorageQueueEntry.cs Outdated
Comment thread samples/Foundatio.AzureStorage.Enqueue/Program.cs Outdated
Comment thread samples/Foundatio.AzureStorage.Dequeue/Program.cs Outdated
- Change connection string validation from ThrowIfNullOrEmpty to ThrowIfNullOrWhiteSpace
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/Foundatio.AzureStorage/Queues/AzureStorageQueueOptions.cs:85

  • AzureStorageQueueOptionsBuilder.ConnectionString now rejects whitespace-only values, but AzureStorageQueue’s constructor still only checks String.IsNullOrEmpty(options.ConnectionString). If options are instantiated/bound without the builder, a whitespace connection string will slip through and fail later inside the Azure SDK. Consider aligning the runtime validation to IsNullOrWhiteSpace (and throwing a clear ArgumentException) so both configuration paths behave consistently.
public class AzureStorageQueueOptionsBuilder<T> : SharedQueueOptionsBuilder<T, AzureStorageQueueOptions<T>, AzureStorageQueueOptionsBuilder<T>> where T : class
{
    public AzureStorageQueueOptionsBuilder<T> ConnectionString(string connectionString)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);

        Target.ConnectionString = connectionString;
        return this;
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread build/common.props Outdated
- Revert Microsoft.SourceLink.GitHub from 10.0.200 back to 10.0.201

- Use ThrowIfNullOrWhiteSpace for ConnectionString validation in queue and storage constructors to align with builder-side validation

- Add missing ConnectionString validation in AzureFileStorage constructor

- Use ThrowIfNullOrWhiteSpace for ContainerName in storage builder
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Foundatio.AzureStorage/Storage/AzureFileStorage.cs
Comment thread build/common.props
Comment on lines 16 to 20
<NoWarn>$(NoWarn);CS1591;NU1701</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions replacing <WarningsAsErrors> with <TreatWarningsAsErrors>, but this repo doesn’t appear to contain <WarningsAsErrors> anywhere, and TreatWarningsAsErrors is already present here. Please update the PR description (or include the missing change) so it accurately reflects what’s actually being modified (e.g., enabling <Nullable>enable</Nullable> in this file).

Copilot uses AI. Check for mistakes.
Console.WriteLine();

await DequeueMessages(connectionString, queueName, mode, count);
await DequeueMessages(connectionString, queueName!, mode, count);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the console error out if these are not specified and not get this far?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Sample now uses null coalescing with default values instead of null-forgiving operators, eliminating the ! hack.

{
// Arrange - Inject a raw (non-envelope) message simulating legacy format
string connectionString = Configuration.GetConnectionString("AzureStorageConnectionString");
var connectionString = Configuration.GetConnectionString("AzureStorageConnectionString");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

always prefer built in types. this should be string?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Changed to \string?\ (built-in type alias).

public async Task DequeueAsync_WithCorruptMessage_MovesToDeadletterAsync()
{
string connectionString = Configuration.GetConnectionString("AzureStorageConnectionString");
var connectionString = Configuration.GetConnectionString("AzureStorageConnectionString");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var connectionString = Configuration.GetConnectionString("AzureStorageConnectionString");
string? connectionString = Configuration.GetConnectionString("AzureStorageConnectionString");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Using \string?\ for connectionString declarations.

Comment on lines +41 to +44
if (timeProvider is not null)
builder.TimeProvider(timeProvider);
if (serializer is not null)
builder.Serializer(serializer);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just chain it like before this is automatically handled

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Reverted to original chaining pattern.

… comments, use guard clauses, fix nullability

- Implement deserializeException pattern matching SQS/AzureServiceBus providers

- Restore removed code comments (envelope unwrap, legacy fallback, legacy mode)

- NormalizePath now accepts/returns string? per reviewer request

- SearchCriteria.Prefix uses required property instead of String.Empty

- SampleMessage uses required properties instead of String.Empty defaults

- Sample programs use null coalescing instead of null-forgiving operators

- Revert test GetQueue methods to original chaining pattern

- Use string? (built-in alias) for connectionString in tests
EmulatorConnectionString;

var queueName = parseResult.GetValue(queueOption);
var queueName = parseResult.GetValue(queueOption) ?? "sample-queue";
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this always be specified since there is a default specified? Otherwise I'd expect some kind of error

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Added early validation for connection string and queue name — prints error and returns exit code 1 if missing.

var message = parseResult.GetValue(messageOption) ?? "Hello World";
var correlationId = parseResult.GetValue(correlationIdOption);
var properties = parseResult.GetValue(propertiesOption);
var properties = parseResult.GetValue(propertiesOption) ?? Array.Empty<string>();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var properties = parseResult.GetValue(propertiesOption) ?? Array.Empty<string>();
var properties = parseResult.GetValue(propertiesOption) ?? [];

why isn't these defaults from the parser options itself?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Using ?? []\ syntax. Removed redundant null coalescing defaults that the parser already provides via \DefaultValueFactory. Also removed the now-redundant \if (properties != null)\ check.

_logger.LogWarning(deserializeException, "Error deserializing message {MessageId} (attempt {DequeueCount}), abandoning for retry", message.MessageId, message.DequeueCount);

var poisonEntry = new AzureStorageQueueEntry<T>(message, null, null, null, this);
var poisonEntry = new AzureStorageQueueEntry<T>(message, null, null, default!, this);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WHY did this need to change, just pass null?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: Reverted to pass
ull\ directly. Updated \AzureStorageQueueEntry\ constructor to accept \T?\ so null is valid without \default!.

Comment thread src/Foundatio.AzureStorage/Storage/AzureFileStorage.cs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

var message = parseResult.GetValue(messageOption) ?? "Hello World";
var correlationId = parseResult.GetValue(correlationIdOption);
var properties = parseResult.GetValue(propertiesOption);
var properties = parseResult.GetValue(propertiesOption) ?? Array.Empty<string>();
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propertiesOption already provides a non-null default (Array.Empty) and this handler now also coalesces to Array.Empty<string>(), so properties is guaranteed non-null. The subsequent if (properties != null) check inside EnqueueMessages is now redundant—consider removing the null check and treating properties as always non-null throughout the sample to simplify the code path.

Suggested change
var properties = parseResult.GetValue(propertiesOption) ?? Array.Empty<string>();
var properties = parseResult.GetValue(propertiesOption);

Copilot uses AI. Check for mistakes.
Comment thread tests/Foundatio.AzureStorage.Tests/Queues/AzureStorageQueueTests.cs Outdated
…lidation, normalize path handling, pass null for poison entry
Comment thread src/Foundatio.AzureStorage/Storage/AzureFileStorage.cs Outdated
Comment thread tests/Foundatio.AzureStorage.Tests/Queues/AzureStorageQueueTests.cs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Assert
Assert.NotNull(entry);
Assert.NotNull(entry?.Value);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.NotNull(entry?.Value) makes it unclear whether the failure is because entry is null or because entry.Value is null, and it also doesn’t reliably help nullable flow analysis for subsequent entry.Value / entry.CompleteAsync() usages. Prefer asserting entry is non-null first (and then entry.Value if needed) so the intent and nullability narrowing are explicit.

Suggested change
Assert.NotNull(entry?.Value);
Assert.NotNull(entry);
Assert.NotNull(entry.Value);

Copilot uses AI. Check for mistakes.
Comment on lines +65 to 71
public async Task<Stream?> GetFileStreamAsync(string path, StreamMode streamMode, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(path);

string normalizedPath = NormalizePath(path);
string normalizedPath = NormalizePath(path)!;
_logger.LogTrace("Getting file stream for {Path}", normalizedPath);

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NormalizePath was made nullable (string?) which forces a proliferation of null-forgiveness (!) at call sites even when inputs have already been validated as non-null/non-empty. Consider keeping NormalizePath non-nullable for required paths (or adding an overload) so callers don’t need ! and nullability stays precise.

Copilot uses AI. Check for mistakes.
{
if (String.IsNullOrEmpty(options.ConnectionString))
throw new ArgumentException("ConnectionString is required.");
ArgumentException.ThrowIfNullOrWhiteSpace(options?.ConnectionString, nameof(options.ConnectionString));
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor validation uses options?.ConnectionString even though options is expected to be non-null (and is immediately dereferenced later). Using the null-conditional here can mask an unexpected null options (changing the failure mode) and is inconsistent with the AzureFileStorage constructor validation. Prefer validating options.ConnectionString directly after ensuring options is non-null.

Suggested change
ArgumentException.ThrowIfNullOrWhiteSpace(options?.ConnectionString, nameof(options.ConnectionString));
ArgumentNullException.ThrowIfNull(options);
ArgumentException.ThrowIfNullOrWhiteSpace(options.ConnectionString, nameof(options.ConnectionString));

Copilot uses AI. Check for mistakes.

if (data is null)
{
_logger.LogWarning(deserializeException, "Error deserializing message {MessageId} (attempt {DequeueCount}), abandoning for retry", message.MessageId, message.DequeueCount);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When data ends up null but no exception was thrown during deserialization (e.g., payload is literal null or an envelope deserializes with Data=null), deserializeException remains null and the log message still says "Error deserializing". Consider handling the deserializeException == null case separately (e.g., log that the payload was null) so operational logs accurately describe why the message is being abandoned/dead-lettered.

Suggested change
_logger.LogWarning(deserializeException, "Error deserializing message {MessageId} (attempt {DequeueCount}), abandoning for retry", message.MessageId, message.DequeueCount);
if (deserializeException is null)
_logger.LogWarning("Message {MessageId} deserialized to null payload (attempt {DequeueCount}), abandoning for retry", message.MessageId, message.DequeueCount);
else
_logger.LogWarning(deserializeException, "Error deserializing message {MessageId} (attempt {DequeueCount}), abandoning for retry", message.MessageId, message.DequeueCount);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is invalid feedback, it's fine to pass null for the exception here and it's what the logging api's do internally.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@niemyjski
Copy link
Copy Markdown
Member Author

@copilot review

Comment thread src/Foundatio.AzureStorage/Storage/AzureFileStorage.cs Outdated
@niemyjski niemyjski merged commit bb7e721 into main Apr 11, 2026
3 checks passed
@niemyjski niemyjski deleted the nrt/treat-warnings-as-errors branch April 11, 2026 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants