SGuard 0.1.0

There is a newer version of this package available.
See the version list below for details.
The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package SGuard --version 0.1.0
                    
NuGet\Install-Package SGuard -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SGuard" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SGuard" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="SGuard" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SGuard --version 0.1.0
                    
#r "nuget: SGuard, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SGuard@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SGuard&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=SGuard&version=0.1.0
                    
Install as a Cake Tool

SGuard

Tests and Update README NuGet NuGet Downloads License: MIT

SGuard is a lightweight, extensible guard clause library for .NET, providing expressive and robust validation for method arguments, object state, and business rules. It offers both boolean checks (Is.*) and exception-throwing guards (ThrowIf.*), with a unified callback model and rich exception diagnostics.

πŸ†• What’s New in 0.1.0

  • Versioning reset: starting fresh at 0.1.0. Older NuGet versions have been unlisted/removed.
    • No functional breaking changes are expected for consumers adopting this version.
  • Targets: .NET 6, 7, 8, and 9.
  • Packaging: README, LICENSE, and package icon included in the NuGet package.

πŸš€ Features

  • Boolean Guards (Is.*): Check conditions without throwing exceptions.
  • Throwing Guards (ThrowIf.*): Throw exceptions when conditions are met, with CallerArgumentExpression-powered messages.
  • Any & All Guards: Predicate-based validation for collections.
  • Comprehensive Comparison Guards: Between, LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual for generics and strings (with StringComparison).
  • Null/Empty Checks: Deep and type-safe null/empty validation for primitives, collections, and complex types.
  • Custom Exception Support: Overloads for custom exception types, with constructor argument support.
  • Callback Model: Unified SGuardCallback and GuardOutcome for success/failure handling.
  • Expression Caching: Efficient, thread-safe caching for compiled expressions.
  • Rich Exception Messages: Informative diagnostics using CallerArgumentExpression.
  • Multi-targeting: Supports .NET 6, 7, 8, and 9.

πŸ“¦ Installation

dotnet add package SGuard

πŸ€” Why SGuard?

  • Clear diagnostics

    • Uses CallerArgumentExpression to produce precise, helpful error messages that point to the exact argument/expression that failed.
  • Consistent callback model

    • A single SGuardCallback(outcome) works across both APIs:
      • ThrowIf.* invokes with Failure when it’s about to throw, Success when it passes.
      • Is.* invokes with Success when the result is true, Failure when false.
    • Callback exceptions are safely swallowed, so your validation flow isn’t disrupted.
  • Rich exception surface

    • Throw built-in exceptions for common guards, or supply your own:
      • Pass a custom exception instance, use a generic TException, or provide constructor arguments for detailed messages.
  • Expressive, dual API

    • Choose the style that fits your code:
      • Is.* returns booleans for control-flow friendly checks.
      • ThrowIf.* fails fast with informative exceptions when rules are violated.
  • Culture-aware comparisons and inclusive ranges

    • String overloads accept StringComparison for correct cultural/ordinal semantics.
    • Between checks are inclusive by design for predictable validation.
  • Performance and ergonomics

    • Expression caching reduces overhead for repeated checks.
    • Minimal allocations and thread-safe evaluation where applicable.
  • Modern .NET support

    • Targets .NET 6, 7, 8, and 9 with multi-targeting, ensuring broad compatibility.

⚑ Quick Start

SGuard helps you validate inputs and state with two complementary APIs:

  • ThrowIf.*: fail fast by throwing informative exceptions.
  • Is.*: return booleans for control-flow-friendly checks.

1) Validate inputs (fail fast)

public record CreateUserRequest(string Username, int Age, string Email);

public User CreateUser(CreateUserRequest req) 
{ 
    ThrowIf.NullOrEmpty(req);
    ThrowIf.NullOrEmpty(req.Email);
    ThrowIf.NullOrEmpty(req.Username);
    ThrowIf.LessThan(req.Age, 13, new ArgumentException("User must be 13+.", nameof(req.Age)));
    
    // Optionally check formats or ranges
    if (!Is.Between(req.Age, 13, 130))
        throw new ArgumentOutOfRangeException(nameof(req.Age), "Age seems invalid.");

    return new User(req.Username, req.Age, req.Email);
}


