🕸️ RuleWeaver

Validation without the boilerplate. A high-performance, configuration-driven, .NET 10–agnostic validation engine.
🚀 Why RuleWeaver?
Tired of creating repetitive Validator classes for every DTO? RuleWeaver eliminates the need for "robotic" validation coding.
- Zero Validation Classes: Define rules directly in
appsettings.json.
- Enterprise-Grade Performance: Singleton cache of execution plans. JSON parsing happens only once; subsequent requests run entirely in memory (Zero I/O).
- Async & Non-Blocking: Built on top of
ValueTask to support database validations without blocking threads, while keeping zero-allocation for CPU-bound rules.
- Deterministic: Structured and frontend-friendly error responses (Array of Objects).
- Extensible: Create custom rules (e.g.,
IsCpf, UniqueEmail) in your API and RuleWeaver discovers them automatically.
- Safe Dependency Injection: Architecture that respects scopes (Singleton Cache + Scoped Engine), safely allowing rules that access
DbContext.
📦 Installation
Install via NuGet Package Manager Console:
Install-Package RuleWeaver
Or via .NET CLI:
dotnet add package RuleWeaver
⚡ Quick Start
1. Register in Program.cs
In your Program.cs, register the service.
The parameter typeof(Program).Assembly allows RuleWeaver to discover custom rules in your project.
using RuleWeaver.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRuleWeaver(typeof(Program).Assembly);
builder.Services.AddControllers();
2. Define your DTO
Create the class you want to validate.
public class CustomerRequest
{
public string Name { get; set; }
public int Age { get; set; }
public string Email { get; set; }
}
3. Configure the Rules (appsettings.json)
Use the RuleWeaver section to map your classes and properties.
{
"RuleWeaver": {
"CustomerRequest": {
"Name": [
{
"RuleName": "Required",
"RuleParameter": null,
"RuleErrorMessage": "Name is required."
},
{
"RuleName": "MinLength",
"RuleParameter": "3"
}
],
"Age": [
{
"RuleName": "MinValue",
"RuleParameter": "18",
"RuleErrorMessage": "Not allowed for under 18."
}
],
"Email": [
{ "RuleName": "Required" },
{ "RuleName": "Email", "RuleErrorMessage": "Invalid email." }
]
}
}
}
4. Use in the Controller
Add the [WeaveValidation] attribute to your Action.
The engine will validate the object before entering the method.
using Microsoft.AspNetCore.Mvc;
using RuleWeaver.Attributes;
[ApiController]
[Route("api/[controller]")]
public class CustomerController : ControllerBase
{
[HttpPost]
[WeaveValidation]
public IActionResult Create([FromBody] CustomerRequest request)
{
return Ok();
}
}
🛠️ Available Rules (Built-in)
The package already includes the following native rules:
| Rule Parameter | Ex | Description |
| -------------- | ----- | ---------------------------------------------- |
| Required | — | Checks if the value is not null or empty. |
| MinLength | 5 | Minimum length for Strings or Lists. |
| MaxLength | 100 | Maximum length for Strings or Lists. |
| MinValue | 18 | Minimum numeric value. |
| MaxValue | 99 | Maximum numeric value. |
| Email | — | Validates email format (Regex). |
| Regex | [0-9] | Validates against a custom regular expression. |
🧩 Creating Custom Rules
Need to validate a specific ID format or check if an email exists in the database?
Create a class implementing IValidationRule.
Note: RuleWeaver uses ValueTask to support high-performance async validations.
using RuleWeaver.Abstractions;
using System.Threading.Tasks;
public class UniqueEmailRule : IValidationRule
{
private readonly MyDbContext _context;
public UniqueEmailRule(MyDbContext context)
{
_context = context;
}
public string Name => "UniqueEmail";
public async ValueTask<RuleResult> ValidateAsync(object? value, string[] args)
{
if (value is null) return RuleResult.Success();
var exists = await _context.Users.AnyAsync(u => u.Email == value.ToString());
if (exists)
{
return RuleResult.Failure("Email already taken.");
}
return RuleResult.Success();
}
}
In JSON:
"Email": [
{ "RuleName": "UniqueEmail" }
]
🪆 Validating Nested Objects
RuleWeaver supports deep validation for complex objects using the special rule "Nested".
To validate a child object (e.g., an Address inside a Customer), you must explicitly tell the engine to dive into that property.
1. The Model Structure
public class AddressDto
{
public string Street { get; set; }
public string ZipCode { get; set; }
}
public class CustomerRequest
{
public string Name { get; set; }
public AddressDto BillingAddress { get; set; }
}
2. The Configuration
Define the rules for the child object (AddressDto) at the root level of the configuration, just like any other class. Then, in the parent class (CustomerRequest), apply the Nested rule.
{
"RuleWeaver": {
"CustomerRequest": {
"Name": [{ "RuleName": "Required" }],
"BillingAddress": [
{ "RuleName": "Required" },
{ "RuleName": "Nested" }
]
},
"AddressDto": {
"Street": [{ "RuleName": "Required" }],
"ZipCode": [{ "RuleName": "MinLength", "RuleParameter": "8" }]
}
}
}
3. The Result
Errors in nested properties are automatically flattened using Dot Notation, making it easy for frontends to map errors to fields.
{
"property": "BillingAddress.Street",
"violations": [
{
"rule": "Required",
"message": "This field is required."
}
]
}
📚 Validating Collections
RuleWeaver automatically detects if a property is a collection (List, Array, IEnumerable).
When you apply the "Nested" rule to a list, the engine iterates through every item and tracks the index in the error output.
1. The Model Structure
public class ContactDto
{
public string Type { get; set; }
public string Value { get; set; }
}
public class CustomerRequest
{
public string Name { get; set; }
public List<ContactDto> Contacts { get; set; }
}
2. The Configuration
Apply the "Nested" rule to the list property (Contacts). The engine handles the iteration logic.
{
"RuleWeaver": {
"CustomerRequest": {
"Name": [{ "RuleName": "Required" }],
"Contacts": [
{ "RuleName": "Nested" }
]
},
"ContactDto": {
"Type": [{ "RuleName": "Required" }],
"Value": [{ "RuleName": "Required" }]
}
}
}
3. The Result
Errors in collections include the Index of the invalid item (e.g., [1]), allowing the frontend to pinpoint exactly which row in a grid or list has the error.
{
"property": "Contacts[1].Value",
"violations": [
{
"rule": "Required",
"message": "This field is required."
}
]
}
📡 Response Format (API)
RuleWeaver returns a 400 Bad Request with a deterministic and frontend-friendly JSON body.
{
"message": "Validation errors occurred.",
"errors": [
{
"property": "Age",
"violations": [
{
"rule": "MinValue",
"message": "Not allowed for under 18."
}
]
},
{
"property": "Password",
"violations": [
{
"rule": "MinLength",
"message": "Length must be at least 8 characters."
},
{
"rule": "Regex",
"message": "Needs at least one number"
}
]
}
]
}
🏛️ Architecture and Performance
RuleWeaver was designed for scalability:
Layer 1 (Singleton Cache): On startup, RuleWeaver reads appsettings.json, parses it, and compiles an Execution Plan. This happens only once.
Layer 2 (Scoped Engine): On each request, the engine simply consults the in-memory plan (O(1)) and executes the rules.
Layer 3 (Async Pipeline): It uses ValueTask to ensure zero-allocation for synchronous rules (CPU-bound) while fully supporting await for I/O-bound rules.
Result: Virtually zero overhead after the initial warm-up.
🤝 Contribution
Contributions are welcome! Feel free to open Issues or submit Pull Requests.
📄 License
This project is licensed under the MIT License.