Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
6.5 KiB
06 — HTTP Conventions (Suite-Standard Wire Layer)
Spec source: ../../../suite/_docs/00_top_level_architecture.md § "Error Response Format" + § "Pagination". These are suite-wide — all .NET services (missions, annotations, admin, satellite-provider) are expected to emit the same shapes.
Implementation status: ⚠ DIVERGES from suite spec on entity bodies (PascalCase) and on the error envelope's missing errors field. The error envelope IS already camelCase on case (an accidental match — the anonymous object literal uses lowercase property names). See Caveats.
Files: Middleware/ErrorHandlingMiddleware.cs, DTOs/ErrorResponse.cs, DTOs/PaginatedResponse.cs
1. High-Level Overview
Purpose: Implement the suite's two cross-cutting wire conventions:
- Error envelope —
{ statusCode, message, errors? }(camelCase,errorsisobject?keyed by field name) — emitted by the global exception → JSON middleware. - Paginated response envelope —
{ items, totalCount, page, pageSize }(camelCase) — wrapped around list endpoints.
Architectural pattern: ASP.NET Core middleware (pipeline interceptor) + plain DTO types.
Upstream dependencies: None internally.
Downstream consumers: 07_host (registers middleware first in pipeline); 02_mission_planning (consumes PaginatedResponse<Mission> from MissionService.GetMissions); every component benefits indirectly from the global error handler.
2. Internal Interface
public class ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger) {
Task Invoke(HttpContext context);
}
public class ErrorResponse { // currently unused on the wire (Caveats)
int StatusCode;
string Message;
List<string>?Errors; // wrong shape per spec — see Caveats
}
public class PaginatedResponse<T> {
List<T> Items;
int TotalCount;
int Page;
int PageSize;
}
3. External API
Not an endpoint owner — defines the error response wire shape (which deviates from spec today).
Spec-mandated shape (../../../suite/_docs/00_top_level_architecture.md § Error Response Format)
{
"statusCode": 400,
"message": "Missing required fields",
"errors": {
"Name": ["Name is required"]
}
}
Code's actual shape (anonymous object via JsonSerializer.Serialize(new { statusCode, message }) with no naming policy override)
{
"statusCode": 404,
"message": "Vehicle <guid> not found"
}
Property names are camelCase (the anonymous-type property names statusCode / message are written lowercase-first in code, and System.Text.Json preserves them as-is when no JsonNamingPolicy is configured). Two divergences from spec remain: no errors field, and the ErrorResponse DTO is unused (middleware writes the anonymous object instead, and the DTO's Errors: List<string>? is the wrong shape per spec — should be object? keyed by field name).
Status code mapping (consistent with spec where there's overlap)
| Exception type | HTTP status |
|---|---|
KeyNotFoundException |
404 Not Found |
ArgumentException (base — covers ArgumentNullException, etc.) |
400 Bad Request |
InvalidOperationException |
409 Conflict |
| anything else | 500 Internal Server Error (message generic, exception logged) |
4. Data Access Patterns
None.
5. Implementation Details
State Management: Stateless.
Key Dependencies: System.Text.Json (response serialization), Microsoft.Extensions.Logging.ILogger<T>.
Error Handling Strategy: This component IS the error handler. Recoverable domain failures (KeyNotFound, Argument, InvalidOperation) are mapped to specific status codes with the exception's message; everything else is a 500 with a sanitized message and a logged stack trace.
6. Extensions and Helpers
ErrorResponse and PaginatedResponse<T> could move to a shared helpers folder if a future component spawns more wire-shape concerns; today only PaginatedResponse<T> is consumed (by MissionService.GetMissions).
7. Caveats & Edge Cases
- PascalCase wire shape for entity bodies vs. suite-spec camelCase. Controller responses that return entities (
Ok(vehicle),Ok(mission),PaginatedResponse<Mission>) serialize PascalCase property names because the entity / DTO types are declared PascalCase and noJsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCaseis configured. Exception: the global error envelope IS already camelCase (the anonymous object literal uses lowercase property names directly). ErrorResponseDTO is dead on the wire — middleware writes an anonymous object instead. AND the DTO'sErrorsisList<string>?while spec saysobject?(per-field validation arrays keyed by field name). Two divergences in one DTO. Note: even if the DTO were used, its property names would serialize as PascalCase (StatusCode,Message,Errors) and would diverge from spec on case — additional reason the anonymous-object workaround happens to align with spec on case.- No
errorsfield emitted — even when it would be relevant (validation failures). Today the codebase has no validation attributes anyway, so 400s come fromArgumentExceptionwith a singleMessage. When validation is added, the spec's per-field shape will need to be implemented. InvalidOperationException → 409is non-standard; any third-party library throwingInvalidOperationExceptionfor an unrelated reason becomes a 409, masking the real cause. In this codebase the only intentional use isVehicleService.DeleteVehicle("vehicle is referenced by missions" — a true 409).- No correlation ID / request ID in the error body — production support has to grep logs by timestamp.
PaginatedResponse<T>is used by exactly one endpoint (missions list).vehiclesandwaypointslistings are unpaginated by spec, so this is correct.
8. Dependency Graph
Must be implemented after: nothing.
Can be implemented in parallel with: 04_persistence, 05_identity.
Blocks: 07_host (pipeline order matters — must be in DI by the time app.UseMiddleware<ErrorHandlingMiddleware> runs); 02_mission_planning (uses PaginatedResponse<T>).
9. Logging Strategy
| Log Level | When | Example |
|---|---|---|
| ERROR | Unhandled exception caught by the catch-all branch | Unhandled exception (stack trace attached via LogError(ex, ...)) |
(No INFO/DEBUG/WARN emitted by this component.)