public sealed class User 
{ 
    public User(string username, int age, string email) 
    {
        ThrowIf.LessThan(age, 0);
        ThrowIf.NullOrEmpty(email);
        ThrowIf.NullOrEmpty(username);
                
        Age = age;
        Email = email;
        Username = username;
    }
}

2) Check conditions (boolean style)

if (Is.Between(value, min, max)) { /* ... */ }
if (Is.LessThan(a, b)) { /* ... */ }
if (Is.Any(list, x => x > 0)) { /* ... */ }
if (!Is.Between(req.Age, 13, 130))
{
    throw new ArgumentOutOfRangeException(nameof(req.Age), "Age seems invalid.");
}

// Numeric comparisons 
bool inRange = Is.Between(value, min, max); 
bool isLess = Is.LessThan(a, b); 
bool isGreaterOrEqual = Is.GreaterThanOrEqual(a, b);
bool before = Is.LessThan("straße", "strasse", StringComparison.InvariantCulture); // culture-aware

// Collections 
bool anyPositive = Is.Any(numbers, n => n > 0); 
bool allNonNull = Is.All(items, it => it is not null);

// Strings (culture/ordinal aware)
bool lessOrdinal = Is.LessThan("apple", "banana", StringComparison.Ordinal);
bool lessIgnoreCase = Is.LessThan("Apple", "banana", StringComparison.OrdinalIgnoreCase)

3) Callbacks (side effects on success/failure)

// ThrowIf: run side effects on the outcome
ThrowIf.LessThan(1, 2, SGuardCallbacks.OnFailure(() => logger.LogWarning("a < b failed")));
ThrowIf.LessThan(5, 2, SGuardCallbacks.OnSuccess(() => logger.LogInformation("a >= b OK")));

// Is: outcome maps to the boolean result (true=Success, false=Failure)
bool ok = Is.Between(5, 1, 10, SGuardCallbacks.OnSuccess(() => metrics.Increment("is.between.true")));

4) Custom exceptions

ThrowIf.LessThanOrEqual(a, b, new MyCustomException("Invalid!"));
ThrowIf.Between<string, string, string, MyCustomException>(value, min, max, new MyCustomException("Out of range!"));

// Throw using your own exception type
ThrowIf.Any(items, i => i is null, new DomainValidationException("Collection contains null item(s)."));

// Another example with range validation
ThrowIf.LessThanOrEqual(quantity, 0, new DomainValidationException("Quantity must be greater than zero."));

5) String comparisons (culture/ordinal aware)

// Ordinal comparisons
bool before = Is.LessThan("apple", "banana", StringComparison.Ordinal);

// Throw if the ordering violates your rule
ThrowIf.GreaterThan("zebra", "apple", StringComparison.Ordinal); // throws (zebra > apple)

6) Notes

  • Between is inclusive (min and max are allowed).
  • ThrowIf invokes callbacks with Failure when it’s about to throw, Success when it passes.
  • Is.* invokes callbacks with Success when the result is true, Failure when false.
  • Callback exceptions are swallowed (they won’t break your validation flow).

Callbacks – When do they run?

  • ThrowIf methods:
    • Outcome = Failure β†’ the guard is about to throw (callback runs just before the exception propagates).
    • Outcome = Success β†’ the guard passes (no exception is thrown).
    • If the API fails due to invalid arguments (e.g., null selector or null exception instance), the callback is NOT invoked.
Examples:
// Failure β†’ throws β†’ OnFailure runs
ThrowIf.LessThan(1, 2, SGuardCallbacks.OnFailure(() => logger.LogWarning("a < b failed")));

// Success β†’ no throw β†’ OnSuccess runs
ThrowIf.LessThan(5, 2, SGuardCallbacks.OnSuccess(() => logger.LogInformation("a >= b OK")));
  • Is methods:
    • Return a boolean and never throw for the check itself.
    • Outcome = Success when the result is true, Outcome = Failure when the result is false.
