Noundry.Guardian

Lightweight guard clause library for defensive programming and parameter validation. Protect method entry points with a fluent Guard.Against.* API that throws appropriate exceptions on validation failure.

.NET 6.0+ .NET Standard 2.0+ MIT License
# Install via NuGet
$ dotnet add package Noundry.Guardian

Quick Start

Guardian uses the Guard.Against static property as the entry point. Each method validates its input and returns the validated value on success, enabling assignment chaining.

using Noundry.Guardian;

public class Product
{
 public Guid Id { get; }
 public string Name { get; }
 public decimal Price { get; }

 public Product(Guid id, string name, decimal price)
 {
 Id = Guard.Against.DefaultStruct(id);
 Name = Guard.Against.NullOrWhiteSpace(name);
 Price = Guard.Against.NegativeOrZero(price);
 }
}

Key Features

Return value pattern

Every guard method returns the validated value, enabling direct assignment: Name = Guard.Against.Null(name);

Appropriate exceptions

Throws the correct exception type: ArgumentNullException, ArgumentException, or ArgumentOutOfRangeException.

Auto parameter names

Uses [CallerArgumentExpression] to automatically capture the parameter name in exception messages.

Wide compatibility

Targets .NET 6/7/8/9/10 and .NET Standard 2.0/2.1 with polyfills for older frameworks.

Null Validation

Guard.Against.Null<T>(value)

Validates that a reference type or nullable value type is not null. Throws ArgumentNullException. For nullable structs, returns the unwrapped value.

// Reference type
string name = Guard.Against.Null(inputName);

// Nullable struct - returns int, not int?
int value = Guard.Against.Null(nullableInt);

// With custom message
var obj = Guard.Against.Null(input, message: "Input object is required");

String Validation

Guard.Against.NullOrWhiteSpace(value)

Validates string is not null, empty, or whitespace-only. Throws ArgumentException.

string name = Guard.Against.NullOrWhiteSpace(input);

// Fails for: null, "", " ", "\t", "\n"
// Passes for: "a", " text ", "any content"

Guard.Against.NullOrEmpty(value)

Validates string is not null or empty. Whitespace-only strings pass. Throws ArgumentException.

string code = Guard.Against.NullOrEmpty(input);

// Fails for: null, ""
// Passes for: " ", " ", "text"

Guard.Against.InvalidFormat(value, pattern)

Validates string matches a regex pattern. Throws ArgumentException.

// Email validation
string email = Guard.Against.InvalidFormat(
 input,
 @"^[^@\s]+@[^@\s]+\.[^@\s]+$");

// Phone number (10 digits)
string phone = Guard.Against.InvalidFormat(
 input,
 @"^\d{10}$");

// Account number with custom message
string account = Guard.Against.InvalidFormat(
 input,
 @"^[A-Z]{2}\d{6}$",
 message: "Account must be 2 letters followed by 6 digits");

Guard.Against.InvalidLength(value, minLength, maxLength)

Validates string length is within bounds. Throws ArgumentOutOfRangeException.

// Username: 3-20 characters
string username = Guard.Against.InvalidLength(input, 3, 20);

// Fixed length (exactly 8 characters)
string code = Guard.Against.InvalidLength(input, 8, 8);

Numeric Validation

All numeric guards work with any IComparable<T> type: int, long, short, byte, float, double, decimal, and more. They throw ArgumentOutOfRangeException.

Method Passes When Example
Negative(value) value >= 0 0 passes, -1 fails
Zero(value) value != 0 1 passes, 0 fails
NegativeOrZero(value) value > 0 1 passes, 0 fails
Positive(value) value <= 0 0 passes, 1 fails
OutOfRange(value, min, max) min <= value <= max 5 in [1,10] passes
GreaterThan(value, max) value <= max max is upper bound
LessThan(value, min) value >= min min is lower bound
GreaterThanOrEqualTo(value, max) value < max strictly less than
LessThanOrEqualTo(value, min) value > min strictly greater than
int quantity = Guard.Against.Negative(input);
decimal price = Guard.Against.NegativeOrZero(input);
int divisor = Guard.Against.Zero(input);
int age = Guard.Against.OutOfRange(input, 18, 120);
decimal discount = Guard.Against.OutOfRange(percentage, 0m, 100m);
decimal maxPrice = Guard.Against.GreaterThan(input, 999999.99m);
decimal minAmount = Guard.Against.LessThan(input, 0.01m);

Default Value Validation

Prevent default/uninitialized values from propagating through your system. Throws ArgumentException.

// Struct defaults: Guid.Empty, default(DateTime), 0
Guid id = Guard.Against.DefaultStruct(inputId);
DateTime date = Guard.Against.DefaultStruct(inputDate);
int value = Guard.Against.DefaultStruct(input);

// Reference type default (null)
string text = Guard.Against.Default(input);

// Convenience: Guard.Against.Default works for both structs and classes
Guid id2 = Guard.Against.Default(inputId); // Same as DefaultStruct

Enum Validation

Ensure enum values are actually defined members. Catches invalid casts like (OrderStatus)999. Throws ArgumentException.

public enum OrderStatus { Pending, Processing, Shipped, Delivered }

// Valid
OrderStatus status = Guard.Against.NotInEnum(inputStatus);

// Catches invalid casts
var invalid = (OrderStatus)999;
Guard.Against.NotInEnum(invalid); // Throws ArgumentException

Collection Validation

Guard.Against.NullOrEmpty<T>(collection)

Validates collection is not null and has at least one element. Returns the materialized collection.

var items = Guard.Against.NullOrEmpty(inputList);
var ids = Guard.Against.NullOrEmpty(productIds);

