Skip to content

Replace custom ValidationBuilder with FluentValidation#112

Merged
antosubash merged 1 commit intomainfrom
feature/fluent-validation-migration
Apr 15, 2026
Merged

Replace custom ValidationBuilder with FluentValidation#112
antosubash merged 1 commit intomainfrom
feature/fluent-validation-migration

Conversation

@antosubash
Copy link
Copy Markdown
Owner

Summary

Replace the custom framework/SimpleModule.Core/Validation/ValidationBuilder.cs (140 LOC) + ValidationResult.cs with FluentValidation 12.1.1. The custom fluent builder reimplemented a narrow subset of what FluentValidation has provided for over a decade.

  • −178 LOC net (791 deletions, 613 insertions across 56 files — 12 of the deletions are framework-test files for code that no longer exists)
  • 17 validators migrated from static Validate(request) methods → AbstractValidator<T> classes
  • 20 endpoints updated to inject IValidator<TRequest> and call ValidateAsync
  • 7 modules register their validators via services.AddValidatorsFromAssemblyContaining<ThisModule>()
  • Zero response-shape changeGlobalExceptionHandler already emitted RFC 7807 ProblemDetails with an errors extension, which matches FluentValidation's ToDictionary() output

Migration map

Old New
public static class XxxValidator with Validate(req) public sealed class XxxValidator : AbstractValidator<T>
new ValidationBuilder().AddErrorIf(...).Build() RuleFor(x => x.Prop).NotEmpty()...
ValidationResult.IsValid / .Errors FluentValidation.Results.ValidationResult.IsValid / .Errors
throw new ValidationException(validation.Errors) throw new Core.Exceptions.ValidationException(validation.ToValidationErrors())

Added ValidationResultExtensions.ToValidationErrors() as a shared helper to bridge FluentValidation's ValidationResult → the Dictionary<string, string[]> shape that ValidationException and the RFC 7807 writer consume.

Kept the existing ValidationException

SimpleModule.Core.Exceptions.ValidationException is the general-purpose "validation failed" exception thrown from many places outside endpoint validators (Chat, UserService, EmailService, PageBuilderService). Removing it would have broadened the PR beyond validation-layer concerns. FluentValidation's own ValidationException is disambiguated by fully qualifying as Core.Exceptions.ValidationException in the 20 migrated endpoints.

Notable edge cases

  • UpdateDefaultMapRequestValidator took a runtime parameter (int maxLayers from IOptions<MapModuleOptions>). FluentValidation handles this via constructor injection — the registered validator receives IOptions<MapModuleOptions> and reads maxLayers once at construction.
  • Orders' RuleForEach on Items was rewritten as a Custom rule to preserve the aggregated "Items" error-field shape (FluentValidation's RuleForEach would produce keys like "Items[0].Quantity").
  • PageBuilder had inline validation in CreateEndpoint.cs / UpdateEndpoint.cs; extracted into CreatePageRequestValidator.cs / UpdatePageRequestValidator.cs.
  • Shared email regex in the old SendEmailRequestValidator was removed — FluentValidation's built-in .EmailAddress() rule replaces it across all three email validators.

Documentation updated

  • .claude/skills/simplemodule/references/endpoints.md — validation section rewritten
  • .claude/skills/minimal-api/references/patterns.md — validation section rewritten
  • cli/SimpleModule.Cli/Templates/FeatureTemplates.csFallbackValidator template and in-line body replacement now emit AbstractValidator<T>

Historical references in docs/plans/* and docs/specs/* were left unchanged as records of past decisions.

Test plan

  • dotnet build — 0 errors, 21 node-modules warnings (unrelated, pre-existing)
  • dotnet test1195 passed, 0 failed, 1 skipped (pre-existing). 12 fewer than main because ValidationBuilderTests (10) and ValidationResultTests (2) were deleted along with the types they tested.
  • No code references to ValidationBuilder, SimpleModule.Core.Validation.ValidationResult, or the old static *Validator.Validate(request) call pattern anywhere in the codebase

Part of a series

PR 2 of 3 replacing custom framework code with trusted OSS libraries:

  1. Custom cache → FusionCache (merged: Replace custom ICacheStore with FusionCache #111)
  2. [this PR] Custom ValidationBuilder → FluentValidation
  3. Custom IEventBus → Wolverine (pending)

Based on latest main (includes #111).

Delete framework/SimpleModule.Core/Validation/ValidationBuilder.cs (140 LOC)
and ValidationResult.cs plus their tests (160 LOC). The custom fluent
builder reimplemented a narrow subset of what FluentValidation has
provided for over a decade.

Add FluentValidation 12.1.1 and FluentValidation.DependencyInjectionExtensions
via central package management. Reference both from SimpleModule.Core so
IValidator<T> and AddValidatorsFromAssembly flow transitively into modules
without per-module csproj edits.

Convert 17 static Validate(request) methods into AbstractValidator<T>
classes across Tenants, Products, Orders, PageBuilder, RateLimiting, Map,
and Email. Inject IValidator<TRequest> into the 20 endpoint lambdas
that use them; switch to async lambdas with ValidateAsync.

Preserve the existing RFC 7807 response shape — GlobalExceptionHandler
already emits ProblemDetails with an `errors` extension matching
FluentValidation's ToDictionary(). Keep SimpleModule.Core.Exceptions.
ValidationException as the general-purpose "validation failed" exception
thrown elsewhere (Chat, UserService, EmailService). Bridge the two with
a new ValidationResultExtensions.ToValidationErrors() helper.

Register validators per-module via services.AddValidatorsFromAssembly
Containing<ThisModule>() in ConfigureServices. Two new PageBuilder
validator files extracted from previously-inline validation in
CreateEndpoint/UpdateEndpoint.

Update the sm CLI's FallbackValidator template and two skill docs
(simplemodule/endpoints.md, minimal-api/patterns.md) to reflect the new
pattern. Historical references in docs/plans/* and docs/specs/* are
left unchanged as records of past decisions.

All 1195 tests pass (12 deleted framework-validation tests accounted for).
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying simplemodule-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: d9402e8
Status: ✅  Deploy successful!
Preview URL: https://bac6da1f.simplemodule-website.pages.dev
Branch Preview URL: https://feature-fluent-validation-mi.simplemodule-website.pages.dev

View logs

@antosubash antosubash merged commit 02e6166 into main Apr 15, 2026
5 checks passed
@antosubash antosubash deleted the feature/fluent-validation-migration branch April 15, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant