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.
4.3 KiB
Module: Azaion.Missions.Middleware
NOTE (forward-looking): post-rename namespace. Today's source still lives under
Azaion.Flights.Middleware. Renames tracked under Jira AZ-EPIC child B5.
Files (1): Middleware/ErrorHandlingMiddleware.cs
Purpose
Global exception → JSON error response mapper. Wraps the rest of the request pipeline and converts a fixed set of exception types to specific HTTP status codes; everything else becomes a 500.
Public Interface
public class ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger) {
public Task Invoke(HttpContext context);
}
Standard ASP.NET Core middleware shape (primary-constructor variant).
Internal Logic
try { await next(context); }
catch (KeyNotFoundException ex) → 404 NotFound, body: { statusCode, message = ex.Message }
catch (ArgumentException ex) → 400 BadRequest, body: { statusCode, message = ex.Message }
catch (InvalidOperationException ex) → 409 Conflict, body: { statusCode, message = ex.Message }
catch (Exception ex) → 500 InternalServerError, body: { statusCode, message = "Internal server error" }
PLUS: logger.LogError(ex, "Unhandled exception");
Body is serialized via JsonSerializer.Serialize(new { statusCode, message }) — an anonymous object literal, NOT the DTOs.ErrorResponse type. The anonymous-type property names are written lowercase-first in code (statusCode, message); System.Text.Json preserves them as-is when no JsonNamingPolicy is configured, so the wire shape is {"statusCode":..., "message":"..."} — camelCase by accidental match with the suite spec. (The unused DTOs.ErrorResponse type, by contrast, declares PascalCase properties and would serialize PascalCase if it were ever used directly.)
Content-Type is set to application/json. Response body is written via WriteAsync (string).
Dependencies
System.Net(HttpStatusCode)System.Text.Json(JsonSerializer)- ASP.NET Core middleware abstractions (transitive via
Microsoft.NET.Sdk.Web) Microsoft.Extensions.Logging.ILogger<T>(transitive)
No internal dependencies.
Consumers
Program.cs—app.UseMiddleware<ErrorHandlingMiddleware>();is called BEFOREUseCors,UseAuthentication,UseAuthorization,UseSwagger,UseSwaggerUI,MapControllers.
Configuration / External Integrations / Security
None.
Tests
None present.
Notes / Smells
DTOs.ErrorResponseis unused here — middleware writes an anonymous object instead. Partial spec divergence:../../suite/_docs/00_top_level_architecture.md§ Error Response Format mandates{ "statusCode", "message", "errors": object? }(camelCase,errorsis an object of per-field arrays). The middleware's anonymous-object output IS already camelCase on case ({"statusCode":..., "message":"..."}) but missing theerrorsfield; the staticErrorResponseDTO has the wrong shape (List<string>? Errorsinstead ofobject?) and is dead code.- Entity / DTO body case-style is PascalCase across the rest of the API (controller responses serialize entities and
PaginatedResponse<T>viaSystem.Text.Jsondefaults with no naming policy override). The error envelope's accidental camelCase match documented in point 1 does NOT extend to those responses — seearchitecture.mdADR-002. InvalidOperationException→ 409 Conflict is a non-standard mapping. The .NET BCL throws this for many "wrong state at the moment" conditions; in this codebase it is used byVehicleService.DeleteVehicleto report "vehicle is referenced by missions" (a true 409). But any third-party library throwingInvalidOperationExceptionfor an unrelated reason would also surface as 409, masking the real cause.- Generic 500 swallows the message (good for security — no internal detail leaked) and logs the exception (good for diagnostics). No correlation ID is included in the response, so production support has to grep logs by timestamp.
- Order of catches matters:
ArgumentExceptionis base ofArgumentNullException/ArgumentOutOfRangeException, so those also become 400 (correct).InvalidOperationExceptionahead of genericExceptionis correct.