mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-22 19:31:18 +00:00
[AZ-353][AZ-354][AZ-356] Refactor 03 batch 2: harden API surface
AZ-353: Centralize 500 handling via GlobalExceptionHandler / AddProblemDetails / UseExceptionHandler. Sanitized ProblemDetails body carries a generic title, RFC9110 type link, and the request's TraceIdentifier as correlationId; the leaky exception message stays server-side in the ERR log entry. Strip per-endpoint try/catch (Exception) wrappers and the unused ILogger<Program> parameters they served. Preserve the typed ArgumentException catch in CreateRoute (AC-3). The handler maps BadHttpRequestException back to its framework-supplied StatusCode so model-binding / malformed-body failures stay 4xx instead of being promoted to 500. AZ-354: Extract CorsConfigurationValidator (pure static helpers) and wire it into Program.cs. Production with empty CorsConfig:AllowedOrigins and no CorsConfig:AllowAnyOrigin opt-in now throws InvalidOperationException at host startup. Development keeps the permissive default but logs a warning post-build. Adds the explicit CorsConfig:AllowAnyOrigin escape hatch. AZ-356: GetSatelliteTilesByMgrs and UploadImage now return Results.Problem(StatusCode 501) with ProblemDetails. Added .ProducesProblem(501) so swagger.json documents the not-implemented status. Tests: SatelliteProvider.Tests now references SatelliteProvider.Api (downward, idiomatic) so unit tests can reach the new helpers. +9 CorsConfigurationValidator unit tests, +3 GlobalExceptionHandler unit tests, +3 StubAndErrorContractTests integration tests (added to smoke + full suites). 58/58 unit + 5/5 smoke + 3/3 stub-contract pass. Code review verdict: PASS. Batch report: _docs/03_implementation/batch_08_report.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SatelliteProvider.Api;
|
||||
|
||||
public sealed class GlobalExceptionHandler : IExceptionHandler
|
||||
{
|
||||
private readonly ILogger<GlobalExceptionHandler> _logger;
|
||||
|
||||
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> TryHandleAsync(
|
||||
HttpContext httpContext,
|
||||
Exception exception,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Framework-level request-binding/parsing failures carry their own HTTP status
|
||||
// (typically 400/415). Honor that status so we don't promote a client error to 5xx.
|
||||
if (exception is BadHttpRequestException badRequest)
|
||||
{
|
||||
await WriteClientErrorAsync(httpContext, badRequest, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
var correlationId = httpContext.TraceIdentifier;
|
||||
|
||||
_logger.LogError(
|
||||
exception,
|
||||
"Unhandled exception while processing {Method} {Path} (correlationId={CorrelationId})",
|
||||
httpContext.Request.Method,
|
||||
httpContext.Request.Path,
|
||||
correlationId);
|
||||
|
||||
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
|
||||
var problem = new ProblemDetails
|
||||
{
|
||||
Status = StatusCodes.Status500InternalServerError,
|
||||
Title = "Internal Server Error",
|
||||
Detail = "An unexpected error occurred. Use the correlationId to look up the server log entry.",
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc9110#name-500-internal-server-error",
|
||||
};
|
||||
problem.Extensions["correlationId"] = correlationId;
|
||||
|
||||
await httpContext.Response.WriteAsJsonAsync(
|
||||
problem,
|
||||
options: null,
|
||||
contentType: "application/problem+json",
|
||||
cancellationToken: cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task WriteClientErrorAsync(
|
||||
HttpContext httpContext,
|
||||
BadHttpRequestException badRequest,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
httpContext.Response.StatusCode = badRequest.StatusCode;
|
||||
|
||||
var problem = new ProblemDetails
|
||||
{
|
||||
Status = badRequest.StatusCode,
|
||||
Title = "Bad Request",
|
||||
Detail = badRequest.Message,
|
||||
};
|
||||
|
||||
await httpContext.Response.WriteAsJsonAsync(
|
||||
problem,
|
||||
options: null,
|
||||
contentType: "application/problem+json",
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user