Examples
// True β†’ OnSuccess runs
bool inRange = Is.Between(5, 1, 10, SGuardCallbacks.OnSuccess(() => metrics.Increment("is.between.true")));

// False β†’ OnFailure runs
bool isLess = Is.LessThan(5, 2, SGuardCallbacks.OnFailure(() => metrics.Increment("is.lt.false")));
Combine callbacks (Success + Failure)
var onFailure = SGuardCallbacks.OnFailure(() => notifier.Notify("Validation failed"));
var onSuccess = SGuardCallbacks.OnSuccess(() => notifier.Notify("Validation passed"));
SGuardCallback combined = onFailure + onSuccess;

// If inside range -> throws -> Failure -> only onFailure runs
// If outside range -> no throw -> Success -> only onSuccess runs
ThrowIf.Between(value, min, max, combined);

Note: The callback is invoked regardless of the outcome of the guard.

// Passing a null exception instance causes an immediate ArgumentNullException.
// The callback is NOT invoked in this case (no Success/Failure outcome is produced).
try
{
    ThrowIf.Between<int, int, int, InvalidOperationException>(
        5, 1, 10,
        (InvalidOperationException)null!, // invalid argument
        SGuardCallbacks.OnFailure(() => logger.LogError("won't run")));
}
catch (ArgumentNullException)
{
    // expected, and callback not called
}

Inline callback when you need the outcome value directly

GuardOutcome? observed = null;

ThrowIf.LessThan(1, 2, outcome => observed = outcome); // throws -> observed remains null (callback still runs with Failure before exception propagation)

More Examples

Throwing Guards
ThrowIf.NullOrEmpty(str);
ThrowIf.NullOrEmpty(obj, x => x.Property);
ThrowIf.Between(value, min, max); // Throws if value is between min and max
ThrowIf.LessThan(a, b, () => Console.WriteLine("Failed!"));
ThrowIf.Any(list, x => x == null);

// Optionally run a callback on failure (e.g., logging/metrics/cleanup)
ThrowIf.GreaterThan(total, limit, () => logger.LogWarning("Limit exceeded"));

// With selector for nested properties (CallerArgumentExpression helps messages)
ThrowIf.NullOrEmpty(order, o => o.Customer.Name);

πŸ“ Usage Examples (Real-life Scenarios)

public static class CheckoutService 
{ 
    public static void ValidateCart(Cart cart, IReadOnlyDictionary<string, int> stockBySku) 
    { 
        ThrowIf.NullOrEmpty(cart); 
        ThrowIf.NullOrEmpty(cart.Items);
        
        // Every item must have positive quantity
        if (!Is.All(cart.Items, i => i.Quantity > 0))
            throw new ArgumentException("All items must have a positive quantity.", nameof(cart.Items));

        // Check stock levels
        foreach (var item in cart.Items)
        {
            var stock = stockBySku.TryGetValue(item.Sku, out var s) ? s : 0;
            ThrowIf.GreaterThan(item.Quantity, stock, new InvalidOperationException($"Insufficient stock for SKU '{item.Sku}'."));
        }

        // Totals
        ThrowIf.LessThanOrEqual(cart.TotalAmount, 0m, new ArgumentOutOfRangeException(nameof(cart.TotalAmount), "Total must be greater than zero."));
    }
}

public void SaveUser(string username)
{
    var callback = SGuardCallbacks.OnFailure(() =>
        logger.LogWarning("Validation failed: username is required"));

    // When username is null or empty, throw an exception with a custom message and invoke the callback.
    ThrowIf.NullOrEmpty(username, callback);
    
    // Proceed with saving the user...
}

public void UpdateEmail(string email)
{
    var onSuccess = SGuardCallbacks.OnSuccess(() =>
        audit.Record("Email validation succeeded"));

    // If valid, onSuccess is called; if not, an exception is thrown
    ThrowIf.NullOrEmpty(email, onSuccess);

    // Proceed with updating the email...
}


