Support the Tiny suite on Buy Me a Coffee to help fund testing and ongoing maintenance.
TinyValidations is a small compile-time validation library for application-layer commands.
It was built for TinyDispatcher-style applications first: define a command, define its validation, register services, and let generated code do the boring work before the handler runs. Native ASP.NET integration is planned, but the core package is intentionally host-agnostic.
Status: 1.0 stable. The core package is intentionally small and host-agnostic. Native host integrations may ship separately.
Preview: 1.1.0-beta.3 fixes validation generation safety and supports compile-time constants and named rule arguments. Generator diagnostics
TV0001throughTV0006are now errors. See the changelog.
- Why TinyValidations?
- Quick start
- Validation declarations
- Inspect generated validations
- Custom rules
- TinyDispatcher
- Tiny suite
- Design principles
- Documentation
- Current limitations
Most validation libraries do their work at runtime.
TinyValidations keeps the developer API small and moves the repetitive validation runner code to a source generator. You declare intent with IValidation<T> and ValidationRules<T>. The generator emits ordinary C# runners that are registered through Microsoft dependency injection.
The goal is not to be a huge validation framework. The goal is calm command validation:
- no reflection-based rule discovery at runtime
- no runtime expression parsing for built-in rules
- direct generated checks for common rules
- generator-native requirement rules for simple application-specific checks
- DI-friendly custom async rules for real business checks
- a small API that fits application-layer code
Install the package:
dotnet add package TinyValidationsThen register TinyValidations:
using Microsoft.Extensions.DependencyInjection;
using TinyValidations;
var services = new ServiceCollection();
services.AddScoped<UserStore>();
services.UseTinyValidations();
var provider = services.BuildServiceProvider();
var validator = provider.GetRequiredService<ITinyValidator>();
var result = await validator.ValidateAsync(new CreateUser("bad-email", "J", 17));Define a validation for your command:
public sealed class CreateUserValidation : IValidation<CreateUser>
{
public void Define(ValidationRules<CreateUser> rules)
{
rules.Required(x => x.Email);
rules.Email(x => x.Email);
rules.TextLengthAtLeast(x => x.Name, 2);
rules.AtLeast(x => x.Age, 18);
rules.Use<UniqueEmailRule>();
}
}
public sealed record CreateUser(string Email, string Name, int Age);Read validation errors:
foreach (var error in result.Errors)
{
Console.WriteLine($"{error.Member}: {error.Message}");
}See the full sample in samples/Basic.
A validation declaration implements IValidation<T>:
public sealed class CreateUserValidation : IValidation<CreateUser>
{
public void Define(ValidationRules<CreateUser> rules)
{
rules.Required(x => x.Email);
}
}The Define method is declaration-only. TinyValidations reads it at compile time and emits generated validation runners.
Supported built-in rules are documented in Rules.
Rule values, regex patterns, and supplied messages must be compile-time constants. Literals,
const fields and locals, nameof, and constant expressions are supported. Named arguments can
be reordered according to C# rules.
Diagnostics TV0001 through TV0006 are compilation errors. The generator reports issues across
declarations and generates the valid declarations independently, but the build fails until the
errors are resolved. See Declaration diagnostics.
Required treats null, whitespace strings, and default non-nullable value types such as
Guid.Empty as missing. A nullable value type is missing only when it is null.
Tooling and adapters can retrieve the generated validation structure without executing validation rules or scanning assemblies:
var validations = TinyValidationBootstrap.GetValidations();
foreach (var validation in validations)
{
Console.WriteLine(validation.ValidatedType.FullName);
Console.WriteLine(validation.DeclarationType.FullName);
foreach (var rule in validation.Rules)
{
Console.WriteLine($"{rule.MemberPath}: {rule.Kind}");
}
foreach (var customRuleType in validation.CustomRuleTypes)
{
Console.WriteLine(customRuleType.FullName);
}
}The structure is collected lazily on the first call and then reused. It contains contributions
from TinyValidations-enabled assemblies loaded by the application. Calling GetValidations() does
not resolve services, construct validators, or run business rules.
Custom rules implement IAsyncValidationRule<T>. They are resolved from dependency injection, so they can use scoped services.
public sealed class UniqueEmailRule : IAsyncValidationRule<CreateUser>
{
private readonly UserStore _users;
public UniqueEmailRule(UserStore users)
{
_users = users;
}
public ValueTask ValidateAsync(
CreateUser instance,
ValidationErrorCollection errors,
CancellationToken cancellationToken)
{
if (_users.Exists(instance.Email))
{
errors.Add(nameof(CreateUser.Email), "Email is already registered.");
}
return ValueTask.CompletedTask;
}
}More detail is in Custom rules.
For static generator-native checks, use Requires:
rules.Requires(
x => x.OrderNumber,
OrderNumberRequirements.HasOrderPrefix,
"Order number must start with ORD-.");Read more in Extending TinyValidations.
TinyValidations is designed to become the validation companion for TinyDispatcher.
The intended flow is:
- A command enters the dispatcher.
- TinyValidations validates the command.
- The dispatcher stops when validation fails.
- The command handler runs only for valid commands.
The current package exposes the validation primitives. The TinyDispatcher integration package is planned separately so the core remains useful outside TinyDispatcher.
Read more in TinyDispatcher integration.
See samples/TinyDispatcherAspNetCore for a minimal ASP.NET sample that wires TinyValidations into TinyDispatcher middleware and maps validation failures to problem details.
See samples/MediatRAspNetCore for the same application shape using a MediatR pipeline behavior.
TinyValidations belongs to the Tiny suite:
| Project | Kind | Responsibility |
|---|---|---|
| TinyDispatcher | Library | Command and query execution |
| TinyValidations | Library | Application input validation |
| TinyEvents | Library | Reliable application-event handling through the outbox pattern |
| TheTinyApplicationLayer | Example | Runnable ASP.NET Core and Blazor application using the complete suite |
The three libraries can be adopted independently. Using one does not require referencing the other two.
TinyValidations is intentionally boring in the places that matter.
- Keep the public API small before adding convenience.
- Prefer source-generated C# over runtime reflection or expression interpretation.
- Keep the core package host-agnostic.
- Treat custom rules as the extension point for business behavior.
- Keep built-in rule declaration shapes narrow and explicit.
- Protect behavior with tests instead of testing private implementation details.
- Make dependency injection registration predictable and idempotent.
- Getting started
- Changelog
- Rules
- Custom rules
- Extending TinyValidations
- TinyDispatcher integration
- MediatR integration
- TinySuite and sample app
- OpenTelemetry
- Architecture
- Roadmap
TinyValidations is intentionally small.
- Declare rule calls directly inside
Define; it is read at compile time and is not executed at runtime. - Selectors must be simple member paths. Put calculations and conditional checks in
Requiresor custom rules. - Rule values, patterns, and supplied messages must be compile-time constants.
static readonlyfields, ordinary variables, property reads, and runtime method calls are not supported as these arguments. - Comparison values must also satisfy the rule's
IComparable<T>constraint. Runtime thresholds, including constructed dates and times, belong inRequiresor custom rules. - Detected unsupported declarations produce errors
TV0001throughTV0006, even without treating warnings as errors. - Generated registration uses a bootstrap contribution mechanism that is idempotent per service collection.
- Native ASP.NET integration is planned but not implemented yet.
- Tests cover runtime behavior, generated behavior, diagnostics, and multi-assembly contributions.
See Roadmap for the next planned steps.