Validation Rules API Reference
🎯 Complete API documentation for the Validation Rules Framework
The Validation Rules Engine provides a powerful, fluent way to define and execute validation rules that integrate seamlessly with the Result pattern.
- Core Classes
- ValidatorRulesBuilder
- IValidatorRule Interface
- Built-in Rule Types
- ValidationResult
- Extension Methods
- Examples
Represents the result of a validation operation.
public class ValidationResult<T> : IResultResponse<T>
{
public bool IsSuccess { get; }
public bool IsFailure { get; }
public bool IsValid { get; }
public T? Value { get; }
public IReadOnlyList<IReason> Reasons { get; }
public IReadOnlyList<IError> ValidationErrors { get; }
// Factory methods
public static ValidationResult<T> Success(T value);
public static ValidationResult<T> Failure(params IReason[] errors);
public static ValidationResult<T> Failure(IEnumerable<IReason> errors);
}
Properties:
- IsValid - true if validation passed, false if failed
- ValidationErrors - Collection of validation errors (convenience property)
- Inherits all standard Result
The main entry point for building validation rules using a fluent API.
public class ValidatorRuleBuilder<T>
{
public ValidatorRuleBuilder<T> Rule<TProperty>(Expression<Func<T, TProperty>> propertySelector, string ruleName, string errorMessage, Func<TProperty, bool> validator);
public ValidatorRuleBuilder<T> RuleAsync<TProperty>(Expression<Func<T, TProperty>> propertySelector, string ruleName, string errorMessage, Func<TProperty, Task<bool>> validator);
public ValidatorRuleBuilder<T> AddRule(IValidatorRule<T> rule);
public ValidatorRuleSet<T> Build();
}
Fluent builder for creating validation rule sets.
public class ValidatorRuleSetBuilder<T>
{
// Basic validation rules
public ValidatorRuleSetBuilder<T> Required(string? message = null);
public ValidatorRuleSetBuilder<T> Must<TProperty>(Func<TProperty, bool> predicate, string message);
public ValidatorRuleSetBuilder<T> Must<TProperty>(Func<TProperty, bool> predicate, Error error);
// Async validation rules
public ValidatorRuleSetBuilder<T> MustAsync<TProperty>(Func<TProperty, Task<bool>> predicate, string message);
public ValidatorRuleSetBuilder<T> MustAsync<TProperty>(Func<TProperty, Task<bool>> predicate, Error error);
// Conditional validation
public ValidatorRuleSetBuilder<T> MustWhen<TProperty>(
Func<T, bool> condition,
Func<TProperty, bool> predicate,
string message);
public ValidatorRuleSetBuilder<T> MustWhenAsync<TProperty>(
Func<T, bool> condition,
Func<TProperty, Task<bool>> predicate,
string message);
// Custom rules
public ValidatorRuleSetBuilder<T> AddRule<TProperty>(IValidatorRule<TProperty> rule);
// Build validator
public ValidatorRuleSet<T> Build();
}
Base interface for all validation rules.
public interface IValidatorRule<T>
{
Task<IResultResponse<T>> ValidateAsync(T value);
}
Rule that validates using a predicate function.
public class PredicateValidatorRule<T> : IValidatorRule<T>
{
// Synchronous predicate
public static PredicateValidatorRule<T> Create(Func<T, bool> predicate, string message);
public static PredicateValidatorRule<T> Create(Func<T, bool> predicate, Error error);
// Asynchronous predicate
public static PredicateValidatorRule<T> CreateAsync(Func<T, Task<bool>> predicate, string message);
public static PredicateValidatorRule<T> CreateAsync(Func<T, Task<bool>> predicate, Error error);
}
Specialized rule for async validation scenarios.
public class AsyncPredicateValidatorRule<T> : IValidatorRule<T>
{
public AsyncPredicateValidatorRule(Func<T, Task<bool>> validator, Error error);
public Task<IResultResponse<T>> ValidateAsync(T value);
}
Rule that validates that a value is not null or empty.
public class RequiredValidatorRule<T> : IValidatorRule<T>
{
public RequiredValidatorRule(string? message = null);
public Task<IResultResponse<T>> ValidateAsync(T value);
}
Container for a collection of validation rules.
public class ValidatorRuleSet<T> : IValidatorRule<T>
{
// Properties
public IReadOnlyList<IValidatorRule<T>> Rules { get; }
// Validation methods
public Task<IResultResponse<T>> ValidateAsync(T value);
public ValidationResult<T> Validate(T value);
// Combination
public ValidatorRuleSet<T> Combine(ValidatorRuleSet<T> other);
}
public static class ValidationExtensions
{
// Validate a value and return Result<T>
public static Result<T> Validate<T>(this T value, ValidatorRuleSet<T> validator);
// Async validation
public static Task<Result<T>> ValidateAsync<T>(this T value, ValidatorRuleSet<T> validator);
// Validate Result<T> content
public static Result<T> Validate<T>(this Result<T> result, ValidatorRuleSet<T> validator);
public static Task<Result<T>> ValidateAsync<T>(this Result<T> result, ValidatorRuleSet<T> validator);
}
public static class ResultValidationExtensions
{
// Ensure conditions on Result<T>
public static Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, Error error);
public static Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, string errorMessage);
// Async versions
public static async Task<Result<T>> EnsureAsync<T>(this Task<Result<T>> resultTask, Func<T, bool> predicate, Error error);
public static async Task<Result<T>> EnsureAsync<T>(this Task<Result<T>> resultTask, Func<T, bool> predicate, string errorMessage);
public static async Task<Result<T>> EnsureAsync<T>(this Result<T> result, Func<T, Task<bool>> predicate, Error error);
public static async Task<Result<T>> EnsureAsync<T>(this Result<T> result, Func<T, Task<bool>> predicate, string errorMessage);
}
using REslava.Result;
// Create a simple validator
var emailValidator = new ValidatorRuleBuilder<string>()
.Rule(email => email, "Required", "Email is required", email => !string.IsNullOrEmpty(email))
.Rule(email => email, "Format", "Invalid email format", email => email.Contains("@"))
.Build();
// Validate
var result = emailValidator.Validate("user@example.com");
if (result.IsSuccess)
{
Console.WriteLine($"Valid email: {result.Value}");
}
else
{
foreach (var error in result.ValidationErrors)
{
Console.WriteLine($"Error: {error.Message}");
}
}
Complex Object Validation
public class User
{
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public int Age { get; set; }
}
// Create comprehensive user validator
var userValidator = ValidatorRulesBuilder<User>
.For(u => u.FirstName)
.Required("First name is required")
.Must(name => name.Length >= 2, "First name must be at least 2 characters")
.For(u => u.LastName)
.Required("Last name is required")
.Must(name => name.Length >= 2, "Last name must be at least 2 characters")
.For(u => u.Email)
.Required("Email is required")
.Must(e => e.Contains("@"), "Invalid email format")
.MustAsync(async email => !await EmailExistsAsync(email), "Email already exists")
.For(u => u.Age)
.Must(age => age >= 18, "Must be 18 or older")
.Must(age => age <= 120, "Invalid age")
.Build();
// Validate user
var user = new User { FirstName = "John", LastName = "Doe", Email = "john@example.com", Age = 25 };
var validationResult = await userValidator.ValidateAsync(user);
Custom Rules
// Create reusable custom rules
var strongPasswordRule = PredicateValidatorRule<string>.Create(
password => password.Length >= 8
&& char.IsUpper(password[0])
&& char.IsDigit(password[^1]),
"Password must be 8+ chars, start with uppercase, end with digit");
var uniqueUsernameRule = PredicateValidatorRule<string>.CreateAsync(
async username => !await UsernameExistsAsync(username),
"Username already taken");
// Use custom rules
var userValidator = ValidatorRulesBuilder<User>
.For(u => u.Password)
.AddRule(strongPasswordRule)
.For(u => u.Username)
.AddRule(uniqueUsernameRule)
.Build();
Conditional Validation
var orderValidator = ValidatorRulesBuilder<Order>
.For(o => o.Total)
.Must(total => total > 0, "Order total must be positive")
.For(o => o.PaymentMethod)
.Required("Payment method required")
.For(o => o.CreditCard)
.MustWhen(
order => order.PaymentMethod == PaymentMethod.CreditCard,
card => card != null && card.IsValid(),
"Valid credit card required for credit card payments")
.For(o => o.BillingAddress)
.MustWhen(
order => order.PaymentMethod != PaymentMethod.Cash,
address => address != null,
"Billing address required for non-cash payments")
.Build();
Combining Validators
// Create specialized validators
var personalInfoValidator = ValidatorRulesBuilder<User>
.For(u => u.FirstName).Required("First name required")
.For(u => u.LastName).Required("Last name required")
.For(u => u.Email).Must(e => e.Contains("@"), "Invalid email")
.Build();
var securityValidator = ValidatorRulesBuilder<User>
.For(u => u.Password).Must(p => p.Length >= 8, "Password too short")
.For(u => u.SecurityQuestion).Required("Security question required")
.Build();
// Combine validators
var completeValidator = personalInfoValidator.Combine(securityValidator);
var result = await completeValidator.ValidateAsync(user);
Integration with Result Pattern
public async Task<Result<User>> RegisterUserAsync(UserRegistrationDto dto)
{
// Validate input using validation rules
var validationResult = await userValidator.ValidateAsync(dto);
if (validationResult.IsFailure)
return Result<User>.Fail(validationResult.Errors);
// Create user
var user = new User(dto.Email, dto.Name);
// Save to database
var saveResult = await userRepository.SaveAsync(user);
if (saveResult.IsFailure)
return saveResult;
return Result<User>.Ok(user);
}
// Using Ensure extension methods
public async Task<Result<User>> UpdateUserAsync(User user)
{
return await Result<User>.Ok(user)
.EnsureAsync(u => u.Email.Contains("@"), "Invalid email format")
.EnsureAsync(async u => !await EmailExistsAsync(u.Email), "Email already exists")
.TapAsync(async u => await userRepository.UpdateAsync(u));
}
- Use Specific Error Messages - Provide clear, actionable feedback
- Create Reusable Rules - Build libraries of common validation rules
- Use Async Wisely - Only use async validation when needed (database/API calls)
- Test Rules Separately - Unit test validation rules in isolation
- Document Complex Rules - Add comments for business logic validation
- Combine Validators - Build complex validation from simple, focused validators
Validation rules automatically handle exceptions and convert them to ExceptionError:
var validator = ValidatorRulesBuilder<string>
.For(s => s)
.Must(value => {
if (value == "throw")
throw new InvalidOperationException("Test exception");
return true;
}, "This should not be reached")
.Build();
var result = validator.Validate("throw");
// result.IsFailure will be true
// result.Errors[0] will be an ExceptionError
All validation rules are thread-safe and can be reused across multiple validation operations. Validators are immutable after creation.