️ Validation Attributes
v1.24.0 — The [Validate] source generator creates a .Validate() extension method for any record or class decorated with System.ComponentModel.DataAnnotations attributes, returning Result<T> — fully composable with the rest of the pipeline.
6.6.1. Quick Setup
using REslava.Result.SourceGenerators;
[Validate]
public record CreateProductRequest(
[Required] string Name,
[Range(0.01, double.MaxValue)] decimal Price,
[StringLength(500)] string? Description
);
6.6.2. Generated Extension Method
The generator emits this code at compile time — no runtime reflection overhead:
// Auto-generated in Generated.ValidationExtensions namespace
public static Result<CreateProductRequest> Validate(this CreateProductRequest instance)
{
var context = new ValidationContext(instance);
var results = new List<ValidationResult>();
if (Validator.TryValidateObject(instance, context, results, validateAllProperties: true))
return Result<CreateProductRequest>.Ok(instance);
var errors = results
.Select(r => (IError)new ValidationError(
r.ErrorMessage ?? "Validation failed",
r.MemberNames.FirstOrDefault()))
.ToList();
return Result<CreateProductRequest>.Fail(errors);
}
6.6.3. Pipeline Integration
// Minimal API — validate and respond in one line
app.MapPost("/api/products", (CreateProductRequest request) =>
request.Validate().ToIResult());
// MVC Controller — validate then call service
return (await request.Validate()
.BindAsync(r => _service.CreateAsync(r)))
.ToActionResult();
// Chain further operations
Result<ProductDto> result = await request.Validate()
.BindAsync(r => _service.CreateAsync(r))
.Map(p => p.ToDto());
6.6.4. Supported Annotations
All 20+ System.ComponentModel.DataAnnotations types work automatically because the generator delegates to Validator.TryValidateObject:
[Required], [Range], [StringLength], [MinLength], [MaxLength], [RegularExpression], [EmailAddress], [Phone], [Url], [Compare], [CreditCard], [DataType], and any custom class inheriting from ValidationAttribute.
6.6.5. Error Details
Each ValidationError carries the field name and message:
result.Errors.OfType<ValidationError>().ToList().ForEach(e =>
Console.WriteLine($"{e.FieldName}: {e.Message}"));
// → Name: The Name field is required.
// → Price: The field Price must be between 0.01 and 1.7976931348623157E+308.