Skip to content

Add Form Request classes (typed binding + validation per endpoint) #163

@antosubash

Description

@antosubash

Background

Validation is performed inline today, often via FluentValidation called from inside the endpoint handler. Laravel's Form Requests bundle (a) parameter binding, (b) authorization, (c) validation rules, and (d) prepared/normalized data into a single class injected into the controller. The handler receives an already valid request object.

Motivation

  • Removes boilerplate from endpoint bodies
  • Centralizes shape + rules per request type (re-usable across endpoints, easy to test)
  • Plays well with the source generator (TS interfaces auto-emitted)
  • Forces a consistent 422 error envelope

Design sketch

[FormRequest]
public sealed class CreateProductRequest : FormRequest
{
    public string Name { get; init; } = "";
    public decimal Price { get; init; }
    public string? Sku { get; init; }

    public override bool Authorize(ClaimsPrincipal user) => user.HasPermission("Products.Create");

    public override IRuleBuilder Rules() => Rules
        .For(x => x.Name).NotEmpty().MaxLength(200)
        .For(x => x.Price).GreaterThan(0)
        .For(x => x.Sku).Optional().Matches("^[A-Z0-9-]+$");

    public override void Prepare()
    {
        Sku = Sku?.Trim().ToUpperInvariant();
    }
}

Endpoint usage:

public IResult Handle(CreateProductRequest request, IProductService svc) =>
    Results.Ok(svc.Create(request));
  • Source generator emits a binder + validator wrapper that runs Authorize → Bind → Prepare → Validate and short-circuits with 403/422 automatically
  • Failed validation returns the standard problem+json envelope { "errors": { "name": ["..."] } }
  • TS interface auto-emitted from [FormRequest] (same path as [Dto])
  • Existing endpoints can opt in incrementally; not a breaking change

Acceptance criteria

  • FormRequest base + [FormRequest] attribute in SimpleModule.Core
  • Source generator discovers, validates shape, emits binder
  • FluentValidation under the hood (no new dependency)
  • 422 error envelope documented
  • xUnit tests: binding, validation failure, authorize failure, prepare hook
  • One template module refactored as reference
  • Docs + Constitution update

References

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions