mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 23:51:13 +00:00
1d89cd9997
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>
77 lines
2.5 KiB
C#
77 lines
2.5 KiB
C#
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);
|
|
}
|
|
}
|