Repository navigation
feat(application): application and API building blocks (handlers, validation, error responses) - #22
Merged
Merged
Conversation
Error gains optional Fields and Details dictionaries so a Result failure can hold the camelCase field messages and rule-specific data (updatedByName, userNames) the API layer needs to build Problem Details responses (ADR-0009). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…orator ICommandHandler<TCommand, TResponse> is the in-house CQRS handler contract (ADR-0007). ValidatingCommandHandler wraps it and runs a FluentValidation validator first (ADR-0008): an invalid command returns validation_failed and never reaches the inner handler, and each FluentValidation failure path is converted to a camelCase field path (emergencyContact.phone) in Error.Fields. Removes the TEMPORARY exit-code line from ControlService.Application.Tests.csproj, since it now has its first tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ErrorResults.ToProblem maps a domain Error to the exact JSON shape of ADR-0009 / API-12 / API-13: type and title from RFC 9110, the code/ message/errors/details extensions, and traceId (filled by ASP.NET Core's own ProblemDetailsService). ErrorStatusCodes only maps the codes exercised so far (validation_failed, not_found, concurrency_conflict); a code missing from it falls back to 500 instead of being hidden, since that is a programming mistake, not a business outcome. UnexpectedErrorExceptionHandler reuses the same mapping for unhandled exceptions, with the generic message of API-14 (docs/api/ conventions.md) and no exception detail in the response body. Covered by unit tests against the mapping function directly (DefaultHttpContext), not WebApplicationFactory: attempts to add a test-only endpoint through IStartupFilter did not register reliably under minimal hosting. RouteGroupTests keeps one real WebApplicationFactory check that /api/v1 mounts without the pipeline breaking (API-01); no feature registers a route under it yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
API-14 fixes the verbatim Portuguese message for an unexpected failure (500 unexpected_error), used by UnexpectedErrorExceptionHandler, and adds the corresponding row to the error codes table. FluentValidation.DependencyInjectionExtensions is added to Directory.Packages.props (version matches FluentValidation 12.1.1) for the first issue that registers a concrete validator; no project references it yet, since there is no concrete validator to register. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Sep 28, 2026
MendesMat
added a commit
that referenced
this pull request
Sep 28, 2026
…-0003 (#24) ## What Marks that ADR-0008 supersedes the validation part of ADR-0003, in the front matter of both records and in the ADR index. ## Why Two accepted ADRs disagreed. ADR-0003 says cross-cutting validation is applied through endpoint filters; ADR-0008, accepted later, decides that a decorator around the command handlers runs the FluentValidation validator, and that is what `ValidatingCommandHandler` does since #22. The owner approved recording the relation. Following `docs/agents/workflows/record-a-decision.md`, only the front matter and the index change; the text of both accepted records stays as it is. ## How to test Documentation only. Check the front matter of `docs/adr/0003-use-minimal-apis-grouped-by-feature.md` and `docs/adr/0008-validate-input-with-fluentvalidation.md`, and the 0003 row of `docs/adr/README.md`. ## Checklist - [x] Developed test-first (ADR-0033): not applicable, documentation only - [x] `dotnet test --solution ControlService.slnx` passes locally: no code change - [x] Documentation updated (ADR front matter and index) - [x] Rule IDs covered or changed: none (ADR-0003, ADR-0008) - [x] User-facing messages: none
MendesMat
added a commit
that referenced
this pull request
Sep 28, 2026
Part of #6 (follow-ups found in the review after #22 was merged) ## What Fixes the gaps between the error mapping merged in #22 and the documentation, adds the real HTTP integration tests the issue asked for, adds the missing `IQueryHandler`, and updates the agent guides. ## Why - **Every ADR-0009 code is mapped.** The domain already returns `link_invalid`, `system_record`, `self_deactivation` and `not_inactive`; the table did not list them, so they would have become 500 responses. `account_inactive` maps to 401 (during a session); the sign-in endpoint returns its 403 explicitly. - **An unmapped code is an unexpected failure (API-12).** It used to return a 500 exposing the code and its message, with nothing logged. It now throws, so the exception handler logs it and answers with `unexpected_error` (API-14). - **Typed result (ADR-0003).** `error.ToProblem()` returns `ProblemHttpResult`, so endpoints can declare `Results<Ok<T>, ProblemHttpResult>` for the OpenAPI document. Its unused `HttpContext` parameter is gone. - **Real integration tests (issue #6 "Done when").** The first attempt failed because an `IStartupFilter` receives a plain `ApplicationBuilder`, not an `IEndpointRouteBuilder`, so the test endpoints were never mapped. With `UseRouting` + `UseEndpoints`, `ErrorResponseTests` now checks through the whole pipeline the documented validation JSON, the generic 500 for an exception and for an unmapped code. That also covers the exception handler wiring in `Program.cs`, which no test exercised before. - **`IQueryHandler<TQuery, TResponse>`** was in the scope of #6 and was missed. - **Docs:** API-15 (the `validation_failed` message, which only appeared in an example); the architecture guide describes `ToProblem`, the code table, `account_inactive` and how a handler turns a field-less value object error into a field error; the testing guide gains the startup filter and `DefaultHttpContext` gotchas and loses the stale `TEMPORARY` notes; rule 3 of `AGENTS.md` and the git workflow now cover pull request bodies. ## How to test ```bash dotnet test --solution ControlService.slnx ``` 137/137 pass, build with 0 warnings, `dotnet format --verify-no-changes` clean. ## Checklist - [x] Developed test-first (ADR-0033), in autonomous mode at the owner's request. The table and the unmapped-code behavior started Red. `ErrorResponseTests` had no Red: it covers wiring of existing behavior; unregistering the exception handler makes both 500 tests fail. `IQueryHandler` is an interface with no behavior. - [x] `dotnet test --solution ControlService.slnx` passes locally - [x] Documentation updated (`docs/api/conventions.md`, `docs/agents/`, `AGENTS.md`) - [x] Rule IDs: API-12, API-13, API-14, API-15 (new), ADR-0003, ADR-0007, ADR-0009 - [x] User-facing messages are verbatim (API-14, API-15)
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.
Closes #6
What
Adds the plumbing every future use case will share: the in-house command handler contract, a FluentValidation decorator that runs before it, and the translation of
Result/Errorfailures into the Problem Details shape documented for the API.Why
ADR-0007 (CQRS without MediatR), ADR-0008 (validate with FluentValidation) and ADR-0009 (Result pattern + Problem Details) define these building blocks; this pull request implements them.
docs/api/conventions.mdAPI-12/API-13 describe the error JSON shape, and API-14 (added here) fixes the verbatim message for an unexpected failure.How to test
dotnet test --solution ControlService.slnx123/123 tests pass;
dotnet build ControlService.slnx -v q -clp:Summaryis clean (0 warnings);dotnet format ControlService.slnx --verify-no-changesis clean.Decisions taken along the way (the owner confirmed all of these before or during the work)
Error→ HTTP status. A small table in the API project (ErrorStatusCodes),code → status, mirroring the ADR-0009 table. A code missing from it falls back to 500 without hiding the code/message, since that is a programming mistake, not a business outcome — kept distinct fromUnexpectedErrorExceptionHandler, which handles a real unhandled exception with the generic API-14 message and never exposes the exception itself.Error.Errorgained optionalFields(camelCase path → messages) andDetails(free-form data, e.g.updatedByName) instead of a separateValidationErrorsubtype, so the API layer has one shape to translate.FluentValidation.DependencyInjectionExtensionswas added toDirectory.Packages.props(version matches the already-approvedFluentValidation12.1.1) as the issue asked, but no project references it yet: nothing to register.ICurrentUserandTimeProviderwere left out of this issue. Nothing here needs them, andICurrentUser's shape depends on the JWT claims that only exist from issue Authentication: sign-in, sessions and the Admin's first access #8 onward; they will be introduced with the audit interceptor in issue Persistence: EF Core, PostgreSQL, audit, concurrency and seeded system records #7.WebApplicationFactoryintegration test with a test-only endpoint injected throughIStartupFilter. That endpoint never registered reliably under minimal hosting (repeated 404s I could not root-cause in the time available), so I switched to unit-testingErrorResults.ToProblem(error, httpContext)directly against aDefaultHttpContext— same translation logic, no HTTP server. One realWebApplicationFactorytest (RouteGroupTests) still confirms/api/v1mounts without breaking the pipeline (API-01); that piece has no meaningful Red since no feature registers a route under it yet.AddProblemDetails()already fills thetraceIdextension by default (Activity.Current?.Id), so my own explicit line was dead duplication — removed in the Refactor step.Checklist
dotnet test --solution ControlService.slnxpasses locally (123/123)docs/api/conventions.md(API-14 and the error codes table)