Skip to content

Add reusable guards identified by the BrilliantMessaging integration #162

Description

@feO2x

Context

BrilliantMessaging is preparing to consume a curated, public single-file export produced by Light.GuardClauses.SourceCodeTransformation for netstandard2.0.

An inventory of its production guard clauses found that most cases already map cleanly to Light.GuardClauses. The following gaps are reusable enough to consider adding upstream. The usage counts below come from the current BrilliantMessaging code base and are intended to show concrete demand, not to prescribe the final API.

Strong candidates

1. Object-disposed condition

BrilliantMessaging has five explicit disposal guards. The closest current expression is a generic boolean assertion with an exception factory, which obscures the intent.

A dedicated condition guard analogous to Check.InvalidOperation would make the call site explicit while remaining usable on netstandard2.0:

Check.ObjectDisposed(isDisposed, objectName, message);

Expected behavior: throw ObjectDisposedException only when the condition is true. The exact object-name and custom-exception-factory overloads should follow existing library conventions.

2. Parseable URI string, including relative-or-absolute references

BrilliantMessaging validates a URI supplied as a string and accepts UriKind.RelativeOrAbsolute. Existing URI assertions operate on Uri instances, so callers must currently write Uri.TryCreate logic themselves.

Potential shape:

value.MustBeUri(UriKind.RelativeOrAbsolute);

Open design question: return the original string for fluent argument validation, or return the parsed Uri. Supporting an explicit UriKind is more general than a dedicated URI-reference-only assertion and could also cover absolute and relative inputs.

3. Throwing type-assignability assertion

BrilliantMessaging has six guards that reject a Type unless it is assignable to a required base type or interface. Light.GuardClauses currently has useful predicates such as IsOrImplements, but no direct throwing assertion for this relationship.

Potential shapes:

type.MustBeAssignableTo(requiredType);
type.MustImplement(requiredInterface);

MustBeAssignableTo is the more general primitive and should use the same semantics as Type.IsAssignableFrom, with a clearly documented direction. A custom exception-factory overload would be valuable because frameworks often need a domain-specific configuration exception.

4. Concrete-class assertion

BrilliantMessaging has three guards requiring a handler type to be a class that is not abstract.

Potential shape:

type.MustBeConcreteClass();

This should reject null, interfaces, abstract classes, and non-class types. It composes naturally with MustBeAssignableTo when both constraints are required.

5. MustBePositive parity for older target frameworks

The generic INumber<T> overload covers additional numeric types on .NET 8 and later, but a source export targeting netstandard2.0 only receives the concrete overloads. BrilliantMessaging has seven byte or ushort positive-value guards and must fall back to MustBeGreaterThan(0).

Add concrete MustBePositive overloads for the integral types not covered by the generic implementation on older targets, at minimum byte and ushort. Ideally, review the complete signed and unsigned integral matrix (sbyte, byte, short, ushort, uint, and ulong) for symmetry. Default and custom-exception-factory overloads should remain consistent with the existing numeric assertions.

Lower-priority candidates

These appeared in BrilliantMessaging, but may be better implemented in its own public partial Check class unless broader use cases emerge.

6. Positive timeout or infinite sentinel

Two guards accept either a positive TimeSpan or Timeout.InfiniteTimeSpan.

Potential shape:

timeout.MustBePositiveOrInfiniteTimeout();

This encodes a standard .NET timeout convention and may be generally useful, but it is narrower than the candidates above.

7. Exactly one value supplied

Two guards require exactly one of a routing-key string and a routing-key factory to be present.

A general xor/presence assertion is possible, but a readable, type-safe API is not obvious. The current recommendation is to keep this as a BrilliantMessaging-specific guard rather than add a generic boolean combinator to Light.GuardClauses.

Source transformation requirements

Any accepted assertion should be selectable through AssertionWhitelist, bring along only its required exception/factory dependencies, and preserve the existing per-assertion option for custom exception-factory overloads. The motivating consumer exports public code for netstandard2.0 under its own namespace.

Proposed scope

  • Add ObjectDisposed.
  • Add a string URI assertion with configurable UriKind.
  • Add MustBeAssignableTo and decide whether MustImplement adds enough value as a convenience assertion.
  • Add MustBeConcreteClass.
  • Add missing concrete MustBePositive overloads for older targets.
  • Decide whether positive-or-infinite timeout belongs upstream.
  • Keep exactly-one-present local unless a broadly useful API emerges.
  • Expose every accepted assertion through the source-code-transformation whitelist and cover its exported form with tests.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions