A console application built in C# that validates different entity types using configurable rules and two validation modes: fail-fast and collect-all.
The rule engine allows you to define validation rules for different entity types and run them against a set of entities. Rules are registered once and reused across any number of validations. The engine supports two modes:
- Fail-Fast — stops at the first broken rule and reports it immediately
- Collect-All — goes through every rule, collects all violations, and reports them all at once
MiniRuleEngine/
├── IEntity.cs # Base interface for all entities
├── EmployeeEntity.cs # Employee entity with domain fields
├── OrderEntity.cs # Order entity with domain fields
├── Rule.cs # Represents a single validation rule
├── RuleEngine.cs # Core engine — stores rules and runs validations
├── RuleRegistration.cs # Registers all rules into the engine
├── RuleViolationException.cs # Exception for a single broken rule
├── EntityValidationException.cs # Exception for an entity with multiple violations
└── Program.cs # Entry point
Represents an employee record.
| Property | Type | Description |
|---|---|---|
Id |
int |
Unique identifier |
FullName |
string |
Full name of the employee |
Salary |
decimal |
Monthly salary |
VacationDays |
int |
Number of vacation days |
Represents a customer order.
| Property | Type | Description |
|---|---|---|
Id |
int |
Unique identifier |
CustomerName |
string |
Name of the customer |
Amount |
decimal |
Order total amount |
CountryCode |
string |
Two-letter country code (e.g. "AM", "US") |
ItemCount |
int |
Number of items in the order |
FullNamemust not be empty or whitespaceSalarymust be between 0 and 1,000,000VacationDaysmust be between 0 and 30
CustomerNamemust not be empty or whitespaceAmountmust be greater than zeroCountryCodemust be exactly 2 charactersItemCountmust be at least 1
delegate void RuleCheck(IEntity entity);Each rule holds one of these. When called, it either passes silently or throws a RuleViolationException.
engine.AddRule(new Rule("Employee must have a name", "Employee",
delegate(IEntity entity)
{
EmployeeEntity emp = (EmployeeEntity)entity;
if (string.IsNullOrWhiteSpace(emp.FullName))
throw new RuleViolationException("Employee must have a name", "FullName cannot be empty.");
}
));Stops and throws on the first violated rule.
engine.ValidateFailFast(entity);Runs every applicable rule, collects all violations, throws once at the end.
engine.ValidateCollectAll(entity);Thrown when a single rule is violated.
| Property | Description |
|---|---|
RuleName |
Name of the rule that was violated |
Message |
Human-readable description of the violation |
Thrown by collect-all mode when one or more rules are violated.
| Property | Description |
|---|---|
Entity |
The entity that failed validation |
Violations |
Array of all RuleViolationExceptions collected |
Message |
Format: "Employee #2 has 2 validation error(s)." |
=== Fail-Fast Mode ===
Employee #1 — VALID
[Employee must have a name]: FullName cannot be empty.
Order #20 — VALID
[OrderMustHaveAName]: Order does not have a name.
=== Collect-All Mode ===
Employee #1 — VALID
Employee #2 has 2 validation error(s).
- [Employee must have a name]: FullName cannot be empty.
- [SalaryBounds]: Salary must be between 0 and 1000000.
Order #20 — VALID
Order #30 has 1 validation error(s).
- [OrderMustHaveAName]: Order does not have a name.
Array-based storage with manual resizing — the RuleEngine uses a raw Rule[] array that doubles in capacity when full, implemented with Array.Resize. This satisfies the homework requirement of not using List<T> for rule storage.
Rule filtering by entity type — each Rule knows its TargetEntityType. The engine calls rule.AppliesTo(entity) before running any check, so employee rules never run against orders and vice versa.
Two independent validation modes — fail-fast and collect-all share the same rule loop logic but differ in how they handle exceptions. Fail-fast rethrows immediately; collect-all accumulates into a List<RuleViolationException> and throws once at the end.
- .NET 10
- No external dependencies