// Works with any IEnumerable<T>
var array = Guard.Against.NullOrEmpty(new[] { 1, 2, 3 });
var list = Guard.Against.NullOrEmpty(new List<string> { "a", "b" });

Guard.Against.NotOneOf<T>(value, allowedValues)

Validates a value exists in a set of allowed values.

string fruit = Guard.Against.NotOneOf(
 input,
 new[] { "apple", "banana", "orange" });

string status = Guard.Against.NotOneOf(
 input,
 new[] { "active", "inactive", "pending" },
 message: "Status must be one of: active, inactive, pending");

Condition Validation

Use custom boolean conditions for business rules that do not fit into other guard categories. Throws ArgumentException.

// Basic condition
Guard.Against.Condition(age >= 18, nameof(age), "Must be 18 or older");

// Business rule: sufficient funds
Guard.Against.Condition(
 balance >= withdrawalAmount,
 nameof(withdrawalAmount),
 $"Insufficient funds. Available: {balance}, Requested: {withdrawalAmount}");

// Date range validation
Guard.Against.Condition(
 startDate < endDate,
 "dateRange",
 "Start date must be before end date");

// Uniqueness constraint
Guard.Against.Condition(
 !existingIds.Contains(newId),
 nameof(newId),
 "ID already exists");

Exception Types Reference

Guard Method Exception Type
NullArgumentNullException
NullOrWhiteSpaceArgumentException
NullOrEmpty (string)ArgumentException
NullOrEmpty (collection)ArgumentNullException / ArgumentException
Default, DefaultStructArgumentException
Negative, Zero, NegativeOrZero, PositiveArgumentOutOfRangeException
OutOfRangeArgumentOutOfRangeException
GreaterThan, LessThan, etc.ArgumentOutOfRangeException
InvalidFormatArgumentException
InvalidLengthArgumentOutOfRangeException
NotInEnumArgumentException
NotOneOfArgumentException
ConditionArgumentException

Real-World Patterns

Constructor Validation

public class Product
{
 public Guid Id { get; }
 public string Name { get; }
 public decimal Price { get; }
 public int StockQuantity { get; }

 public Product(Guid id, string name, decimal price, int stockQuantity)
 {
 Id = Guard.Against.DefaultStruct(id);
 Name = Guard.Against.NullOrWhiteSpace(name);
 Name = Guard.Against.InvalidLength(Name, 2, 100);
 Price = Guard.Against.NegativeOrZero(price);
 StockQuantity = Guard.Against.Negative(stockQuantity);
 }
}

Method Parameter Validation

public void ProcessOrder(
 Guid orderId,
 string customerEmail,
 decimal amount,
 List<Guid> productIds)
{
 Guard.Against.DefaultStruct(orderId);
 Guard.Against.InvalidFormat(customerEmail, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
 Guard.Against.OutOfRange(amount, 0.01m, 100000m);
 Guard.Against.NullOrEmpty(productIds);

 // Process order...
}

Controller / API Validation

public class ProductsController : ControllerBase
{
 private readonly IProductService _service;

 public ProductsController(IProductService service)
 {
 _service = Guard.Against.Null(service);
 }

 [HttpGet("{id}")]
 public async Task<ActionResult<Product>> GetById(Guid id)
 {
 try
 {
 Guard.Against.DefaultStruct(id);
 var product = await _service.GetByIdAsync(id);
 return product == null ? NotFound() : Ok(product);
 }
 catch (ArgumentException ex)
 {
 return BadRequest(ex.Message);
 }
 }
}

Business Rule: Bank Transfer

public void Transfer(BankAccount target, decimal amount)
{
 Guard.Against.Null(target);
 Guard.Against.NegativeOrZero(amount);
 Guard.Against.GreaterThan(amount, 1000000m);
 Guard.Against.Condition(
 amount <= Balance,
 nameof(amount),
 "Insufficient funds for transfer");
 Guard.Against.Condition(
 target.Id != Id,
 nameof(target),
 "Cannot transfer to same account");

 Balance -= amount;
 target.Balance += amount;
}

Enum State Machine

public void UpdateStatus(OrderStatus newStatus)
{
 Guard.Against.NotInEnum(newStatus);
 Guard.Against.Condition(
 Status != OrderStatus.Cancelled,
 nameof(newStatus),
 "Cannot update status of cancelled order");

 Status = newStatus;
}

Common Regex Patterns for InvalidFormat

Pattern Regex
Email^[^@\s]+@[^@\s]+\.[^@\s]+$
Phone (10 digits)^\d{10}$
US ZIP Code^\d{5}(-\d{4})?$
URL^https?://[^\s]+$
Alphanumeric^[a-zA-Z0-9]+$
UUID/GUID^[0-9a-fA-F]{8}-...-[0-9a-fA-F]{12}$
Date (YYYY-MM-DD)^\d{4}-\d{2}-\d{2}$
Hex Color^#[0-9a-fA-F]{6}$

Quick Decision Guide

Choose the right guard method for your scenario:

Scenario Guard Method
Reference must not be nullGuard.Against.Null(value)
String must have contentGuard.Against.NullOrWhiteSpace(value)
String must match patternGuard.Against.InvalidFormat(value, pattern)
String length boundsGuard.Against.InvalidLength(value, min, max)
GUID/DateTime not defaultGuard.Against.DefaultStruct(value)
Number must be positiveGuard.Against.NegativeOrZero(value)
Number within rangeGuard.Against.OutOfRange(value, min, max)
Collection must have itemsGuard.Against.NullOrEmpty(collection)
Value in allowed setGuard.Against.NotOneOf(value, allowed)
Enum is definedGuard.Against.NotInEnum(enumValue)
Custom business ruleGuard.Against.Condition(expr, name, msg)

Related Packages