Files
DocumentService/DocumentOperator.API/Middleware/ExceptionHandlingMiddleware.cs
OlgunR c5db216f15 Add Swiss QR Code extraction endpoint
Added a new `/extract-swiss-qr-code` endpoint to extract and parse Swiss QR Codes from the last page of a PDF document. Implemented the `ExtractSwissQrCode` handler method, along with helper methods to map domain value objects (`SwissQrCodeData` and `AddressData`) to DTOs.

Updated `ExceptionHandlingMiddleware` to handle the new `SwissQrCodeNotFoundException` with a 404 Not Found response.

Added integration tests in `ExtractSwissQrCodeEndpointTests` to validate the endpoint's behavior for valid requests, invalid Base64 input, empty references, and empty PDFs. Introduced a helper method to load embedded PDF resources as Base64 strings for testing.

Updated `using` directives to include necessary namespaces for the new feature and exception handling.
2026-06-26 08:50:07 +02:00

139 lines
5.0 KiB
C#

using DocumentOperator.Domain.Common.Exceptions;
using DocumentOperator.Domain.Exceptions;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using System.Net;
using System.Text.Json;
namespace DocumentOperator.API.Middleware;
/// <summary>
/// Central exception handling middleware
/// Maps exceptions to HTTP status codes and RFC 7807 Problem Details
/// </summary>
public class ExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception: {ExceptionMessage}", ex.Message);
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var (statusCode, problemDetails) = MapExceptionToProblemDetails(exception, context);
context.Response.StatusCode = (int)statusCode;
context.Response.ContentType = "application/problem+json";
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
await context.Response.WriteAsync(JsonSerializer.Serialize(problemDetails, options));
}
private static (HttpStatusCode StatusCode, ProblemDetails ProblemDetails) MapExceptionToProblemDetails(
Exception exception,
HttpContext context)
{
return exception switch
{
// FluentValidation (400 Bad Request)
ValidationException validationEx => (
HttpStatusCode.BadRequest,
new ProblemDetails
{
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
Title = "Validation Error",
Status = (int)HttpStatusCode.BadRequest,
Detail = string.Join("; ", validationEx.Errors.Select(e => e.ErrorMessage)),
Instance = context.Request.Path
}
),
// Domain Validation Exception (400 Bad Request)
DomainValidationException domainEx => (
HttpStatusCode.BadRequest,
new ProblemDetails
{
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
Title = "Domain Validation Error",
Status = (int)HttpStatusCode.BadRequest,
Detail = domainEx.Message,
Instance = context.Request.Path
}
),
// Not Found Exception (404 Not Found)
NotFoundException notFoundEx => (
HttpStatusCode.NotFound,
new ProblemDetails
{
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4",
Title = "Resource Not Found",
Status = (int)HttpStatusCode.NotFound,
Detail = notFoundEx.Message,
Instance = context.Request.Path
}
),
// Swiss QR Code Not Found Exception (404 Not Found)
SwissQrCodeNotFoundException qrNotFoundEx => (
HttpStatusCode.NotFound,
new ProblemDetails
{
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4",
Title = "Swiss QR Code Not Found",
Status = (int)HttpStatusCode.NotFound,
Detail = qrNotFoundEx.Message,
Instance = context.Request.Path
}
),
// PDF Processing Exception (500 Internal Server Error)
PdfProcessingException pdfEx => (
HttpStatusCode.InternalServerError,
new ProblemDetails
{
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1",
Title = "PDF Processing Error",
Status = (int)HttpStatusCode.InternalServerError,
Detail = pdfEx.Message,
Instance = context.Request.Path
}
),
// Generic Exception (500 Internal Server Error)
_ => (
HttpStatusCode.InternalServerError,
new ProblemDetails
{
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1",
Title = "Internal Server Error",
Status = (int)HttpStatusCode.InternalServerError,
Detail = "An unexpected error occurred. Please contact support.",
Instance = context.Request.Path
}
)
};
}
}