Make Response and InvokeAsync non-nullable; unify request/response constraints#19
Merged
Merged
Conversation
…constraints Response on RequestContext was TResponse? and InvokeAsync returned Task<TResponse?>, forcing every caller to null-check a response the pipeline is expected to produce. Constrain TResponse : notnull and make Response a non-nullable TResponse (initialized to default!), so InvokeAsync returns Task<TResponse>. Also unify the request constraint: the Diagnostics and Serilog extensions used TRequest : class while the core used TRequest : notnull, which blocked value-type requests (e.g. int) from compiling against the diagnostics/ logging middleware even though the core pipeline accepts them. Nothing in those bodies relied on reference semantics, so relax class -> notnull everywhere. Request and response are now symmetric: any non-null value or reference type, with Unit as the "nothing" type on either side. BREAKING CHANGE: public signatures of RequestContext.Response, InvokeAsync, and the generic constraints on the extension methods change. Major version bump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a long-standing design flaw:
RequestContext.ResponsewasTResponse?andInvokeAsyncreturnedTask<TResponse?>, forcing every caller to null-check a response the pipeline is expected to produce.TResponse : notnullacross the core, the static factories, theRequestMiddlewaredelegate, andPlumberApplicationFactory.RequestContext.Responseis now a non-nullableTResponse(initialized todefault!);InvokeAsync(handler + factory) returnsTask<TResponse>.TRequest : classwhile the core usedTRequest : notnull. That blocked value-type requests (e.g.int) from compiling against the diagnostics/logging middleware even though the core pipeline accepts them. Nothing in those bodies relied on reference semantics, soclass→notnulleverywhere.Request and response are now symmetric — any non-null value or reference type, with
Unitas the "nothing" type on either side.TRequest : notnullTResponse : notnullUnitUnitnullnullNote on runtime semantics
The
notnullconstraint stops callers supplying a nullable type argument, and the property/return read as non-null. There is no runtime guard forcing middleware to assignResponse— a reference-type pipeline that returns without setting it yieldsdefault!typed as non-null. This is deliberate:Unitevent-style pipelines rely ondefault(Unit)being the valid sole value, and it matches the ASP.NETHttpContext.Responseconvention.Breaking change
Public signatures of
RequestContext.Response,InvokeAsync, and the generic constraints on the extension methods change. Major version bump.Verification
dotnet build— clean, 0 warningsdotnet format --severity info --verify-no-changes— cleandotnet test— 179 passed, 0 failed, 100% line/branch/method coverage🤖 Generated with Claude Code