βœ… Test and Coverage Status

Test and Coverage Status

Test Results

Total Passed Failed Skipped
367 367 0 0

Code Coverage

Summary

Generated on: 09/04/2025 - 20:34:01
Coverage date: 09/03/2025 - 19:56:53 - 09/04/2025 - 20:33:59
Parser: MultiReport (11x Cobertura)
Assemblies: 1
Classes: 15
Files: 50
Line coverage: 86.9% (815 of 937)
Covered lines: 815
Uncovered lines: 122
Coverable lines: 937
Total lines: 4360
Branch coverage: 83% (186 of 224)
Covered branches: 186
Total branches: 224
Method coverage: Feature is only available for sponsors
Name Covered Uncovered Coverable Total Line coverage Covered Total Branch coverage
SGuard 815 122 937 4360 86.9% 186 224 83%
SGuard.ExceptionActivator 15 0 15 56 100% 6 8 75%
SGuard.Exceptions.AllException 0 4 4 44 0% 0 0
SGuard.Exceptions.AnyException 2 2 4 36 50% 0 0
SGuard.Exceptions.BetweenException 23 6 29 135 79.3% 0 0
SGuard.Exceptions.GreaterThanException 17 6 23 108 73.9% 0 0
SGuard.Exceptions.GreaterThanOrEqualException 17 6 23 110 73.9% 0 0
SGuard.Exceptions.LessThanException 15 6 21 111 71.4% 0 0
SGuard.Exceptions.LessThanOrEqualException 15 6 21 111 71.4% 0 0
SGuard.Exceptions.NullOrEmptyException 15 4 19 98 78.9% 0 0
SGuard.Is 181 0 181 1100 100% 22 24 91.6%
SGuard.SGuard 31 1 32 124 96.8% 12 12 100%
SGuard.SGuardCallbacks 2 2 4 68 50% 0 0
SGuard.Throw 21 0 21 243 100% 0 0
SGuard.ThrowIf 311 19 330 1558 94.2% 52 56 92.8%
SGuard.Visitor.NullOrEmptyVisitor 150 60 210 458 71.4% 94 124 75.8%

πŸ”’ Versioning

This project follows Semantic Versioning. As of this release, versioning restarts at 0.1.0. If you previously consumed older versions, please upgrade to the latest package.

🀝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

🌐 Code of Conduct

This project adheres to the .NET Foundation Code of Conduct. By participating, you are expected to uphold this code.

πŸ“œ License

This project is licensed under the MIT License, a permissive open source license. See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.1 61 9/5/2025

# Release Notes - Version 0.1.0

           This release restarts the package versioning at 0.1.0. Previous NuGet versions have been unlisted/removed.
           The codebase remains functionally compatible with the current API surface; consumers adopting SGuard from
           this version should not require code changes.

           ## Highlights
           - Fresh start at v0.1.0 under the **MIT License**
           - Targets: **.NET 6 / 7 / 8 / 9**
           - README, LICENSE, and package icon included in the NuGet package

           ## Features
           - Guard APIs:
             - Boolean checks via `Is.*`
             - Throwing guards via `ThrowIf.*` with CallerArgumentExpression for rich diagnostics
           - Unified model with `GuardCallback` and `GuardOutcome` for consistent outcomes and callbacks
           - Comparisons:
             - Generic comparisons for `IComparable<T>`
             - String operations with `StringComparison` support and culture-aware scenarios
             - Range helpers (Between, LessThan, LessThanOrEqual, etc.)
           - Collections and predicates:
             - `Any<T>` and `All<T>` with predicate support
             - Null/empty checks with selector support and expression caching for performance
           - Exceptions:
             - Multiple overloads for custom exception types
             - Parameterless and parameterized constructors supported
             - Generic creation via `ExceptionActivator.Create<T>`

           ## Notes
           - If you were using earlier package versions, please migrate to `0.1.0`. No runtime breaking changes are expected.