Skip to content

️ 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.