### Install ValiCraft Source: https://github.com/hquinn/valicraft/blob/main/README.md Add the NuGet package to your project via the .NET CLI. ```bash dotnet add package ValiCraft ``` -------------------------------- ### Polymorphic Static Validator Composition Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Example of composing static validators for polymorphic types. Use `Polymorphic` with `WhenType` and `Validate` to apply specific validators based on runtime type. ```csharp builder.Polymorphic(x => x.Payment) .WhenType().Validate() .WhenType().Validate() .Otherwise().Allow(); ``` -------------------------------- ### Build and Test ValiCraft Source: https://github.com/hquinn/valicraft/blob/main/CONTRIBUTING.md Commands for cloning the repository, building the solution, running tests, and executing benchmarks. ```bash # Clone your fork git clone https://github.com/YOUR_USERNAME/ValiCraft.git cd ValiCraft # Build the solution dotnet build # Run all tests dotnet test # Run specific test project dotnet test tests/ValiCraft.Generator.Tests dotnet test tests/ValiCraft.IntegrationTests dotnet test tests/ValiCraft.Tests # Run benchmarks (optional) dotnet run -c Release --project benchmarks/ValiCraft.Benchmarks ``` -------------------------------- ### Create Reusable Extension Methods Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Define extension methods with attributes to enable IntelliSense and default error messages for custom rules. ```csharp using ValiCraft; using ValiCraft.Attributes; using ValiCraft.BuilderTypes; public static class MyRuleExtensions { /// /// Validates that a string is a valid US postal code. /// [DefaultMessage("{TargetName} must be a valid US postal code")] [MapToValidationRule(typeof(MyRules), nameof(MyRules.UsPostalCode))] public static IValidationRuleBuilderType IsUsPostalCode( this IBuilderType builder) where TRequest : notnull { return builder.Is(MyRules.UsPostalCode); } /// /// Validates that a number is divisible by a specified value. /// [DefaultMessage("{TargetName} must be divisible by {Divisor}")] [RulePlaceholder("{Divisor}", "divisor")] [MapToValidationRule(typeof(MyRules), nameof(MyRules.DivisibleBy))] public static IValidationRuleBuilderType IsDivisibleBy( this IBuilderType builder, int divisor) where TRequest : notnull { return builder.Is(MyRules.DivisibleBy, divisor); } } public static class MyRules { public static bool UsPostalCode(string? value) { if (string.IsNullOrEmpty(value)) return false; return Regex.IsMatch(value, @"^\d{5}(-\d{4})?$"); } public static bool DivisibleBy(int value, int divisor) { return divisor != 0 && value % divisor == 0; } } ``` -------------------------------- ### Host-Level Validator Registration in Multi-Project Solutions Source: https://github.com/hquinn/valicraft/blob/main/docs/dependency-injection.md In a multi-project solution, a single `AddValiCraft()` call in the host project registers all validators from the host project and all referenced projects. This relies on compile-time discovery of assembly-level attributes. ```csharp // In MyApp.WebApi (references MyApp.Orders and MyApp.Customers) builder.Services.AddValiCraft(); // Registers validators from all three projects ``` -------------------------------- ### Define a Data Model Source: https://github.com/hquinn/valicraft/blob/main/README.md Create a standard C# class to be validated. ```csharp public class User { public required string Username { get; set; } public string? Email { get; set; } public int Age { get; set; } } ``` -------------------------------- ### Apply advanced collection validation rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Demonstrates custom lambdas, method references, error overrides, and failure modes for collection validation. ```csharp // Custom lambda builder.EnsureEach(p => p.Tags) .Is(t => !string.IsNullOrEmpty(t)); // Method reference builder.EnsureEach(p => p.Amounts) .Is(Rules.GreaterThan, 0M); // Error overrides builder.EnsureEach(p => p.Tags) .IsNotNullOrWhiteSpace() .WithMessage("Tag must not be empty") .WithErrorCode("TagRequired") .WithSeverity(ErrorSeverity.Warning); // Halt on first failure builder.EnsureEach(p => p.Tags, OnFailureMode.Halt) .IsNotNullOrWhiteSpace() .HasMinLength(3); ``` -------------------------------- ### Usage of Async User Validator Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Instantiate and use an async validator to validate a user object. Ensure the repository dependency is provided during instantiation. ```csharp // Usage var validator = new UserValidator(repository); ValidationErrors? result = await validator.ValidateAsync(user, cancellationToken); ``` -------------------------------- ### Register Validators in Multi-Project Solutions Source: https://context7.com/hquinn/valicraft/llms.txt In multi-project solutions, validators are discovered across all referenced projects automatically. Use AddValiCraft() to register all validators or AddMyAppOrdersValiCraft() and AddMyAppCustomersValiCraft() to register modules independently. ```csharp // In MyApp.WebApi (references MyApp.Orders and MyApp.Customers) var builder = WebApplication.CreateBuilder(args); // Registers validators from all three projects builder.Services.AddValiCraft(); // Or register modules independently builder.Services.AddMyAppOrdersValiCraft(); // Only Orders module builder.Services.AddMyAppCustomersValiCraft(); // Only Customers module var app = builder.Build(); ``` -------------------------------- ### Reference Methods for Validation Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Use static methods or class members as validation predicates to keep logic clean and reusable. ```csharp public static class ValidationHelpers { public static bool IsValidLuhn(string? value) { // Luhn algorithm implementation if (string.IsNullOrEmpty(value)) return false; // ... validation logic return true; } } // In validator builder.Ensure(x => x.CreditCardNumber) .Is(ValidationHelpers.IsValidLuhn) .WithMessage("Invalid credit card number"); ``` ```csharp builder.Ensure(x => x.OrderDate) .Is(BusinessRules.Orders.IsValidOrderDate); ``` -------------------------------- ### Compose Static Validators for Customer Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Demonstrates composing static validators within another static validator. Use `Validate()` to delegate validation without instantiation. ```csharp [GenerateValidator] public partial class CustomerValidator : StaticValidator { protected override void DefineRules(IValidationRuleBuilder builder) { // Delegate to another static validator - no instance needed! builder.Ensure(x => x.BillingAddress) .Validate(); builder.EnsureEach(x => x.ShippingAddresses) .Validate(); } } ``` -------------------------------- ### Run Validation and Handle Results in C# Source: https://github.com/hquinn/valicraft/blob/main/docs/core-concepts.md Execute validation on a model and check the result. A null result indicates a valid model; otherwise, process the errors. ```csharp var result = validator.Validate(user); if (result is null) { // Model is valid ProcessUser(user); } else { // result.Errors contains the list of validation errors HandleErrors(result); } ``` -------------------------------- ### Numeric Comparison Rules in C# Source: https://context7.com/hquinn/valicraft/llms.txt Define rules for greater/less than, range, sign, and equality checks for numeric types. Ensure values fall within specified bounds or match/differ from expected values. ```csharp [GenerateValidator] public partial class ProductValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Greater/less than comparisons builder.Ensure(x => x.Price) .IsGreaterThan(0m) .IsLessThan(10000m); builder.Ensure(x => x.Quantity) .IsGreaterOrEqualThan(1) .IsLessOrEqualThan(999); // Range validation (inclusive) builder.Ensure(x => x.Rating) .IsBetween(1, 5); // Range validation (exclusive) builder.Ensure(x => x.Discount) .IsBetweenExclusive(0m, 1m); // Sign validation builder.Ensure(x => x.Stock) .IsPositiveOrZero(); builder.Ensure(x => x.Balance) .IsNegativeOrZero(); // Equality checks builder.Ensure(x => x.Version) .IsEqual(2); builder.Ensure(x => x.Status) .IsNotEqual(0); builder.Ensure(x => x.Id) .IsNotDefault(); } } ``` -------------------------------- ### Apply Parameterized Is() Rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Pass additional arguments to validation rules to enable dynamic comparison logic. ```csharp // Using built-in rule with parameter builder.Ensure(x => x.Quantity) .Is(Rules.GreaterThan, 0); // Same as .IsGreaterThan(0) // Custom rule with parameter public static bool IsDivisibleBy(int value, int divisor) => divisor != 0 && value % divisor == 0; builder.Ensure(x => x.Quantity) .Is(IsDivisibleBy, 10) .WithMessage("Quantity must be divisible by 10"); ``` ```csharp // Two parameters public static bool IsBetween(int value, int min, int max) => value >= min && value <= max; builder.Ensure(x => x.Age) .Is(IsBetween, 18, 120) .WithMessage("Age must be between 18 and 120"); // Three parameters public static bool IsValidDate(DateTime value, int minYear, int maxYear, bool allowWeekends) { if (value.Year < minYear || value.Year > maxYear) return false; if (!allowWeekends && (value.DayOfWeek == DayOfWeek.Saturday || value.DayOfWeek == DayOfWeek.Sunday)) return false; return true; } builder.Ensure(x => x.AppointmentDate) .Is(IsValidDate, 2020, 2030, false) .WithMessage("Date must be a weekday between 2020 and 2030"); ``` -------------------------------- ### Direct Rules on Collection Items Source: https://context7.com/hquinn/valicraft/llms.txt Validate each item in a collection of simple types by chaining rules directly on EnsureEach. Rules applied directly to collection items will include the item's index in the error path. ```csharp builder.EnsureEach(x => x.Tags) .IsNotNullOrWhiteSpace() .HasMinLength(3); // With custom predicates builder.EnsureEach(x => x.Amounts) .Is(Rules.GreaterThan, 0M); // With error overrides builder.EnsureEach(x => x.ProductCodes) .IsNotNullOrWhiteSpace() .WithMessage("Product code must not be empty") .WithErrorCode("ProductCodeRequired") .WithSeverity(ErrorSeverity.Warning); // With halt on first failure builder.EnsureEach(x => x.RequiredTags, OnFailureMode.Halt) .IsNotNullOrWhiteSpace() .HasMinLength(2); ``` -------------------------------- ### Extension Method Pattern Structure Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Standard template for creating custom validation extension methods. ```csharp [DefaultMessage("...")] // Optional: default error message [RulePlaceholder("...", "...")] // Optional: custom placeholders (can have multiple) [MapToValidationRule(typeof(RulesClass), nameof(RulesClass.MethodName))] // Required public static IValidationRuleBuilderType RuleName( this IBuilderType builder, /* optional parameters */) where TRequest : notnull { return builder.Is(RulesClass.MethodName /* , parameters */); } ``` -------------------------------- ### Define Static Address Validator Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Create a static validator for address data without requiring dependency injection. Rules are defined in `DefineRules` using `StaticValidator`. ```csharp [GenerateValidator] public partial class AddressValidator : StaticValidator
{ protected override void DefineRules(IValidationRuleBuilder
builder) { builder.Ensure(x => x.Street) .IsNotNullOrWhiteSpace(); builder.Ensure(x => x.City) .IsNotNullOrWhiteSpace(); builder.Ensure(x => x.PostalCode) .Matches(@"^\d{5}$"); } } ``` -------------------------------- ### Apply String Validation Rules Source: https://context7.com/hquinn/valicraft/llms.txt Utilize built-in string validation methods for length, format, and pattern matching. ```csharp [GenerateValidator] public partial class ProfileValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Not null/empty/whitespace checks builder.Ensure(x => x.DisplayName) .IsNotNullOrWhiteSpace(); // Length constraints builder.Ensure(x => x.Bio) .HasMinLength(10) .HasMaxLength(500); builder.Ensure(x => x.Handle) .HasLengthBetween(3, 20); // Format validation builder.Ensure(x => x.Email) .IsEmailAddress(); builder.Ensure(x => x.Website) .IsUrl(); builder.Ensure(x => x.AlphanumericCode) .IsAlphaNumeric(); // String content checks builder.Ensure(x => x.Slug) .StartsWith("post-") .EndsWith("-2024"); builder.Ensure(x => x.Description) .Contains("keyword"); // Regex pattern matching builder.Ensure(x => x.PhoneNumber) .Matches(@"^\+?[1-9]\d{1,14}$"); } } ``` -------------------------------- ### Customize Error Messages with Placeholders Source: https://context7.com/hquinn/valicraft/llms.txt Override default error messages using WithMessage() and utilize placeholders or .NET format specifiers. ```csharp [GenerateValidator] public partial class OrderValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Custom message with placeholders builder.Ensure(x => x.Age) .IsGreaterOrEqualThan(18) .WithMessage("You must be at least {ValueToCompare} years old to register"); // Format specifiers for values builder.Ensure(x => x.Price) .IsGreaterThan(0) .WithMessage("Price must be positive. Got: {TargetValue:C}"); // Currency format builder.Ensure(x => x.Percentage) .IsBetween(0, 100) .WithMessage("Value {TargetValue:F2} is outside range {Min}-{Max}"); // Available placeholders: // {TargetName} - Property name (humanized: "firstName" -> "First Name") // {TargetValue} - The actual value being validated // {ValueToCompare} - Comparison value (for comparison rules) // Rule-specific placeholders - {Min}, {Max}, {MinLength}, etc. } } ``` -------------------------------- ### Create a Validator Source: https://github.com/hquinn/valicraft/blob/main/README.md Define validation rules using the fluent builder pattern within a partial class marked with [GenerateValidator]. ```csharp using ValiCraft; using ValiCraft.Attributes; [GenerateValidator] public partial class UserValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.Username) .IsNotNullOrWhiteSpace() .HasMinLength(3) .HasMaxLength(50); builder.Ensure(x => x.Email) .IsNotNullOrWhiteSpace() .IsEmailAddress(); builder.Ensure(x => x.Age) .IsGreaterOrEqualThan(0) .IsLessThan(150); } } ``` ```csharp [GenerateValidator(IncludeDefaultMetadata = true)] public partial class UserValidator : Validator { // ... } ``` -------------------------------- ### Define Basic Is() Validation Rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Use inline lambdas, regex, or block logic to define custom validation rules for properties. ```csharp builder.Ensure(x => x.PostalCode) .Is(code => code != null && code.Length == 5) .WithMessage("Postal code must be exactly 5 characters"); ``` ```csharp builder.Ensure(x => x.Email) .Is(email => Regex.IsMatch(email ?? "", @"^[\w.-]+@[\w.-]+\.\w+$")) .WithMessage("Invalid email format"); ``` ```csharp builder.Ensure(x => x.Password) .Is(password => { if (string.IsNullOrEmpty(password)) return false; if (password.Length < 8) return false; if (!password.Any(char.IsUpper)) return false; if (!password.Any(char.IsDigit)) return false; return true; }) .WithMessage("Password must be at least 8 characters with uppercase and digit"); ``` -------------------------------- ### Inject nested validators via constructor Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Demonstrates constructor injection of validators for improved testability. ```csharp [GenerateValidator] public partial class OrderValidator(IValidator
addressValidator) : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.ShippingAddress) .ValidateWith(addressValidator); } } ``` -------------------------------- ### Create Custom Validation Rule Extensions Source: https://context7.com/hquinn/valicraft/llms.txt Define reusable validation rules using extension methods decorated with MapToValidationRule and DefaultMessage attributes. ```csharp using ValiCraft; using ValiCraft.Attributes; using ValiCraft.BuilderTypes; using System.Text.RegularExpressions; public static class CustomRules { public static bool UsPostalCode(string? value) { if (string.IsNullOrEmpty(value)) return false; return Regex.IsMatch(value, @"^\d{5}(-\d{4})?$"); } public static bool DivisibleBy(int value, int divisor) { return divisor != 0 && value % divisor == 0; } } public static class CustomRuleExtensions { [DefaultMessage("{TargetName} must be a valid US postal code")] [MapToValidationRule(typeof(CustomRules), nameof(CustomRules.UsPostalCode))] public static IValidationRuleBuilderType IsUsPostalCode( this IBuilderType builder) where TRequest : notnull { return builder.Is(CustomRules.UsPostalCode); } [DefaultMessage("{TargetName} must be divisible by {Divisor}")] [RulePlaceholder("{Divisor}", "divisor")] [MapToValidationRule(typeof(CustomRules), nameof(CustomRules.DivisibleBy))] public static IValidationRuleBuilderType IsDivisibleBy( this IBuilderType builder, int divisor) where TRequest : notnull { return builder.Is(CustomRules.DivisibleBy, divisor); } } // Usage in validator [GenerateValidator] public partial class AddressValidator : Validator
{ protected override void DefineRules(IValidationRuleBuilder
builder) { builder.Ensure(x => x.ZipCode) .IsUsPostalCode(); // Uses default message builder.Ensure(x => x.UnitNumber) .IsDivisibleBy(100) .WithMessage("Unit must be in increments of {Divisor}"); } } ``` -------------------------------- ### Chain Error Customizations Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Combine multiple error customization methods into a single fluent chain. ```csharp builder.Ensure(x => x.OrderTotal) .IsGreaterThan(0) .WithMessage("Order total must be positive") .WithErrorCode("INVALID_ORDER_TOTAL") .WithTargetName("Order Total") .WithSeverity(ErrorSeverity.Error) .WithMetadata("Category", "Financial"); ``` -------------------------------- ### Define Async User Validator Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Implement an asynchronous validator for user data, including checks against a repository. Requires implementing `AsyncValidator` and defining rules in `DefineRules`. ```csharp [GenerateValidator] public partial class UserValidator : AsyncValidator { private readonly IUserRepository _repository; public UserValidator(IUserRepository repository) { _repository = repository; } protected override void DefineRules(IAsyncValidationRuleBuilder builder) { builder.Ensure(x => x.Username) .IsNotNullOrWhiteSpace() .HasMinLength(3) .Is(async (username, ct) => await _repository.IsUsernameAvailableAsync(username, ct)) .WithMessage("Username '{TargetValue}' is already taken"); } } ``` -------------------------------- ### Format Error Message Placeholders Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Apply standard .NET format specifiers to placeholders within error messages. ```csharp builder.Ensure(x => x.Price) .IsGreaterThan(0) .WithMessage("Price must be positive. Got: {TargetValue:C}"); // Currency format builder.Ensure(x => x.Percentage) .IsBetween(0, 100) .WithMessage("Value {TargetValue:F2} is outside the valid range"); // 2 decimal places ``` -------------------------------- ### Implement Async Custom Rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Use the async overload of the Is method for asynchronous validation logic. ```csharp builder.Ensure(x => x.Username) .Is(async (username, ct) => await IsUsernameAvailableAsync(username, ct)) .WithMessage("Username is already taken"); ``` -------------------------------- ### Implement Async Is() Rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Use asynchronous predicates in AsyncValidator classes to perform I/O-bound validation, such as database checks. ```csharp [GenerateValidator] public partial class UserValidator : AsyncValidator { private readonly IUserRepository _repository; public UserValidator(IUserRepository repository) { _repository = repository; } protected override void DefineRules(IAsyncValidationRuleBuilder builder) { // Async lambda builder.Ensure(x => x.Username) .Is(async (username, ct) => await _repository.IsUsernameAvailableAsync(username, ct)) .WithMessage("Username is already taken"); // Async with parameter builder.Ensure(x => x.Email) .Is(async (email, domain, ct) => await _repository.IsEmailAllowedAsync(email, domain, ct), "example.com") .WithMessage("Email must be from the example.com domain"); } } ``` -------------------------------- ### Implement Custom Predicate Validation with Is() Source: https://context7.com/hquinn/valicraft/llms.txt Apply custom validation logic using inline lambdas, block lambdas, method references, or parameterized rules. ```csharp [GenerateValidator] public partial class CustomValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Inline lambda expression builder.Ensure(x => x.PostalCode) .Is(code => code != null && code.Length == 5) .WithMessage("Postal code must be exactly 5 characters"); // Block lambda for complex logic builder.Ensure(x => x.Password) .Is(password => { if (string.IsNullOrEmpty(password)) return false; if (password.Length < 8) return false; if (!password.Any(char.IsUpper)) return false; if (!password.Any(char.IsDigit)) return false; return true; }) .WithMessage("Password must be 8+ chars with uppercase and digit"); // Method reference builder.Ensure(x => x.CreditCardNumber) .Is(ValidationHelpers.IsValidLuhn) .WithMessage("Invalid credit card number"); // Using built-in Rules class with parameters builder.Ensure(x => x.Quantity) .Is(Rules.GreaterThan, 0); // Custom method with parameter builder.Ensure(x => x.OrderQuantity) .Is(IsDivisibleBy, 10) .WithMessage("Quantity must be divisible by 10"); } private static bool IsDivisibleBy(int value, int divisor) => divisor != 0 && value % divisor == 0; } public static class ValidationHelpers { public static bool IsValidLuhn(string? value) { if (string.IsNullOrEmpty(value)) return false; // Luhn algorithm implementation... return true; } } ``` -------------------------------- ### Compose Static Validators in C# Source: https://context7.com/hquinn/valicraft/llms.txt Use StaticValidator to define rules and compose validators without requiring instantiation. ```csharp [GenerateValidator] public partial class AddressValidator : StaticValidator
{ protected override void DefineRules(IValidationRuleBuilder
builder) { builder.Ensure(x => x.Street).IsNotNullOrWhiteSpace(); builder.Ensure(x => x.City).IsNotNullOrWhiteSpace(); } } [GenerateValidator] public partial class CustomerValidator : StaticValidator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.Name) .IsNotNullOrWhiteSpace(); // Compose with another static validator - no instance needed builder.Ensure(x => x.BillingAddress) .Validate(); builder.EnsureEach(x => x.ShippingAddresses) .Validate(); } } // Works with polymorphic validation too builder.Polymorphic(x => x.Payment) .WhenType().Validate() .WhenType().Validate() .Otherwise().Allow(); ``` -------------------------------- ### Define Static Validation Rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Reference a static method returning bool where the first parameter is the value being validated. ```csharp public static class CustomRules { public static bool IsValidPostalCode(string? value) { if (string.IsNullOrEmpty(value)) return false; return Regex.IsMatch(value, @"^\d{5}(-\d{4})?$"); } } // In your validator builder.Ensure(x => x.PostalCode) .Is(CustomRules.IsValidPostalCode); ``` -------------------------------- ### Module-Level Validator Registration Source: https://github.com/hquinn/valicraft/blob/main/docs/dependency-injection.md Each project can also expose its own extension method for registering its validators independently. The method name is derived from the assembly name, following the pattern `Add{AssemblyName}ValiCraft()`. ```csharp // Register only validators from the Orders module builder.Services.AddMyAppOrdersValiCraft(); ``` -------------------------------- ### Execute Validation Source: https://github.com/hquinn/valicraft/blob/main/README.md Instantiate the generated validator and call the Validate method to check model state. ```csharp var validator = new UserValidator(); var user = new User { Username = "john", Email = "john@example.com", Age = 30 }; ValidationErrors? result = validator.Validate(user); if (result is null) { Console.WriteLine("User is valid!"); } else { Console.WriteLine($"Validation failed: {result.Message}"); } ``` -------------------------------- ### Validate Method Return Values with ValiCraft Source: https://context7.com/hquinn/valicraft/llms.txt Use the Ensure method within a Validator class to target method return values, including those requiring parameters. ```csharp public class Invoice { public string? Prefix { get; set; } public int Number { get; set; } public decimal Subtotal { get; set; } public string GetInvoiceId() => $"{Prefix}-{Number}"; public decimal GetTotalWithTax(decimal taxRate) => Subtotal * (1 + taxRate); } [GenerateValidator] public partial class InvoiceValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Parameterless method builder.Ensure(x => x.GetInvoiceId()) .IsNotNullOrWhiteSpace(); // Method with parameters builder.Ensure(x => x.GetTotalWithTax(0.1m)) .IsGreaterThan(0m); } } ``` -------------------------------- ### Attach Metadata to Errors Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Add custom key-value pairs to errors for additional context. ```csharp builder.Ensure(x => x.OrderTotal) .IsGreaterThan(0) .WithMetadata("Category", "Financial") .WithMetadata("RequiresReview", true); ``` -------------------------------- ### Group Failure Modes with WithOnFailure Source: https://context7.com/hquinn/valicraft/llms.txt Apply a failure mode to multiple rule chains at once using `WithOnFailure`. This is useful for applying a specific failure mode, like `OnFailureMode.Halt`, to a block of related validation rules. ```csharp [GenerateValidator] public partial class OrderValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // All rules in this block share the Halt failure mode builder.WithOnFailure(OnFailureMode.Halt, b => { b.Ensure(x => x.OrderNumber) .IsNotNull(); b.Ensure(x => x.OrderTotal) .IsGreaterThan(0m); // Nested with different mode b.EnsureEach(x => x.LineItems, OnFailureMode.Continue, itemBuilder => { itemBuilder.Ensure(item => item.Code) .IsNotNull(); itemBuilder.Ensure(item => item.Amount) .IsGreaterThan(0); }); }); } } ``` -------------------------------- ### Usage of Static Address Validator Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Validate an address object using a static validator. No instantiation is needed; call the static `Validate` method directly. ```csharp // Usage - no instantiation needed! ValidationErrors? result = AddressValidator.Validate(address); ``` -------------------------------- ### Nest Failure Modes Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Apply different failure modes at different levels of the validation hierarchy. ```csharp builder.WithOnFailure(OnFailureMode.Halt, b => { // Parent: Halt on any failure b.Ensure(x => x.OrderNumber) .IsNotNull(); // Child: Continue collecting all item errors even if parent would halt b.EnsureEach(x => x.LineItems, OnFailureMode.Continue, itemBuilder => { itemBuilder.Ensure(item => item.Code) .IsNotNull(); itemBuilder.Ensure(item => item.Amount) .IsGreaterThan(0); }); }); ``` -------------------------------- ### Customize Error Message with Placeholders Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Override the default error message using WithMessage, which supports placeholders like {ValueToCompare}. ```csharp builder.Ensure(x => x.Age) .IsGreaterOrEqualThan(18) .WithMessage("You must be at least {ValueToCompare} years old to register"); ``` -------------------------------- ### Customize Error Codes, Severity, Target Names, and Metadata Source: https://context7.com/hquinn/valicraft/llms.txt Customize error codes, severity levels, property names, and attach metadata for enhanced error handling. Use `WithErrorCode`, `WithTargetName`, `WithSeverity`, and `WithMetadata` to modify validation outcomes. ```csharp using Valicraft.Core; using Valicraft.Core.Validation; public class Order { public string OrderNumber { get; set; } public string Nickname { get; set; } public object ProfilePicture { get; set; } public decimal OrderTotal { get; set; } public bool IsExpress { get; set; } public string Notes { get; set; } public int PriorityLevel { get; set; } public bool IsInternational { get; set; } public Address ShippingAddress { get; set; } public List LineItems { get; set; } } public class LineItem { public string Code { get; set; } public decimal Amount { get; set; } } public class Address { // Address properties } public class OrderValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.OrderNumber) .IsNotNullOrWhiteSpace() .WithMessage("Order number is mandatory") .WithErrorCode("MISSING_ORDER_NUMBER") .WithTargetName("PO Number") // Override humanized name .WithSeverity(ErrorSeverity.Critical); builder.Ensure(x => x.Nickname) .HasMaxLength(20) .WithSeverity(ErrorSeverity.Warning); // Non-blocking warning builder.Ensure(x => x.ProfilePicture) .IsNotNull() .WithSeverity(ErrorSeverity.Info); // Informational // Attach metadata for additional context builder.Ensure(x => x.OrderTotal) .IsGreaterThan(0) .WithMessage("Order total must be positive") .WithErrorCode("INVALID_ORDER_TOTAL") .WithMetadata("Category", "Financial") .WithMetadata("RequiresReview", true); } } ``` -------------------------------- ### Static Validators for Stateless Validation Source: https://context7.com/hquinn/valicraft/llms.txt Utilize `StaticValidator` for stateless validation scenarios where dependency injection is not required. This provides zero instantiation cost. ```csharp [GenerateValidator] public partial class AddressValidator : StaticValidator
{ protected override void DefineRules(IValidationRuleBuilder
builder) { builder.Ensure(x => x.Street) .IsNotNullOrWhiteSpace(); builder.Ensure(x => x.City) .IsNotNullOrWhiteSpace(); builder.Ensure(x => x.PostalCode) .Matches(@"^\d{5}$"); } } // Usage - no instantiation needed, pure static method call ValidationErrors? result = AddressValidator.Validate(address); if (result is null) { ProcessAddress(address); } ``` -------------------------------- ### Register All ValiCraft Validators Source: https://github.com/hquinn/valicraft/blob/main/docs/dependency-injection.md Call `AddValiCraft()` in your application's service collection to register all non-static validators discovered at compile time. This includes `Validator` as `IValidator` and `AsyncValidator` as `IAsyncValidator`. ```csharp using ValiCraft.DependencyInjection; var builder = WebApplication.CreateBuilder(args); builder.Services.AddValiCraft(); ``` -------------------------------- ### Apply Custom Extension Rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Use the generated extension methods within a validator class, optionally overriding default messages. ```csharp [GenerateValidator] public partial class AddressValidator : Validator
{ protected override void DefineRules(IValidationRuleBuilder
builder) { builder.Ensure(x => x.PostalCode) .IsUsPostalCode(); // Uses default message builder.Ensure(x => x.UnitNumber) .IsDivisibleBy(100) .WithMessage("Unit must be in increments of {Divisor}"); // Custom message } } ``` -------------------------------- ### Validate collection elements with direct rules Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Chain validation rules directly on collection elements using EnsureEach for simple types. ```csharp builder.EnsureEach(p => p.Tags) .IsNotNullOrWhiteSpace() .HasMinLength(3); ``` -------------------------------- ### Asynchronous Validation with I/O Operations Source: https://context7.com/hquinn/valicraft/llms.txt Implement asynchronous validation rules using `AsyncValidator` for operations like database lookups or API calls. Supports cancellation tokens and parameterized async predicates. ```csharp [GenerateValidator] public partial class UserValidator : AsyncValidator { private readonly IUserRepository _repository; public UserValidator(IUserRepository repository) { _repository = repository; } protected override void DefineRules(IAsyncValidationRuleBuilder builder) { // Sync rules work in async validators builder.Ensure(x => x.Username) .IsNotNullOrWhiteSpace() .HasMinLength(3); // Async predicate with cancellation token builder.Ensure(x => x.Username) .Is(async (username, ct) => await _repository.IsUsernameAvailableAsync(username, ct)) .WithMessage("Username '{TargetValue}' is already taken"); // Async with parameter builder.Ensure(x => x.Email) .Is(async (email, domain, ct) => await _repository.IsEmailAllowedAsync(email, domain, ct), "example.com") .WithMessage("Email must be from the example.com domain"); } } // Usage var validator = new UserValidator(repository); ValidationErrors? result = await validator.ValidateAsync(user, cancellationToken); if (result is null) { Console.WriteLine("User is valid!"); } ``` -------------------------------- ### Conditional Validation with If() Source: https://context7.com/hquinn/valicraft/llms.txt Apply validation rules only when a condition is met using `If()` for conditional validation blocks. This allows for dynamic rule application based on object state. ```csharp [GenerateValidator] public partial class OrderValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.OrderNumber) .IsNotNullOrWhiteSpace(); // Only validate when IsExpress is true builder.If(x => x.IsExpress == true, b => { b.Ensure(x => x.Notes) .IsNotNullOrWhiteSpace(); b.Ensure(x => x.PriorityLevel) .IsGreaterThan(0); }); // Only validate ShippingAddress when IsInternational is true builder.If(x => x.IsInternational == true, b => { b.Ensure(x => x.ShippingAddress) .ValidateWith(new AddressValidator()); }); } } ``` -------------------------------- ### Define a Basic Validator Source: https://context7.com/hquinn/valicraft/llms.txt Inherit from Validator and use the [GenerateValidator] attribute to define rules via the fluent builder API. ```csharp using ValiCraft; using ValiCraft.Attributes; public class User { public required string Username { get; set; } public string? Email { get; set; } public int Age { get; set; } } [GenerateValidator] public partial class UserValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.Username) .IsNotNullOrWhiteSpace() .HasMinLength(3) .HasMaxLength(50); builder.Ensure(x => x.Email) .IsNotNullOrWhiteSpace() .IsEmailAddress(); builder.Ensure(x => x.Age) .IsGreaterOrEqualThan(0) .IsLessThan(150); } } ``` -------------------------------- ### Implement polymorphic validation Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Validate properties based on their runtime type using the Polymorphic builder. ```csharp public abstract class Payment { public decimal Amount { get; set; } } public class CreditCardPayment : Payment { public string CardNumber { get; set; } } public class CryptoPayment : Payment { public string WalletAddress { get; set; } } public class CashPayment : Payment { } public class Order { public Payment? Payment { get; set; } } [GenerateValidator] public partial class OrderValidator( IValidator creditCardValidator, IValidator cryptoValidator) : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Polymorphic(x => x.Payment) .WhenType().ValidateWith(creditCardValidator) .WhenType().ValidateWith(cryptoValidator) .WhenType().Fail("Cash payments are not accepted") .Otherwise().Allow(); // Or .Fail() to reject unknown types } } ``` -------------------------------- ### Register Validators with Dependency Injection Source: https://context7.com/hquinn/valicraft/llms.txt Register validators for automatic discovery and injection using AddValiCraft in the ASP.NET Core service container. ```csharp using ValiCraft.DependencyInjection; var builder = WebApplication.CreateBuilder(args); // Register all validators (Transient by default) builder.Services.AddValiCraft(); // Or specify lifetime builder.Services.AddValiCraft(ServiceLifetime.Scoped); var app = builder.Build(); // Validators are now injectable public class OrderController(IValidator orderValidator) { public IActionResult CreateOrder(Order order) { var result = orderValidator.Validate(order); if (result is null) { return Ok(order); } return BadRequest(result); } } // Async validators public class UserController(IAsyncValidator userValidator) { public async Task CreateUser(User user, CancellationToken ct) { var result = await userValidator.ValidateAsync(user, ct); return result is null ? Ok(user) : BadRequest(result); } } ``` -------------------------------- ### Define Static Order Validator Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Implement a static asynchronous validator for order data. This allows for async validation rules without the need for dependency injection. ```csharp [GenerateValidator] public partial class OrderValidator : StaticAsyncValidator { protected override void DefineRules(IAsyncValidationRuleBuilder builder) { builder.Ensure(x => x.OrderNumber) .IsNotNullOrWhiteSpace(); } } ``` -------------------------------- ### Define OrderValidator in C# Source: https://github.com/hquinn/valicraft/blob/main/docs/core-concepts.md Inherit from Validator and override DefineRules to set up validation logic for a specific model. ```csharp using ValiCraft; [GenerateValidator] public partial class OrderValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Define your validation rules here } } ``` -------------------------------- ### Date and Time Validation Rules in C# Source: https://context7.com/hquinn/valicraft/llms.txt Validate date and time values using temporal checks, relative comparisons, and age calculations. Rules include checking for future/past dates, date ranges, and minimum/maximum ages. ```csharp [GenerateValidator] public partial class EventValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Future/past validation (uses UTC) builder.Ensure(x => x.StartDate) .IsInFuture(); builder.Ensure(x => x.EndDate) .IsInFutureOrPresent(); builder.Ensure(x => x.CreatedAt) .IsInPast(); // Relative date comparisons builder.Ensure(x => x.Deadline) .IsAfter(DateTime.UtcNow.AddDays(7)); builder.Ensure(x => x.RegistrationClose) .IsBefore(DateTime.UtcNow.AddMonths(3)); // Date range validation builder.Ensure(x => x.EventDate) .IsDateBetween( new DateTime(2024, 1, 1), new DateTime(2024, 12, 31)); // Age validation from birth date builder.Ensure(x => x.DateOfBirth) .HasMinAge(18) .HasMaxAge(120); } } ``` -------------------------------- ### Apply Grouped Failure Modes Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Use WithOnFailure to apply a specific failure mode to multiple rule chains within a block. ```csharp builder.WithOnFailure(OnFailureMode.Halt, b => { // All rules in this block share the Halt failure mode b.Ensure(x => x.OrderNumber) .IsNotNull(); b.EnsureEach(x => x.LineItems, itemBuilder => { itemBuilder.Ensure(item => item.Code) .IsNotNull(); }); }); ``` -------------------------------- ### Usage of Static Order Validator Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Validate an order object using a static asynchronous validator. Call the static `ValidateAsync` method directly. ```csharp // Usage ValidationErrors? result = await OrderValidator.ValidateAsync(order, cancellationToken); ``` -------------------------------- ### Validate Record Class Types with ValiCraft Source: https://context7.com/hquinn/valicraft/llms.txt ValiCraft supports validation of record classes. Define a record class and then create a validator for it using the `GenerateValidator` attribute. ```csharp // Record class validation public record ShippingInfo(string? Carrier, string? TrackingNumber, int WeightKg); [GenerateValidator] public partial class ShippingInfoValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.Carrier).IsNotNullOrWhiteSpace(); builder.Ensure(x => x.TrackingNumber).IsNotNullOrWhiteSpace(); builder.Ensure(x => x.WeightKg).IsGreaterThan(0); } } ``` -------------------------------- ### Validate Struct Types with ValiCraft Source: https://context7.com/hquinn/valicraft/llms.txt ValiCraft supports validation of structs. Define a struct and then create a validator for it using the `GenerateValidator` attribute. ```csharp // Struct validation public struct Coordinate { public double Latitude { get; set; } public double Longitude { get; set; } } [GenerateValidator] public partial class CoordinateValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.Latitude) .IsGreaterThan(-90.0) .IsLessThan(90.0); builder.Ensure(x => x.Longitude) .IsGreaterThan(-180.0) .IsLessThan(180.0); } } ``` -------------------------------- ### Nested Validation with Constructor Injection Source: https://context7.com/hquinn/valicraft/llms.txt Inject child validators via the constructor for improved testability and DI integration. Ensure validators are registered with the DI container. ```csharp [GenerateValidator] public partial class OrderValidator(IValidator
addressValidator) : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.OrderNumber) .IsNotNullOrWhiteSpace(); builder.Ensure(x => x.ShippingAddress) .ValidateWith(addressValidator); } } // Registration builder.Services.AddValiCraft(); // Auto-registers all validators // Usage with DI public class OrderController(IValidator orderValidator) { public IActionResult CreateOrder(Order order) { var result = orderValidator.Validate(order); return result is null ? Ok(order) : BadRequest(result); } } ``` -------------------------------- ### Collection Validation Rules in C# Source: https://context7.com/hquinn/valicraft/llms.txt Validate collections based on count constraints, uniqueness, and item presence. Use rules for minimum/maximum counts, specific counts, and checking for empty or non-empty collections. ```csharp [GenerateValidator] public partial class OrderValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { // Count constraints builder.Ensure(x => x.Items) .HasMinCount(1) .HasMaxCount(100); builder.Ensure(x => x.Tags) .HasCountBetween(1, 10); builder.Ensure(x => x.ExactItems) .HasCount(5); // Empty/non-empty checks builder.Ensure(x => x.RequiredItems) .HasItems(); builder.Ensure(x => x.OptionalItems) .IsEmpty(); // Uniqueness validation builder.Ensure(x => x.ProductCodes) .IsUnique(); // Item containment builder.Ensure(x => x.Categories) .CollectionContains("electronics"); builder.Ensure(x => x.Flags) .CollectionNotContains("deprecated"); } } ``` -------------------------------- ### Validate Record Struct Types with ValiCraft Source: https://context7.com/hquinn/valicraft/llms.txt ValiCraft supports validation of record structs. Define a record struct and then create a validator for it using the `GenerateValidator` attribute. ```csharp // Record struct validation public record struct GeoRect(double North, double South, double East, double West); [GenerateValidator] public partial class GeoRectValidator : Validator { protected override void DefineRules(IValidationRuleBuilder builder) { builder.Ensure(x => x.North).IsBetween(-90.0, 90.0); builder.Ensure(x => x.South).IsBetween(-90.0, 90.0); } } ``` -------------------------------- ### Implement Static Async Validators Source: https://context7.com/hquinn/valicraft/llms.txt Use StaticAsyncValidator for asynchronous validation logic that does not require dependency injection. ```csharp [GenerateValidator] public partial class OrderValidator : StaticAsyncValidator { protected override void DefineRules(IAsyncValidationRuleBuilder builder) { builder.Ensure(x => x.OrderNumber) .IsNotNullOrWhiteSpace(); builder.Ensure(x => x.OrderTotal) .IsGreaterThan(0m); } } // Usage - static async method ValidationErrors? result = await OrderValidator.ValidateAsync(order, cancellationToken); ``` -------------------------------- ### Control Validation Flow with Failure Modes Source: https://github.com/hquinn/valicraft/blob/main/docs/advanced-features.md Use OnFailureMode to determine if validation should halt or continue after a rule failure. ```csharp // Halt: Stop validating this property on first failure builder.Ensure(x => x.Age, OnFailureMode.Halt) .IsGreaterOrEqualThan(0) // If this fails... .IsLessThan(150); // ...this won't run // Continue (default): Collect all errors builder.Ensure(x => x.Name, OnFailureMode.Continue) .IsNotNullOrWhiteSpace() .HasMinLength(2) .HasMaxLength(100); ```