You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Streams are commonly passed between upload, export, and document-generation layers, but an unsupported read, write, or seek operation may fail only after the stream has traveled far from the call site. Add dedicated MustBeReadable, MustBeWritable, and MustBeSeekable guards so callers can state these capability requirements at API boundaries.
The guards must be thin, allocation-free checks of the corresponding Stream capability properties, preserve the fluent API and concrete stream type, and participate in the customizable single-file source distribution.
Acceptance Criteria
MustBeReadable, MustBeWritable, and MustBeSeekable are available for Stream instances and subclasses on .NET Standard 2.0, .NET Standard 2.1, and .NET 10 with identical semantics.
Each guard accepts a non-null stream exactly when its corresponding CanRead, CanWrite, or CanSeek property returns true; it does not inspect other capabilities, probe the stream with an I/O operation, change its position, or otherwise mutate it.
A null stream throws ArgumentNullException by default, while a stream lacking the required capability throws ArgumentException with the captured guarded expression and a capability-specific message; no new public exception type is introduced.
Every guard returns the original successfully validated instance in its concrete stream shape, accepts an optional custom message, and provides a custom-exception-factory overload consistent with the existing nullable reference APIs.
Successful default and custom-factory guards perform no heap allocation.
Automated tests cover all supported and unsupported capability states, null values, default exception contracts, custom messages, custom exception factories, caller argument expressions, preservation of the original instance and concrete type, and confirmation that each guard reads only its matching capability property.
The source-export whitelist catalog and committed settings contain all three assertion families, and focused source-export tests cover their guard, throw-helper, and exception-factory reachability.
The committed .NET Standard 2.0 single-file distribution is regenerated with the new stream guards, and generated source validates for both .NET Standard 2.0 and .NET 10.
XML documentation and the assertion overview describe the three guards, their null and failure behavior, fluent return values, and property-only validation semantics.
The complete solution restores and builds without warnings in Release configuration, and all automated tests pass on the pinned SDK.
Technical Details
Implement one Check.<Assertion>.cs file per guard family. Use a generic stream subtype parameter so fluent chains retain types such as FileStream and MemoryStream. The following signatures illustrate the shared public shape; the writable and seekable families mirror it:
Apply the repository's established nullable-flow and JetBrains contract annotations. Default overloads should follow the MustBeAbsoluteUri null-check pattern: validate null first, inspect the relevant property once, and return the same reference. Factory overloads invoke the factory for either null or capability failure and pass the original nullable stream value to it.
The hot success path is only a null check, one virtual property read, one conditional branch, and the return. Keep it aggressively inlineable and route default failure construction through non-returning Throw helpers. Those helpers should create ArgumentException messages that name the missing capability without formatting the stream or calling its ToString; failures must not attempt an operation to discover why a capability is unavailable. Boolean IsReadable, IsWritable, and IsSeekable predicates are out of scope because CanRead, CanWrite, and CanSeek already expose the predicates directly.
Use small hand-written Stream test doubles to represent independent capability combinations and to record property access; do not assume that capabilities imply one another. Include disposed framework streams as representative property-defined failures, but preserve the Stream implementation's own behavior if a custom capability getter throws.
Add MustBeReadable, MustBeWritable, and MustBeSeekable to AssertionWhitelist and settings.json, cover trimming/reachability of their default and factory paths, regenerate Light.GuardClauses.SingleFile.cs as the .NET Standard 2.0 artifact, and add a stream assertion section to docs/assertion-overview.md.
Rationale
Streams are commonly passed between upload, export, and document-generation layers, but an unsupported read, write, or seek operation may fail only after the stream has traveled far from the call site. Add dedicated
MustBeReadable,MustBeWritable, andMustBeSeekableguards so callers can state these capability requirements at API boundaries.The guards must be thin, allocation-free checks of the corresponding
Streamcapability properties, preserve the fluent API and concrete stream type, and participate in the customizable single-file source distribution.Acceptance Criteria
MustBeReadable,MustBeWritable, andMustBeSeekableare available forStreaminstances and subclasses on .NET Standard 2.0, .NET Standard 2.1, and .NET 10 with identical semantics.CanRead,CanWrite, orCanSeekproperty returnstrue; it does not inspect other capabilities, probe the stream with an I/O operation, change its position, or otherwise mutate it.ArgumentNullExceptionby default, while a stream lacking the required capability throwsArgumentExceptionwith the captured guarded expression and a capability-specific message; no new public exception type is introduced.Technical Details
Implement one
Check.<Assertion>.csfile per guard family. Use a generic stream subtype parameter so fluent chains retain types such asFileStreamandMemoryStream. The following signatures illustrate the shared public shape; the writable and seekable families mirror it:Apply the repository's established nullable-flow and JetBrains contract annotations. Default overloads should follow the
MustBeAbsoluteUrinull-check pattern: validate null first, inspect the relevant property once, and return the same reference. Factory overloads invoke the factory for either null or capability failure and pass the original nullable stream value to it.The hot success path is only a null check, one virtual property read, one conditional branch, and the return. Keep it aggressively inlineable and route default failure construction through non-returning
Throwhelpers. Those helpers should createArgumentExceptionmessages that name the missing capability without formatting the stream or calling itsToString; failures must not attempt an operation to discover why a capability is unavailable. BooleanIsReadable,IsWritable, andIsSeekablepredicates are out of scope becauseCanRead,CanWrite, andCanSeekalready expose the predicates directly.Use small hand-written
Streamtest doubles to represent independent capability combinations and to record property access; do not assume that capabilities imply one another. Include disposed framework streams as representative property-defined failures, but preserve theStreamimplementation's own behavior if a custom capability getter throws.Add
MustBeReadable,MustBeWritable, andMustBeSeekabletoAssertionWhitelistandsettings.json, cover trimming/reachability of their default and factory paths, regenerateLight.GuardClauses.SingleFile.csas the .NET Standard 2.0 artifact, and add a stream assertion section todocs/assertion-overview.md.