-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Add Guards.TryValidateNonNegative<T> to Encina.GuardClauses to provide an ROP-compatible equivalent of .NET 8's ArgumentOutOfRangeException.ThrowIfNegative<T>.
Motivation
.NET 8 introduced ArgumentOutOfRangeException.ThrowIfNegative<T>() which validates that a value is >= 0. However, this is exception-based and incompatible with Encina's Railway Oriented Programming (ROP) pattern using Either<EncinaError, T>.
Currently, Encina has TryValidatePositive (> 0) but lacks TryValidateNonNegative (>= 0). This is a common need in domain modeling where zero is a valid value (e.g., account balance, discount percentage, stock quantity).
Priority: High
Use Cases
- Validating account balances (zero balance is valid, negative is not)
- Validating discount percentages (0% is valid, -5% is not)
- Validating stock quantities after decrement operations
Proposed Solution
/// <summary>
/// Validates that a value is not negative (greater than or equal to zero).
/// </summary>
public static bool TryValidateNonNegative<T>(
T value,
string paramName,
out EncinaError error,
string? message = null)
where T : IComparable<T>
Usage Example
// Try-pattern (imperative)
if (!Guards.TryValidateNonNegative(balance, nameof(balance), out var error))
return Left<EncinaError, AccountId>(error);
// Or with Ensure (functional)
return Right<EncinaError, decimal>(balance)
.Ensure(b => b >= 0m, b => EncinaErrors.Create("guard.validation_failed",
$"balance must be non-negative, was {b}"));
Alternatives Considered
- Use
TryValidate(value >= 0, ...)with manual condition: Works but loses semantic metadata (guard type, actual value) in the error details. - Use
TryValidateInRange(value, 0, max, ...): Requires knowing the max value, which is often not available.
Affected Packages
- Encina.GuardClauses
Test Matrix
| Test Type | Required? | Scope | Notes |
|---|---|---|---|
| UnitTests | Required | Success/failure paths, edge cases (zero, negative, positive), custom messages | |
| GuardTests | Required | Null parameter checks | |
| PropertyTests | Recommended | Invariant: returns true iff value >= 0 |
Implementation Tasks
- Add
Guards.TryValidateNonNegative<T>method toGuards.cs - Add comprehensive XML documentation with examples
- Add unit tests (positive, zero, negative values, custom messages)
- Add guard clause tests
- Add property-based tests
- Update README.md with new guard documentation
- Update
PublicAPI.Unshipped.txt
Acceptance Criteria
- Method implemented: returns
truefor values >= 0,falsefor negative values - Metadata includes guard type (
NonNegative), parameter name, actual value, type - Custom message support via optional parameter
- Comprehensive unit tests
- XML doc comments with examples
- Zero build warnings
- README updated
.NET Equivalent
ArgumentOutOfRangeException.ThrowIfNegative<T>(T value, string? paramName) (since .NET 8)
Related Issues
Part of ROP guard clause parity with .NET 8-10 built-in throw helpers.