From 7c3e6504671a815018e93acb63341550b9c8fb3d Mon Sep 17 00:00:00 2001 From: Pc Date: Thu, 30 Mar 2023 23:04:24 +0330 Subject: [PATCH] feat: Use built-in problem-details .Net 7 instead of Hellang.Middleware.ProblemDetails --- src/BuildingBlocks/BuildingBlocks.csproj | 1 - .../Commands/V1/CreateBookingEndpoint.cs | 20 +- .../InfrastructureExtensions.cs | 6 +- .../ProblemDetailsExtensions.cs | 183 +++++++++--------- .../V1/CreateAircraftEndpoint.cs | 21 +- .../V1/CreateAirportEndpoint.cs | 21 +- .../InfrastructureExtensions.cs | 5 +- .../ProblemDetailsExtensions.cs | 167 ++++++++-------- .../CreatingFlight/V1/CreateFlightEndpoint.cs | 20 +- .../DeletingFlight/V1/DeleteFlightEndpoint.cs | 20 +- .../V1/GetAvailableFlightsEndpoint.cs | 20 +- .../V1/GetFlightByIdEndpoint.cs | 23 +-- .../UpdatingFlight/V1/UpdateFlightEndpoint.cs | 21 +- .../CreatingSeat/V1/CreateSeatEndpoint.cs | 21 +- .../V1/GetAvailableSeatsEndpoint.cs | 22 +-- .../Commands/V1/ReserveSeatEndpoint.cs | 21 +- .../InfrastructureExtensions.cs | 7 +- .../ProblemDetailsExtensions.cs | 174 +++++++++-------- .../V1/RegisterNewUserEndpoint.cs | 15 +- .../InfrastructureExtensions.cs | 6 +- .../ProblemDetailsExtensions.cs | 173 +++++++++-------- .../V1/CompleteRegisterPassengerEndpoint.cs | 20 +- .../Queries/V1/GetPassengerByIdEndpoint.cs | 22 +-- 23 files changed, 398 insertions(+), 611 deletions(-) diff --git a/src/BuildingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BuildingBlocks.csproj index 2ce1533..6ef8e41 100644 --- a/src/BuildingBlocks/BuildingBlocks.csproj +++ b/src/BuildingBlocks/BuildingBlocks.csproj @@ -44,7 +44,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/src/Services/Booking/src/Booking/Booking/Features/CreatingBook/Commands/V1/CreateBookingEndpoint.cs b/src/Services/Booking/src/Booking/Booking/Features/CreatingBook/Commands/V1/CreateBookingEndpoint.cs index 4b9d0f2..c005d12 100644 --- a/src/Services/Booking/src/Booking/Booking/Features/CreatingBook/Commands/V1/CreateBookingEndpoint.cs +++ b/src/Services/Booking/src/Booking/Booking/Features/CreatingBook/Commands/V1/CreateBookingEndpoint.cs @@ -1,7 +1,6 @@ namespace Booking.Booking.Features.CreatingBook.Commands.V1; using BuildingBlocks.Web; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -18,25 +17,10 @@ public class CreateBookingEndpoint : IMinimalEndpoint { builder.MapPost($"{EndpointConfig.BaseApiPath}/booking", CreateBooking) .RequireAuthorization() - .WithTags("Booking") - .WithName("CreateBooking") .WithMetadata(new SwaggerOperationAttribute("Create Booking", "Create Booking")) .WithApiVersionSet(builder.NewApiVersionSet("Booking").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "Booking Created", - typeof(CreateBookingResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs index 4734d5e..92de13d 100644 --- a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -3,7 +3,6 @@ using Booking.Data; using BuildingBlocks.Core; using BuildingBlocks.EventStoreDB; using BuildingBlocks.HealthCheck; -using BuildingBlocks.IdsGenerator; using BuildingBlocks.Jwt; using BuildingBlocks.Logging; using BuildingBlocks.Mapster; @@ -15,7 +14,6 @@ using BuildingBlocks.Swagger; using BuildingBlocks.Web; using Figgle; using FluentValidation; -using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -72,7 +70,7 @@ public static class InfrastructureExtensions builder.Services.AddCustomVersioning(); builder.Services.AddCustomMediatR(); builder.Services.AddValidatorsFromAssembly(typeof(BookingRoot).Assembly); - builder.Services.AddCustomProblemDetails(); + builder.Services.AddProblemDetails(); builder.Services.AddCustomMapster(typeof(BookingRoot).Assembly); builder.Services.AddCustomHealthCheck(); builder.Services.AddCustomMassTransit(env, typeof(BookingRoot).Assembly); @@ -94,7 +92,7 @@ public static class InfrastructureExtensions var env = app.Environment; var appOptions = app.GetOptions(nameof(AppOptions)); - app.UseProblemDetails(); + app.UseCustomProblemDetails(); app.UseSerilogRequestLogging(options => { options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; diff --git a/src/Services/Booking/src/Booking/Extensions/Infrastructure/ProblemDetailsExtensions.cs b/src/Services/Booking/src/Booking/Extensions/Infrastructure/ProblemDetailsExtensions.cs index 9e3aa31..32c2ffb 100644 --- a/src/Services/Booking/src/Booking/Extensions/Infrastructure/ProblemDetailsExtensions.cs +++ b/src/Services/Booking/src/Booking/Extensions/Infrastructure/ProblemDetailsExtensions.cs @@ -1,107 +1,106 @@ -using BuildingBlocks.Exception; -using Grpc.Core; -using Hellang.Middleware.ProblemDetails; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - namespace Booking.Extensions.Infrastructure; +using BuildingBlocks.Exception; +using Grpc.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; public static class ProblemDetailsExtensions { - public static IServiceCollection AddCustomProblemDetails(this IServiceCollection services) + public static WebApplication UseCustomProblemDetails(this WebApplication app) { - services.AddProblemDetails(x => + app.UseExceptionHandler(exceptionHandlerApp => { - // Control when an exception is included - x.IncludeExceptionDetails = (ctx, _) => + exceptionHandlerApp.Run(async context => { - // Fetch services from HttpContext.RequestServices - var env = ctx.RequestServices.GetRequiredService(); - return env.IsDevelopment() || env.IsStaging(); - }; - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/application-rule-validation-error", - }); + context.Response.ContentType = "application/problem+json"; - // Exception will produce and returns from our FluentValidation RequestValidationBehavior - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = (int)ex.StatusCode, - Detail = ex.Message, - Type = "https://somedomain/input-validation-rules-error", - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/bad-request-error", - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status404NotFound, - Detail = ex.Message, - Type = "https://somedomain/not-found-error", - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status500InternalServerError, - Detail = ex.Message, - Type = "https://somedomain/api-server-error", - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/application-error", - }); - - x.Map(ex => new ProblemDetails - { - Status = StatusCodes.Status400BadRequest, - Title = ex.GetType().Name, - Detail = ex.Status.Detail, - Type = "https://somedomain/grpc-error" - }); - - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/db-update-concurrency-error" - }); - - x.MapToStatusCode(StatusCodes.Status400BadRequest); - - x.MapStatusCode = context => - { - return context.Response.StatusCode switch + if (context.RequestServices.GetService() is { } problemDetailsService) { - StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode - { - Status = context.Response.StatusCode, - Title = "identity exception", - Detail = "You are not Authorized", - Type = "https://somedomain/identity-error", - }, + var exceptionHandlerFeature = context.Features.Get(); + var exceptionType = exceptionHandlerFeature?.Error; - _ => new StatusCodeProblemDetails(context.Response.StatusCode) - }; - }; + if (exceptionType is not null) + { + (string Detail, string Type, string Title, int StatusCode) details = exceptionType switch + { + ConflictException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + ValidationException validationException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = (int)validationException.StatusCode + ), + BadRequestException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + NotFoundException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status404NotFound + ), + AppException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + DbUpdateConcurrencyException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + RpcException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + _ => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status500InternalServerError + ) + }; + + await problemDetailsService.WriteAsync(new ProblemDetailsContext + { + HttpContext = context, + ProblemDetails = + { + Title = details.Title, + Detail = details.Detail, + Type = details.Type, + Status = details.StatusCode + } + }); + } + } + }); }); - return services; + + return app; } } diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftEndpoint.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftEndpoint.cs index c4d2fc4..4cb2608 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftEndpoint.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftEndpoint.cs @@ -3,8 +3,6 @@ namespace Flight.Aircrafts.Features.CreatingAircraft.V1; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Dtos; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -21,25 +19,10 @@ public class CreateAircraftEndpoint : IMinimalEndpoint { builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/aircraft", CreateAircraft) .RequireAuthorization() - .WithTags("Flight") - .WithName("CreateAircraft") .WithMetadata(new SwaggerOperationAttribute("Create Aircraft", "Create Aircraft")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "Aircraft Created", - typeof(CreateAircraftResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Airports/Features/CreatingAirport/V1/CreateAirportEndpoint.cs b/src/Services/Flight/src/Flight/Airports/Features/CreatingAirport/V1/CreateAirportEndpoint.cs index 64308bd..a383539 100644 --- a/src/Services/Flight/src/Flight/Airports/Features/CreatingAirport/V1/CreateAirportEndpoint.cs +++ b/src/Services/Flight/src/Flight/Airports/Features/CreatingAirport/V1/CreateAirportEndpoint.cs @@ -3,8 +3,6 @@ namespace Flight.Airports.Features.CreatingAirport.V1; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Dtos; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -21,25 +19,10 @@ public class CreateAirportEndpoint : IMinimalEndpoint { builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/airport", CreateAirport) .RequireAuthorization() - .WithTags("Flight") - .WithName("CreateAirport") .WithMetadata(new SwaggerOperationAttribute("Create Airport", "Create Airport")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "Airport Created", - typeof(CreateAirportResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs index 33bccff..82558e7 100644 --- a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -19,7 +19,6 @@ using Flight.Data; using Flight.Data.Seed; using Flight.GrpcServer.Services; using FluentValidation; -using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -51,7 +50,7 @@ public static class InfrastructureExtensions }); builder.Services.AddCustomMediatR(); - builder.Services.AddCustomProblemDetails(); + builder.Services.AddProblemDetails(); var appOptions = builder.Services.GetOptions(nameof(AppOptions)); Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name)); @@ -99,7 +98,7 @@ public static class InfrastructureExtensions var env = app.Environment; var appOptions = app.GetOptions(nameof(AppOptions)); - app.UseProblemDetails(); + app.UseCustomProblemDetails(); app.UseSerilogRequestLogging(options => { options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; diff --git a/src/Services/Flight/src/Flight/Extensions/Infrastructure/ProblemDetailsExtensions.cs b/src/Services/Flight/src/Flight/Extensions/Infrastructure/ProblemDetailsExtensions.cs index 006dd8b..a6a070d 100644 --- a/src/Services/Flight/src/Flight/Extensions/Infrastructure/ProblemDetailsExtensions.cs +++ b/src/Services/Flight/src/Flight/Extensions/Infrastructure/ProblemDetailsExtensions.cs @@ -1,98 +1,107 @@ -using System; using BuildingBlocks.Exception; -using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; namespace Flight.Extensions.Infrastructure; +using Grpc.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; using Microsoft.EntityFrameworkCore; public static class ProblemDetailsExtensions { - public static IServiceCollection AddCustomProblemDetails(this IServiceCollection services) + public static WebApplication UseCustomProblemDetails(this WebApplication app) { - services.AddProblemDetails(x => + app.UseExceptionHandler(exceptionHandlerApp => { - // Control when an exception is included - x.IncludeExceptionDetails = (ctx, _) => + exceptionHandlerApp.Run(async context => { - // Fetch services from HttpContext.RequestServices - var env = ctx.RequestServices.GetRequiredService(); - return env.IsDevelopment() || env.IsStaging(); - }; - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/application-rule-validation-error" - }); + context.Response.ContentType = "application/problem+json"; - // Exception will produce and returns from our FluentValidation RequestValidationBehavior - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = (int)ex.StatusCode, - Detail = ex.Message, - Type = "https://somedomain/input-validation-rules-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/bad-request-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status404NotFound, - Detail = ex.Message, - Type = "https://somedomain/not-found-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status500InternalServerError, - Detail = ex.Message, - Type = "https://somedomain/api-server-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/application-error" - }); - - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/db-update-concurrency-error" - }); - - x.MapToStatusCode(StatusCodes.Status400BadRequest); - - x.MapStatusCode = context => - { - return context.Response.StatusCode switch + if (context.RequestServices.GetService() is { } problemDetailsService) { - StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode - { - Status = context.Response.StatusCode, - Title = "identity exception", - Detail = "You are not Authorized", - Type = "https://somedomain/identity-error" - }, + var exceptionHandlerFeature = context.Features.Get(); + var exceptionType = exceptionHandlerFeature?.Error; - _ => new StatusCodeProblemDetails(context.Response.StatusCode) - }; - }; + if (exceptionType is not null) + { + (string Detail, string Type, string Title, int StatusCode) details = exceptionType switch + { + ConflictException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + ValidationException validationException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = (int)validationException.StatusCode + ), + BadRequestException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + NotFoundException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status404NotFound + ), + AppException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + DbUpdateConcurrencyException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + RpcException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + _ => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status500InternalServerError + ) + }; + + await problemDetailsService.WriteAsync(new ProblemDetailsContext + { + HttpContext = context, + ProblemDetails = + { + Title = details.Title, + Detail = details.Detail, + Type = details.Type, + Status = details.StatusCode + } + }); + } + } + }); }); - return services; + + return app; } } diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlightEndpoint.cs b/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlightEndpoint.cs index 867b677..fe584cc 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlightEndpoint.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlightEndpoint.cs @@ -4,7 +4,6 @@ using System; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -24,25 +23,10 @@ public class CreateFlightEndpoint : IMinimalEndpoint { builder.MapPost($"{EndpointConfig.BaseApiPath}/flight", CreateFlight) .RequireAuthorization() - .WithTags("Flight") - .WithName("CreateFlight") .WithMetadata(new SwaggerOperationAttribute("Create Flight", "Create Flight")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status201Created, - "Flight Created", - typeof(CreateFlightResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces(StatusCodes.Status201Created) + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlightEndpoint.cs b/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlightEndpoint.cs index 8f172fa..9588584 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlightEndpoint.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlightEndpoint.cs @@ -3,8 +3,6 @@ using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Flight.Flights.Dtos; -using Hellang.Middleware.ProblemDetails; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -17,24 +15,10 @@ public class DeleteFlightEndpoint : IMinimalEndpoint { builder.MapDelete($"{EndpointConfig.BaseApiPath}/flight/{{id}}", DeleteFlight) .RequireAuthorization() - .WithTags("Flight") - .WithName("DeleteFlight") .WithMetadata(new SwaggerOperationAttribute("Delete Flight", "Delete Flight")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status204NoContent, - "Flight Deleted")) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces(StatusCodes.Status204NoContent) + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlightsEndpoint.cs b/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlightsEndpoint.cs index 0744247..72eca9a 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlightsEndpoint.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlightsEndpoint.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; using Dtos; -using Hellang.Middleware.ProblemDetails; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -20,25 +19,10 @@ public class GetAvailableFlightsEndpoint : IMinimalEndpoint { builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-flights", GetAvailableFlights) .RequireAuthorization() - .WithTags("Flight") - .WithName("GetAvailableFlights") .WithMetadata(new SwaggerOperationAttribute("Get Available Flights", "Get Available Flights")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "GetAvailableFlights", - typeof(GetAvailableFlightsResult))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightByIdEndpoint.cs b/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightByIdEndpoint.cs index 8d32c3c..0f09943 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightByIdEndpoint.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightByIdEndpoint.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; using Dtos; -using Hellang.Middleware.ProblemDetails; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -19,28 +18,10 @@ public class GetFlightByIdEndpoint : IMinimalEndpoint { builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/{{id}}", GetById) .RequireAuthorization() - .WithTags("Flight") - .WithName("GetFlightById") .WithMetadata(new SwaggerOperationAttribute("Get Flight By Id", "Get Flight By Id")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .Produces() - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "GetFlightById", - typeof(GetFlightByIdResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlightEndpoint.cs b/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlightEndpoint.cs index 66a8d45..ffc663b 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlightEndpoint.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlightEndpoint.cs @@ -4,8 +4,6 @@ using System; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Dtos; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -22,27 +20,10 @@ public class UpdateFlightEndpoint : IMinimalEndpoint { builder.MapPut($"{EndpointConfig.BaseApiPath}/flight", UpdateFlight) .RequireAuthorization() - .WithTags("Flight") - .WithName("UpdateFlight") .WithMetadata(new SwaggerOperationAttribute("Update Flight", "Update Flight")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .Produces() .Produces(StatusCodes.Status204NoContent) - .Produces(StatusCodes.Status400BadRequest) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status204NoContent, - "Flight Updated")) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Seats/Features/CreatingSeat/V1/CreateSeatEndpoint.cs b/src/Services/Flight/src/Flight/Seats/Features/CreatingSeat/V1/CreateSeatEndpoint.cs index 8b693c2..e2192ab 100644 --- a/src/Services/Flight/src/Flight/Seats/Features/CreatingSeat/V1/CreateSeatEndpoint.cs +++ b/src/Services/Flight/src/Flight/Seats/Features/CreatingSeat/V1/CreateSeatEndpoint.cs @@ -3,8 +3,6 @@ namespace Flight.Seats.Features.CreatingSeat.V1; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Flight.Seats.Dtos; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -21,25 +19,10 @@ public class CreateSeatEndpoint : IMinimalEndpoint { builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/seat", CreateSeat) .RequireAuthorization() - .WithTags("Flight") - .WithName("CreateSeat") .WithMetadata(new SwaggerOperationAttribute("Create Seat", "Create Seat")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "Seat Created", - typeof(CreateSeatResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeatsEndpoint.cs b/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeatsEndpoint.cs index 5caa219..65c2cab 100644 --- a/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeatsEndpoint.cs +++ b/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeatsEndpoint.cs @@ -4,8 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Flight.Seats.Dtos; -using Hellang.Middleware.ProblemDetails; +using Dtos; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -20,25 +19,10 @@ public class GetAvailableSeatsEndpoint : IMinimalEndpoint { builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-seats/{{id}}", GetAvailableSeats) .RequireAuthorization() - .WithTags("Flight") - .WithName("GetAvailableSeats") .WithMetadata(new SwaggerOperationAttribute("Get Available Seats", "Get Available Seats")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "GetAvailableSeats", - typeof(GetAvailableSeatsResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Flight/src/Flight/Seats/Features/ReservingSeat/Commands/V1/ReserveSeatEndpoint.cs b/src/Services/Flight/src/Flight/Seats/Features/ReservingSeat/Commands/V1/ReserveSeatEndpoint.cs index 6eb8fb0..23ece24 100644 --- a/src/Services/Flight/src/Flight/Seats/Features/ReservingSeat/Commands/V1/ReserveSeatEndpoint.cs +++ b/src/Services/Flight/src/Flight/Seats/Features/ReservingSeat/Commands/V1/ReserveSeatEndpoint.cs @@ -3,8 +3,6 @@ namespace Flight.Seats.Features.ReservingSeat.Commands.V1; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Flight.Seats.Dtos; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -21,25 +19,10 @@ public class ReserveSeatEndpoint : IMinimalEndpoint { builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/reserve-seat", ReserveSeat) .RequireAuthorization() - .WithTags("Flight") - .WithName("ReserveSeat") .WithMetadata(new SwaggerOperationAttribute("Reserve Seat", "Reserve Seat")) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "ReserveSeat", - typeof(ReserveSeatResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs index fbb7205..563c523 100644 --- a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -3,7 +3,6 @@ using System.Threading.RateLimiting; using BuildingBlocks.Core; using BuildingBlocks.EFCore; using BuildingBlocks.HealthCheck; -using BuildingBlocks.IdsGenerator; using BuildingBlocks.Logging; using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; @@ -13,7 +12,6 @@ using BuildingBlocks.Swagger; using BuildingBlocks.Web; using Figgle; using FluentValidation; -using Hellang.Middleware.ProblemDetails; using Identity.Data; using Identity.Data.Seed; using Microsoft.AspNetCore.Builder; @@ -72,7 +70,7 @@ public static class InfrastructureExtensions builder.Services.AddCustomVersioning(); builder.Services.AddCustomMediatR(); builder.Services.AddValidatorsFromAssembly(typeof(IdentityRoot).Assembly); - builder.Services.AddCustomProblemDetails(); + builder.Services.AddProblemDetails(); builder.Services.AddCustomMapster(typeof(IdentityRoot).Assembly); builder.Services.AddCustomHealthCheck(); @@ -98,7 +96,7 @@ public static class InfrastructureExtensions app.UseForwardedHeaders(); - app.UseProblemDetails(); + app.UseCustomProblemDetails(); app.UseSerilogRequestLogging(options => { options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; @@ -107,7 +105,6 @@ public static class InfrastructureExtensions app.UseMigration(env); app.UseCorrelationId(); app.UseHttpMetrics(); - app.UseProblemDetails(); app.UseCustomHealthCheck(); app.UseIdentityServer(); app.MapMetrics(); diff --git a/src/Services/Identity/src/Identity/Extensions/Infrastructure/ProblemDetailsExtensions.cs b/src/Services/Identity/src/Identity/Extensions/Infrastructure/ProblemDetailsExtensions.cs index 829252b..c75dc07 100644 --- a/src/Services/Identity/src/Identity/Extensions/Infrastructure/ProblemDetailsExtensions.cs +++ b/src/Services/Identity/src/Identity/Extensions/Infrastructure/ProblemDetailsExtensions.cs @@ -1,98 +1,106 @@ -using System; -using BuildingBlocks.Exception; -using Hellang.Middleware.ProblemDetails; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - namespace Identity.Extensions.Infrastructure; +using BuildingBlocks.Exception; +using Grpc.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; public static class ProblemDetailsExtensions { - public static IServiceCollection AddCustomProblemDetails(this IServiceCollection services) + public static WebApplication UseCustomProblemDetails(this WebApplication app) { - services.AddProblemDetails(x => + app.UseExceptionHandler(exceptionHandlerApp => { - // Control when an exception is included - x.IncludeExceptionDetails = (ctx, _) => + exceptionHandlerApp.Run(async context => { - // Fetch services from HttpContext.RequestServices - var env = ctx.RequestServices.GetRequiredService(); - return env.IsDevelopment() || env.IsStaging(); - }; - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/application-rule-validation-error" - }); + context.Response.ContentType = "application/problem+json"; - // Exception will produce and returns from our FluentValidation RequestValidationBehavior - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = (int)ex.StatusCode, - Detail = ex.Message, - Type = "https://somedomain/input-validation-rules-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/bad-request-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status404NotFound, - Detail = ex.Message, - Type = "https://somedomain/not-found-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status500InternalServerError, - Detail = ex.Message, - Type = "https://somedomain/api-server-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/application-error" - }); - - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/db-update-concurrency-error" - }); - - x.MapToStatusCode(StatusCodes.Status400BadRequest); - - x.MapStatusCode = context => - { - return context.Response.StatusCode switch + if (context.RequestServices.GetService() is { } problemDetailsService) { - StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode - { - Status = context.Response.StatusCode, - Title = "identity exception", - Detail = "You are not Authorized", - Type = "https://somedomain/identity-error" - }, + var exceptionHandlerFeature = context.Features.Get(); + var exceptionType = exceptionHandlerFeature?.Error; - _ => new StatusCodeProblemDetails(context.Response.StatusCode) - }; - }; + if (exceptionType is not null) + { + (string Detail, string Type, string Title, int StatusCode) details = exceptionType switch + { + ConflictException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + ValidationException validationException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = (int)validationException.StatusCode + ), + BadRequestException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + NotFoundException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status404NotFound + ), + AppException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + DbUpdateConcurrencyException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + RpcException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + _ => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status500InternalServerError + ) + }; + + await problemDetailsService.WriteAsync(new ProblemDetailsContext + { + HttpContext = context, + ProblemDetails = + { + Title = details.Title, + Detail = details.Detail, + Type = details.Type, + Status = details.StatusCode + } + }); + } + } + }); }); - return services; + + return app; } } diff --git a/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUserEndpoint.cs b/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUserEndpoint.cs index 86dd71c..1bfebfa 100644 --- a/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUserEndpoint.cs +++ b/src/Services/Identity/src/Identity/Identity/Features/RegisteringNewUser/V1/RegisterNewUserEndpoint.cs @@ -3,7 +3,6 @@ namespace Identity.Identity.Features.RegisteringNewUser.V1; using System.Threading; using System.Threading.Tasks; using BuildingBlocks.Web; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -21,20 +20,10 @@ public class RegisterNewUserEndpoint : IMinimalEndpoint public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder) { builder.MapPost($"{EndpointConfig.BaseApiPath}/identity/register-user", RegisterNewUser) - .WithTags("Identity") - .WithName("RegisterUser") .WithMetadata(new SwaggerOperationAttribute("Register User", "Register User")) .WithApiVersionSet(builder.NewApiVersionSet("Identity").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "User Registered", - typeof(RegisterNewUserResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs index 8b58126..27faf7a 100644 --- a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -3,7 +3,6 @@ using BuildingBlocks.Core; using BuildingBlocks.EFCore; using BuildingBlocks.Exception; using BuildingBlocks.HealthCheck; -using BuildingBlocks.IdsGenerator; using BuildingBlocks.Jwt; using BuildingBlocks.Logging; using BuildingBlocks.Mapster; @@ -15,7 +14,6 @@ using BuildingBlocks.Swagger; using BuildingBlocks.Web; using Figgle; using FluentValidation; -using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -73,7 +71,7 @@ public static class InfrastructureExtensions builder.Services.AddCustomVersioning(); builder.Services.AddCustomMediatR(); builder.Services.AddValidatorsFromAssembly(typeof(PassengerRoot).Assembly); - builder.Services.AddCustomProblemDetails(); + builder.Services.AddProblemDetails(); builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly); builder.Services.AddHttpContextAccessor(); builder.Services.AddCustomHealthCheck(); @@ -93,7 +91,7 @@ public static class InfrastructureExtensions var env = app.Environment; var appOptions = app.GetOptions(nameof(AppOptions)); - app.UseProblemDetails(); + app.UseCustomProblemDetails(); app.UseSerilogRequestLogging(options => { options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; diff --git a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/ProblemDetailsExtensions.cs b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/ProblemDetailsExtensions.cs index c785cee..dcad206 100644 --- a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/ProblemDetailsExtensions.cs +++ b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/ProblemDetailsExtensions.cs @@ -1,97 +1,106 @@ -using BuildingBlocks.Exception; -using Hellang.Middleware.ProblemDetails; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - namespace Passenger.Extensions.Infrastructure; +using BuildingBlocks.Exception; +using Grpc.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; public static class ProblemDetailsExtensions { - public static IServiceCollection AddCustomProblemDetails(this IServiceCollection services) + public static WebApplication UseCustomProblemDetails(this WebApplication app) { - services.AddProblemDetails(x => + app.UseExceptionHandler(exceptionHandlerApp => { - // Control when an exception is included - x.IncludeExceptionDetails = (ctx, _) => + exceptionHandlerApp.Run(async context => { - // Fetch services from HttpContext.RequestServices - var env = ctx.RequestServices.GetRequiredService(); - return env.IsDevelopment() || env.IsStaging(); - }; - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/application-rule-validation-error" - }); + context.Response.ContentType = "application/problem+json"; - // Exception will produce and returns from our FluentValidation RequestValidationBehavior - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = (int)ex.StatusCode, - Detail = ex.Message, - Type = "https://somedomain/input-validation-rules-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/bad-request-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status404NotFound, - Detail = ex.Message, - Type = "https://somedomain/not-found-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status500InternalServerError, - Detail = ex.Message, - Type = "https://somedomain/api-server-error" - }); - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status400BadRequest, - Detail = ex.Message, - Type = "https://somedomain/application-error" - }); - - x.Map(ex => new ProblemDetailsWithCode - { - Title = ex.GetType().Name, - Status = StatusCodes.Status409Conflict, - Detail = ex.Message, - Type = "https://somedomain/db-update-concurrency-error" - }); - - x.MapToStatusCode(StatusCodes.Status400BadRequest); - - x.MapStatusCode = context => - { - return context.Response.StatusCode switch + if (context.RequestServices.GetService() is { } problemDetailsService) { - StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode - { - Status = context.Response.StatusCode, - Title = "identity exception", - Detail = "You are not Authorized", - Type = "https://somedomain/identity-error" - }, + var exceptionHandlerFeature = context.Features.Get(); + var exceptionType = exceptionHandlerFeature?.Error; - _ => new StatusCodeProblemDetails(context.Response.StatusCode) - }; - }; + if (exceptionType is not null) + { + (string Detail, string Type, string Title, int StatusCode) details = exceptionType switch + { + ConflictException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + ValidationException validationException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = (int)validationException.StatusCode + ), + BadRequestException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + NotFoundException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status404NotFound + ), + AppException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + DbUpdateConcurrencyException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status409Conflict + ), + RpcException => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status400BadRequest + ), + _ => + ( + exceptionType.Message, + exceptionType.GetType().ToString(), + exceptionType.GetType().Name, + context.Response.StatusCode = StatusCodes.Status500InternalServerError + ) + }; + + await problemDetailsService.WriteAsync(new ProblemDetailsContext + { + HttpContext = context, + ProblemDetails = + { + Title = details.Title, + Detail = details.Detail, + Type = details.Type, + Status = details.StatusCode + } + }); + } + } + }); }); - return services; + + return app; } } diff --git a/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerEndpoint.cs b/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerEndpoint.cs index 9327000..2c4cdc0 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerEndpoint.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerEndpoint.cs @@ -1,7 +1,6 @@ namespace Passenger.Passengers.Features.CompletingRegisterPassenger.V1; using BuildingBlocks.Web; -using Hellang.Middleware.ProblemDetails; using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; @@ -19,25 +18,10 @@ public class CompleteRegisterPassengerEndpoint : IMinimalEndpoint { builder.MapPost($"{EndpointConfig.BaseApiPath}/passenger/complete-registration", CompleteRegisterPassenger) .RequireAuthorization() - .WithTags("Passenger") - .WithName("CompleteRegisterPassenger") .WithMetadata(new SwaggerOperationAttribute("Complete Register Passenger", "Complete Register Passenger")) .WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "Register Passenger Completed", - typeof(CompleteRegisterPassengerResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder; diff --git a/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerByIdEndpoint.cs b/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerByIdEndpoint.cs index fe1e62d..c7184cd 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerByIdEndpoint.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerByIdEndpoint.cs @@ -1,12 +1,11 @@ namespace Passenger.Passengers.Features.GettingPassengerById.Queries.V1; using BuildingBlocks.Web; -using Hellang.Middleware.ProblemDetails; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; -using Passenger.Passengers.Dtos; +using Dtos; using Swashbuckle.AspNetCore.Annotations; public record GetPassengerByIdResponseDto(PassengerDto PassengerDto); @@ -17,25 +16,10 @@ public class GetPassengerByIdEndpoint : IMinimalEndpoint { builder.MapGet($"{EndpointConfig.BaseApiPath}/passenger/{{id}}", GetById) .RequireAuthorization() - .WithTags("Passenger") - .WithName("GetPassengerById") .WithMetadata(new SwaggerOperationAttribute("Get Passenger By Id", "Get Passenger By Id")) .WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build()) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status200OK, - "GetPassengerById", - typeof(GetPassengerByIdResponseDto))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status400BadRequest, - "BadRequest", - typeof(StatusCodeProblemDetails))) - .WithMetadata( - new SwaggerResponseAttribute( - StatusCodes.Status401Unauthorized, - "UnAuthorized", - typeof(StatusCodeProblemDetails))) + .Produces() + .ProducesProblem(StatusCodes.Status400BadRequest) .HasApiVersion(1.0); return builder;