feat(core): add Form Request classes for typed binding + validation per endpoint#215
Merged
Conversation
…er endpoint (#163) Introduces Laravel-style FormRequest<TSelf> base class that bundles parameter binding, authorization, validation rules, and data normalization into a single class. The endpoint handler receives an already-valid request object. Pipeline: Bind → Authorize (403) → Prepare → Validate (422) → Handler - FormRequest base + [FormRequest] attribute in SimpleModule.Core - Source generator discovers [FormRequest] types, validates shape (SM0056/SM0057), emits TypeScript interfaces (same path as [Dto]) - FormRequestEndpointFilter auto-applied to all module route groups - FluentValidation under the hood via RuleConfigurator<T> - Validator cached per type for performance - 422 Unprocessable Entity with RFC 7807 problem+json for validation failures - Email module CreateTemplateEndpoint refactored as reference implementation - 16 new xUnit tests covering binding, validation, authorization, and prepare hooks - Constitution updated with FormRequest rules and SM0056/SM0057 diagnostics
…tia errors, testability - Fix C3: modules without RoutePrefix now get a group with AddFormRequestFilter() instead of mapping directly to app (no-prefix endpoints were silently skipping the FormRequest validation pipeline) - Fix C1: ConfigureEndpoints escape hatch now wraps in a group with AddFormRequestFilter() so FormRequest types work in escape-hatch modules - Fix M4: extract shared InertiaErrorResult utility used by both FormRequestEndpointFilter and GlobalExceptionHandler (removes duplication) - Fix m2: add public ValidateRulesAsync() method on FormRequest so consumers can unit-test their validation rules without constructing filter context - Update generator tests to match new generated code shape
- Fix duplicate TS interfaces when both [Dto] and [FormRequest] on same class (DtoFinder now deduplicates FQNs before the [FormRequest] scan) - Restore .RequirePermission() on CreateTemplateEndpoint for ASP.NET auth metadata visibility (OpenAPI/Swagger, policy-based audit); remove redundant FormRequest.Authorize() override from CreateTemplateFormRequest - Revert escape-hatch ConfigureEndpoints to receive WebApplication (not RouteGroupBuilder) — avoids breaking contract for modules that cast to IApplicationBuilder - ValidateRulesAsync now calls Prepare() before validation, consistent with the filter pipeline - Add When/Unless forwarding to RuleConfigurator for grouped conditional rules - Replace ConcurrentDictionary validator cache with volatile + Interlocked (single-entry-per-type, simpler, avoids redundant dictionary overhead)
Convert 4 Settings module endpoints to use FormRequest for input validation:
- UpdateSettingEndpoint (PUT /api/settings) — validates key format, scope enum
- UpdateMySettingEndpoint (PUT /api/settings/me) — reuses UpdateSettingFormRequest
- CreateMenuItemEndpoint (POST /api/settings/menus) — validates label, URL length
- UpdateMenuItemEndpoint (PUT /api/settings/menus/{id}) — validates same as create
Demonstrates dual-validation: FormRequest validates shape (key format, field
lengths, enum membership), service layer validates domain (setting type/range
against definitions). FormRequest returns 422, service exceptions return 400.
Symmetric validation: both Create and Update menu endpoints now share the same
rules; both admin and user setting endpoints use the same FormRequest.
Includes 16 new integration tests verifying 422 responses, Prepare normalization,
RFC 7807 shape, and auth-before-validation ordering.
…ntics - Fix regex to allow camelCase/PascalCase keys (email.defaultFromAddress, FileStorage.MaxFileSizeMb, etc.) and enforce segment-based validation that rejects trailing dots and empty segments - Create dedicated UpdateMySettingFormRequest (Key + Value only) for PUT /api/settings/me — removes misleading Scope field that was validated but silently ignored - Fix double error messages for empty key by splitting rules with .When() guard so regex only fires on non-empty keys - Rename misleading test: NullValue "clears setting" → actually stores empty string (documents real behavior, not aspirational) - Add tests for camelCase keys and trailing-dot rejection
Upstream PR #211 changed UpdateSettingRequest.Value from string? to JsonElement and added .RequirePermission(SettingsPermissions.Update). Update FormRequest integration tests to: - Use JsonSerializer.Deserialize<JsonElement>() for Value fields - Pass SettingsPermissions.Update to CreateAuthenticatedClient - Replace hyphenated test keys with camelCase (regex rejects hyphens)
Deploying simplemodule-website with
|
| Latest commit: |
e7e84dc
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://aa4cd267.simplemodule-website.pages.dev |
| Branch Preview URL: | https://worktree-form-requests.simplemodule-website.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FormRequest<TSelf>base class that bundles parameter binding, authorization, validation rules (RuleConfigurator<T>wrapping FluentValidation), and data normalization (Prepare()) into a single class[FormRequest]types, enforces SM0056 (sealed) and SM0057 (extends base), auto-generates TypeScript interfaces, and appliesFormRequestEndpointFilterto all module route groupsWhat changed
SimpleModule.Core/FormRequests/):FormRequest,FormRequest<TSelf>,FormRequestAttribute,RuleConfigurator<T>,FormRequestEndpointFilter,FormRequestExtensionsSimpleModule.Core/Inertia/InertiaErrorResult): extracted from duplicated code inGlobalExceptionHandlerand the filterFormRequestFinder, diagnostics (SM0056/SM0057), DTO/TypeScript generation for[FormRequest]types, filter wiring on route groupsCreateTemplateFormRequest+ endpoint conversion (reference implementation)UpdateSettingFormRequest,UpdateMySettingFormRequest,CreateMenuItemFormRequest,UpdateMenuItemFormRequest+ 4 endpoint conversions with 16 integration testsCloses #163
Test plan
dotnet build— 0 errorsdotnet test— 1,204 tests pass, 0 failures/api/settings→ expect 422 with field errors/api/settings/menus→ expect 201 with Location header