mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-05-02 19:02:55 +08:00
Merge pull request #241 from meysamhadeli/feat/Use-builtin-problem-details-dotnet-7
feat: Use built-in problem-details .Net 7 instead of Hellang.Middlewa…
This commit is contained in:
commit
e1d99a6f24
@ -44,7 +44,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
|
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="IdGen" Version="3.0.3" />
|
<PackageReference Include="IdGen" Version="3.0.3" />
|
||||||
<PackageReference Include="Mapster" Version="7.3.0" />
|
<PackageReference Include="Mapster" Version="7.3.0" />
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
namespace Booking.Booking.Features.CreatingBook.Commands.V1;
|
namespace Booking.Booking.Features.CreatingBook.Commands.V1;
|
||||||
|
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -18,25 +17,11 @@ public class CreateBookingEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/booking", CreateBooking)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/booking", CreateBooking)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Booking")
|
|
||||||
.WithName("CreateBooking")
|
.WithName("CreateBooking")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Create Booking", "Create Booking"))
|
.WithMetadata(new SwaggerOperationAttribute("Create Booking", "Create Booking"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Booking").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Booking").Build())
|
||||||
.WithMetadata(
|
.Produces<CreateBookingResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"Booking Created",
|
|
||||||
typeof(CreateBookingResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -3,7 +3,6 @@ using Booking.Data;
|
|||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
using BuildingBlocks.EventStoreDB;
|
using BuildingBlocks.EventStoreDB;
|
||||||
using BuildingBlocks.HealthCheck;
|
using BuildingBlocks.HealthCheck;
|
||||||
using BuildingBlocks.IdsGenerator;
|
|
||||||
using BuildingBlocks.Jwt;
|
using BuildingBlocks.Jwt;
|
||||||
using BuildingBlocks.Logging;
|
using BuildingBlocks.Logging;
|
||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
@ -15,7 +14,6 @@ using BuildingBlocks.Swagger;
|
|||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -72,7 +70,7 @@ public static class InfrastructureExtensions
|
|||||||
builder.Services.AddCustomVersioning();
|
builder.Services.AddCustomVersioning();
|
||||||
builder.Services.AddCustomMediatR();
|
builder.Services.AddCustomMediatR();
|
||||||
builder.Services.AddValidatorsFromAssembly(typeof(BookingRoot).Assembly);
|
builder.Services.AddValidatorsFromAssembly(typeof(BookingRoot).Assembly);
|
||||||
builder.Services.AddCustomProblemDetails();
|
builder.Services.AddProblemDetails();
|
||||||
builder.Services.AddCustomMapster(typeof(BookingRoot).Assembly);
|
builder.Services.AddCustomMapster(typeof(BookingRoot).Assembly);
|
||||||
builder.Services.AddCustomHealthCheck();
|
builder.Services.AddCustomHealthCheck();
|
||||||
builder.Services.AddCustomMassTransit(env, typeof(BookingRoot).Assembly);
|
builder.Services.AddCustomMassTransit(env, typeof(BookingRoot).Assembly);
|
||||||
@ -94,7 +92,7 @@ public static class InfrastructureExtensions
|
|||||||
var env = app.Environment;
|
var env = app.Environment;
|
||||||
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
|
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
|
||||||
|
|
||||||
app.UseProblemDetails();
|
app.UseCustomProblemDetails();
|
||||||
app.UseSerilogRequestLogging(options =>
|
app.UseSerilogRequestLogging(options =>
|
||||||
{
|
{
|
||||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||||
|
|||||||
@ -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;
|
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.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
public static class ProblemDetailsExtensions
|
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
|
exceptionHandlerApp.Run(async context =>
|
||||||
x.IncludeExceptionDetails = (ctx, _) =>
|
|
||||||
{
|
{
|
||||||
// Fetch services from HttpContext.RequestServices
|
context.Response.ContentType = "application/problem+json";
|
||||||
var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
|
|
||||||
return env.IsDevelopment() || env.IsStaging();
|
if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService)
|
||||||
|
{
|
||||||
|
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
|
||||||
|
var exceptionType = exceptionHandlerFeature?.Error;
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
};
|
};
|
||||||
x.Map<ConflictException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-rule-validation-error",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exception will produce and returns from our FluentValidation RequestValidationBehavior
|
await problemDetailsService.WriteAsync(new ProblemDetailsContext
|
||||||
x.Map<ValidationException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
{
|
||||||
Title = ex.GetType().Name,
|
HttpContext = context,
|
||||||
Status = (int)ex.StatusCode,
|
ProblemDetails =
|
||||||
Detail = ex.Message,
|
{
|
||||||
Type = "https://somedomain/input-validation-rules-error",
|
Title = details.Title,
|
||||||
|
Detail = details.Detail,
|
||||||
|
Type = details.Type,
|
||||||
|
Status = details.StatusCode
|
||||||
|
}
|
||||||
});
|
});
|
||||||
x.Map<BadRequestException>(ex => new ProblemDetailsWithCode
|
}
|
||||||
{
|
}
|
||||||
Title = ex.GetType().Name,
|
});
|
||||||
Status = StatusCodes.Status400BadRequest,
|
});
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/bad-request-error",
|
return app;
|
||||||
});
|
|
||||||
x.Map<NotFoundException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status404NotFound,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/not-found-error",
|
|
||||||
});
|
|
||||||
x.Map<InternalServerException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status500InternalServerError,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/api-server-error",
|
|
||||||
});
|
|
||||||
x.Map<AppException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status400BadRequest,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-error",
|
|
||||||
});
|
|
||||||
|
|
||||||
x.Map<RpcException>(ex => new ProblemDetails
|
|
||||||
{
|
|
||||||
Status = StatusCodes.Status400BadRequest,
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Detail = ex.Status.Detail,
|
|
||||||
Type = "https://somedomain/grpc-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.Map<DbUpdateConcurrencyException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/db-update-concurrency-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.MapToStatusCode<ArgumentNullException>(StatusCodes.Status400BadRequest);
|
|
||||||
|
|
||||||
x.MapStatusCode = context =>
|
|
||||||
{
|
|
||||||
return context.Response.StatusCode switch
|
|
||||||
{
|
|
||||||
StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Status = context.Response.StatusCode,
|
|
||||||
Title = "identity exception",
|
|
||||||
Detail = "You are not Authorized",
|
|
||||||
Type = "https://somedomain/identity-error",
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => new StatusCodeProblemDetails(context.Response.StatusCode)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return services;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,6 @@ namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Dtos;
|
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -21,25 +19,11 @@ public class CreateAircraftEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/aircraft", CreateAircraft)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/aircraft", CreateAircraft)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("CreateAircraft")
|
.WithName("CreateAircraft")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Create Aircraft", "Create Aircraft"))
|
.WithMetadata(new SwaggerOperationAttribute("Create Aircraft", "Create Aircraft"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces<CreateAircraftResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"Aircraft Created",
|
|
||||||
typeof(CreateAircraftResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -3,8 +3,6 @@ namespace Flight.Airports.Features.CreatingAirport.V1;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Dtos;
|
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -21,25 +19,11 @@ public class CreateAirportEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/airport", CreateAirport)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/airport", CreateAirport)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("CreateAirport")
|
.WithName("CreateAirport")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Create Airport", "Create Airport"))
|
.WithMetadata(new SwaggerOperationAttribute("Create Airport", "Create Airport"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces<CreateAirportResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"Airport Created",
|
|
||||||
typeof(CreateAirportResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -19,7 +19,6 @@ using Flight.Data;
|
|||||||
using Flight.Data.Seed;
|
using Flight.Data.Seed;
|
||||||
using Flight.GrpcServer.Services;
|
using Flight.GrpcServer.Services;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -51,7 +50,7 @@ public static class InfrastructureExtensions
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddCustomMediatR();
|
builder.Services.AddCustomMediatR();
|
||||||
builder.Services.AddCustomProblemDetails();
|
builder.Services.AddProblemDetails();
|
||||||
|
|
||||||
var appOptions = builder.Services.GetOptions<AppOptions>(nameof(AppOptions));
|
var appOptions = builder.Services.GetOptions<AppOptions>(nameof(AppOptions));
|
||||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||||
@ -99,7 +98,7 @@ public static class InfrastructureExtensions
|
|||||||
var env = app.Environment;
|
var env = app.Environment;
|
||||||
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
|
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
|
||||||
|
|
||||||
app.UseProblemDetails();
|
app.UseCustomProblemDetails();
|
||||||
app.UseSerilogRequestLogging(options =>
|
app.UseSerilogRequestLogging(options =>
|
||||||
{
|
{
|
||||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||||
|
|||||||
@ -1,98 +1,107 @@
|
|||||||
using System;
|
|
||||||
using BuildingBlocks.Exception;
|
using BuildingBlocks.Exception;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace Flight.Extensions.Infrastructure;
|
namespace Flight.Extensions.Infrastructure;
|
||||||
|
|
||||||
|
using Grpc.Core;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
public static class ProblemDetailsExtensions
|
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
|
exceptionHandlerApp.Run(async context =>
|
||||||
x.IncludeExceptionDetails = (ctx, _) =>
|
|
||||||
{
|
{
|
||||||
// Fetch services from HttpContext.RequestServices
|
context.Response.ContentType = "application/problem+json";
|
||||||
var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
|
|
||||||
return env.IsDevelopment() || env.IsStaging();
|
if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService)
|
||||||
|
{
|
||||||
|
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
|
||||||
|
var exceptionType = exceptionHandlerFeature?.Error;
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
};
|
};
|
||||||
x.Map<ConflictException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-rule-validation-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exception will produce and returns from our FluentValidation RequestValidationBehavior
|
await problemDetailsService.WriteAsync(new ProblemDetailsContext
|
||||||
x.Map<ValidationException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
{
|
||||||
Title = ex.GetType().Name,
|
HttpContext = context,
|
||||||
Status = (int)ex.StatusCode,
|
ProblemDetails =
|
||||||
Detail = ex.Message,
|
{
|
||||||
Type = "https://somedomain/input-validation-rules-error"
|
Title = details.Title,
|
||||||
|
Detail = details.Detail,
|
||||||
|
Type = details.Type,
|
||||||
|
Status = details.StatusCode
|
||||||
|
}
|
||||||
});
|
});
|
||||||
x.Map<BadRequestException>(ex => new ProblemDetailsWithCode
|
}
|
||||||
{
|
}
|
||||||
Title = ex.GetType().Name,
|
});
|
||||||
Status = StatusCodes.Status400BadRequest,
|
});
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/bad-request-error"
|
return app;
|
||||||
});
|
|
||||||
x.Map<NotFoundException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status404NotFound,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/not-found-error"
|
|
||||||
});
|
|
||||||
x.Map<InternalServerException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status500InternalServerError,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/api-server-error"
|
|
||||||
});
|
|
||||||
x.Map<AppException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status400BadRequest,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.Map<DbUpdateConcurrencyException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/db-update-concurrency-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.MapToStatusCode<ArgumentNullException>(StatusCodes.Status400BadRequest);
|
|
||||||
|
|
||||||
x.MapStatusCode = context =>
|
|
||||||
{
|
|
||||||
return context.Response.StatusCode switch
|
|
||||||
{
|
|
||||||
StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Status = context.Response.StatusCode,
|
|
||||||
Title = "identity exception",
|
|
||||||
Detail = "You are not Authorized",
|
|
||||||
Type = "https://somedomain/identity-error"
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => new StatusCodeProblemDetails(context.Response.StatusCode)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return services;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -24,25 +23,11 @@ public class CreateFlightEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight", CreateFlight)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight", CreateFlight)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("CreateFlight")
|
.WithName("CreateFlight")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Create Flight", "Create Flight"))
|
.WithMetadata(new SwaggerOperationAttribute("Create Flight", "Create Flight"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces<CreateFlightResponseDto>(StatusCodes.Status201Created)
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status201Created,
|
|
||||||
"Flight Created",
|
|
||||||
typeof(CreateFlightResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Flight.Flights.Dtos;
|
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -17,24 +15,11 @@ public class DeleteFlightEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapDelete($"{EndpointConfig.BaseApiPath}/flight/{{id}}", DeleteFlight)
|
builder.MapDelete($"{EndpointConfig.BaseApiPath}/flight/{{id}}", DeleteFlight)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("DeleteFlight")
|
.WithName("DeleteFlight")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Delete Flight", "Delete Flight"))
|
.WithMetadata(new SwaggerOperationAttribute("Delete Flight", "Delete Flight"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces(StatusCodes.Status204NoContent)
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status204NoContent,
|
|
||||||
"Flight Deleted"))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -5,7 +5,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Dtos;
|
using Dtos;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -20,25 +19,11 @@ public class GetAvailableFlightsEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-flights", GetAvailableFlights)
|
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-flights", GetAvailableFlights)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("GetAvailableFlights")
|
.WithName("GetAvailableFlights")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Get Available Flights", "Get Available Flights"))
|
.WithMetadata(new SwaggerOperationAttribute("Get Available Flights", "Get Available Flights"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces<GetAvailableFlightsResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"GetAvailableFlights",
|
|
||||||
typeof(GetAvailableFlightsResult)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -4,7 +4,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Dtos;
|
using Dtos;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -19,28 +18,11 @@ public class GetFlightByIdEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/{{id}}", GetById)
|
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/{{id}}", GetById)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("GetFlightById")
|
.WithName("GetFlightById")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Get Flight By Id", "Get Flight By Id"))
|
.WithMetadata(new SwaggerOperationAttribute("Get Flight By Id", "Get Flight By Id"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.Produces<FlightDto>()
|
.Produces<GetFlightByIdResponseDto>()
|
||||||
.Produces(StatusCodes.Status200OK)
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
.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)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -4,8 +4,6 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Dtos;
|
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -22,27 +20,11 @@ public class UpdateFlightEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPut($"{EndpointConfig.BaseApiPath}/flight", UpdateFlight)
|
builder.MapPut($"{EndpointConfig.BaseApiPath}/flight", UpdateFlight)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("UpdateFlight")
|
.WithName("UpdateFlight")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Update Flight", "Update Flight"))
|
.WithMetadata(new SwaggerOperationAttribute("Update Flight", "Update Flight"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.Produces<FlightDto>()
|
|
||||||
.Produces(StatusCodes.Status204NoContent)
|
.Produces(StatusCodes.Status204NoContent)
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
.ProducesProblem(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)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -3,8 +3,6 @@ namespace Flight.Seats.Features.CreatingSeat.V1;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Flight.Seats.Dtos;
|
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -21,25 +19,11 @@ public class CreateSeatEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/seat", CreateSeat)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/seat", CreateSeat)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("CreateSeat")
|
.WithName("CreateSeat")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Create Seat", "Create Seat"))
|
.WithMetadata(new SwaggerOperationAttribute("Create Seat", "Create Seat"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces<CreateSeatResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"Seat Created",
|
|
||||||
typeof(CreateSeatResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -4,8 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Flight.Seats.Dtos;
|
using Dtos;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -20,25 +19,11 @@ public class GetAvailableSeatsEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-seats/{{id}}", GetAvailableSeats)
|
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-seats/{{id}}", GetAvailableSeats)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("GetAvailableSeats")
|
.WithName("GetAvailableSeats")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Get Available Seats", "Get Available Seats"))
|
.WithMetadata(new SwaggerOperationAttribute("Get Available Seats", "Get Available Seats"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces<GetAvailableSeatsResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"GetAvailableSeats",
|
|
||||||
typeof(GetAvailableSeatsResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -3,8 +3,6 @@ namespace Flight.Seats.Features.ReservingSeat.Commands.V1;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Flight.Seats.Dtos;
|
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -21,25 +19,11 @@ public class ReserveSeatEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/reserve-seat", ReserveSeat)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/reserve-seat", ReserveSeat)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Flight")
|
|
||||||
.WithName("ReserveSeat")
|
.WithName("ReserveSeat")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Reserve Seat", "Reserve Seat"))
|
.WithMetadata(new SwaggerOperationAttribute("Reserve Seat", "Reserve Seat"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||||
.WithMetadata(
|
.Produces<ReserveSeatResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"ReserveSeat",
|
|
||||||
typeof(ReserveSeatResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -3,7 +3,6 @@ using System.Threading.RateLimiting;
|
|||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
using BuildingBlocks.HealthCheck;
|
using BuildingBlocks.HealthCheck;
|
||||||
using BuildingBlocks.IdsGenerator;
|
|
||||||
using BuildingBlocks.Logging;
|
using BuildingBlocks.Logging;
|
||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
using BuildingBlocks.MassTransit;
|
using BuildingBlocks.MassTransit;
|
||||||
@ -13,7 +12,6 @@ using BuildingBlocks.Swagger;
|
|||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using Identity.Data;
|
using Identity.Data;
|
||||||
using Identity.Data.Seed;
|
using Identity.Data.Seed;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -72,7 +70,7 @@ public static class InfrastructureExtensions
|
|||||||
builder.Services.AddCustomVersioning();
|
builder.Services.AddCustomVersioning();
|
||||||
builder.Services.AddCustomMediatR();
|
builder.Services.AddCustomMediatR();
|
||||||
builder.Services.AddValidatorsFromAssembly(typeof(IdentityRoot).Assembly);
|
builder.Services.AddValidatorsFromAssembly(typeof(IdentityRoot).Assembly);
|
||||||
builder.Services.AddCustomProblemDetails();
|
builder.Services.AddProblemDetails();
|
||||||
builder.Services.AddCustomMapster(typeof(IdentityRoot).Assembly);
|
builder.Services.AddCustomMapster(typeof(IdentityRoot).Assembly);
|
||||||
builder.Services.AddCustomHealthCheck();
|
builder.Services.AddCustomHealthCheck();
|
||||||
|
|
||||||
@ -98,7 +96,7 @@ public static class InfrastructureExtensions
|
|||||||
|
|
||||||
app.UseForwardedHeaders();
|
app.UseForwardedHeaders();
|
||||||
|
|
||||||
app.UseProblemDetails();
|
app.UseCustomProblemDetails();
|
||||||
app.UseSerilogRequestLogging(options =>
|
app.UseSerilogRequestLogging(options =>
|
||||||
{
|
{
|
||||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||||
@ -107,7 +105,6 @@ public static class InfrastructureExtensions
|
|||||||
app.UseMigration<IdentityContext>(env);
|
app.UseMigration<IdentityContext>(env);
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
app.UseHttpMetrics();
|
app.UseHttpMetrics();
|
||||||
app.UseProblemDetails();
|
|
||||||
app.UseCustomHealthCheck();
|
app.UseCustomHealthCheck();
|
||||||
app.UseIdentityServer();
|
app.UseIdentityServer();
|
||||||
app.MapMetrics();
|
app.MapMetrics();
|
||||||
|
|||||||
@ -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;
|
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.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
public static class ProblemDetailsExtensions
|
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
|
exceptionHandlerApp.Run(async context =>
|
||||||
x.IncludeExceptionDetails = (ctx, _) =>
|
|
||||||
{
|
{
|
||||||
// Fetch services from HttpContext.RequestServices
|
context.Response.ContentType = "application/problem+json";
|
||||||
var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
|
|
||||||
return env.IsDevelopment() || env.IsStaging();
|
if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService)
|
||||||
|
{
|
||||||
|
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
|
||||||
|
var exceptionType = exceptionHandlerFeature?.Error;
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
};
|
};
|
||||||
x.Map<ConflictException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-rule-validation-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exception will produce and returns from our FluentValidation RequestValidationBehavior
|
await problemDetailsService.WriteAsync(new ProblemDetailsContext
|
||||||
x.Map<ValidationException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
{
|
||||||
Title = ex.GetType().Name,
|
HttpContext = context,
|
||||||
Status = (int)ex.StatusCode,
|
ProblemDetails =
|
||||||
Detail = ex.Message,
|
{
|
||||||
Type = "https://somedomain/input-validation-rules-error"
|
Title = details.Title,
|
||||||
|
Detail = details.Detail,
|
||||||
|
Type = details.Type,
|
||||||
|
Status = details.StatusCode
|
||||||
|
}
|
||||||
});
|
});
|
||||||
x.Map<BadRequestException>(ex => new ProblemDetailsWithCode
|
}
|
||||||
{
|
}
|
||||||
Title = ex.GetType().Name,
|
});
|
||||||
Status = StatusCodes.Status400BadRequest,
|
});
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/bad-request-error"
|
return app;
|
||||||
});
|
|
||||||
x.Map<NotFoundException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status404NotFound,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/not-found-error"
|
|
||||||
});
|
|
||||||
x.Map<InternalServerException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status500InternalServerError,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/api-server-error"
|
|
||||||
});
|
|
||||||
x.Map<AppException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status400BadRequest,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.Map<DbUpdateConcurrencyException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/db-update-concurrency-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.MapToStatusCode<ArgumentNullException>(StatusCodes.Status400BadRequest);
|
|
||||||
|
|
||||||
x.MapStatusCode = context =>
|
|
||||||
{
|
|
||||||
return context.Response.StatusCode switch
|
|
||||||
{
|
|
||||||
StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Status = context.Response.StatusCode,
|
|
||||||
Title = "identity exception",
|
|
||||||
Detail = "You are not Authorized",
|
|
||||||
Type = "https://somedomain/identity-error"
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => new StatusCodeProblemDetails(context.Response.StatusCode)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return services;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ namespace Identity.Identity.Features.RegisteringNewUser.V1;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -21,20 +20,11 @@ public class RegisterNewUserEndpoint : IMinimalEndpoint
|
|||||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/identity/register-user", RegisterNewUser)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/identity/register-user", RegisterNewUser)
|
||||||
.WithTags("Identity")
|
|
||||||
.WithName("RegisterUser")
|
.WithName("RegisterUser")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Register User", "Register User"))
|
.WithMetadata(new SwaggerOperationAttribute("Register User", "Register User"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Identity").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Identity").Build())
|
||||||
.WithMetadata(
|
.Produces<RegisterNewUserResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"User Registered",
|
|
||||||
typeof(RegisterNewUserResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -3,7 +3,6 @@ using BuildingBlocks.Core;
|
|||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
using BuildingBlocks.Exception;
|
using BuildingBlocks.Exception;
|
||||||
using BuildingBlocks.HealthCheck;
|
using BuildingBlocks.HealthCheck;
|
||||||
using BuildingBlocks.IdsGenerator;
|
|
||||||
using BuildingBlocks.Jwt;
|
using BuildingBlocks.Jwt;
|
||||||
using BuildingBlocks.Logging;
|
using BuildingBlocks.Logging;
|
||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
@ -15,7 +14,6 @@ using BuildingBlocks.Swagger;
|
|||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -73,7 +71,7 @@ public static class InfrastructureExtensions
|
|||||||
builder.Services.AddCustomVersioning();
|
builder.Services.AddCustomVersioning();
|
||||||
builder.Services.AddCustomMediatR();
|
builder.Services.AddCustomMediatR();
|
||||||
builder.Services.AddValidatorsFromAssembly(typeof(PassengerRoot).Assembly);
|
builder.Services.AddValidatorsFromAssembly(typeof(PassengerRoot).Assembly);
|
||||||
builder.Services.AddCustomProblemDetails();
|
builder.Services.AddProblemDetails();
|
||||||
builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly);
|
builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly);
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
builder.Services.AddCustomHealthCheck();
|
builder.Services.AddCustomHealthCheck();
|
||||||
@ -93,7 +91,7 @@ public static class InfrastructureExtensions
|
|||||||
var env = app.Environment;
|
var env = app.Environment;
|
||||||
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
|
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
|
||||||
|
|
||||||
app.UseProblemDetails();
|
app.UseCustomProblemDetails();
|
||||||
app.UseSerilogRequestLogging(options =>
|
app.UseSerilogRequestLogging(options =>
|
||||||
{
|
{
|
||||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||||
|
|||||||
@ -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;
|
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.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
public static class ProblemDetailsExtensions
|
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
|
exceptionHandlerApp.Run(async context =>
|
||||||
x.IncludeExceptionDetails = (ctx, _) =>
|
|
||||||
{
|
{
|
||||||
// Fetch services from HttpContext.RequestServices
|
context.Response.ContentType = "application/problem+json";
|
||||||
var env = ctx.RequestServices.GetRequiredService<IHostEnvironment>();
|
|
||||||
return env.IsDevelopment() || env.IsStaging();
|
if (context.RequestServices.GetService<IProblemDetailsService>() is { } problemDetailsService)
|
||||||
|
{
|
||||||
|
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
|
||||||
|
var exceptionType = exceptionHandlerFeature?.Error;
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
};
|
};
|
||||||
x.Map<ConflictException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-rule-validation-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exception will produce and returns from our FluentValidation RequestValidationBehavior
|
await problemDetailsService.WriteAsync(new ProblemDetailsContext
|
||||||
x.Map<ValidationException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
{
|
||||||
Title = ex.GetType().Name,
|
HttpContext = context,
|
||||||
Status = (int)ex.StatusCode,
|
ProblemDetails =
|
||||||
Detail = ex.Message,
|
{
|
||||||
Type = "https://somedomain/input-validation-rules-error"
|
Title = details.Title,
|
||||||
|
Detail = details.Detail,
|
||||||
|
Type = details.Type,
|
||||||
|
Status = details.StatusCode
|
||||||
|
}
|
||||||
});
|
});
|
||||||
x.Map<BadRequestException>(ex => new ProblemDetailsWithCode
|
}
|
||||||
{
|
}
|
||||||
Title = ex.GetType().Name,
|
});
|
||||||
Status = StatusCodes.Status400BadRequest,
|
});
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/bad-request-error"
|
return app;
|
||||||
});
|
|
||||||
x.Map<NotFoundException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status404NotFound,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/not-found-error"
|
|
||||||
});
|
|
||||||
x.Map<InternalServerException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status500InternalServerError,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/api-server-error"
|
|
||||||
});
|
|
||||||
x.Map<AppException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status400BadRequest,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/application-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.Map<DbUpdateConcurrencyException>(ex => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Title = ex.GetType().Name,
|
|
||||||
Status = StatusCodes.Status409Conflict,
|
|
||||||
Detail = ex.Message,
|
|
||||||
Type = "https://somedomain/db-update-concurrency-error"
|
|
||||||
});
|
|
||||||
|
|
||||||
x.MapToStatusCode<ArgumentNullException>(StatusCodes.Status400BadRequest);
|
|
||||||
|
|
||||||
x.MapStatusCode = context =>
|
|
||||||
{
|
|
||||||
return context.Response.StatusCode switch
|
|
||||||
{
|
|
||||||
StatusCodes.Status401Unauthorized => new ProblemDetailsWithCode
|
|
||||||
{
|
|
||||||
Status = context.Response.StatusCode,
|
|
||||||
Title = "identity exception",
|
|
||||||
Detail = "You are not Authorized",
|
|
||||||
Type = "https://somedomain/identity-error"
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => new StatusCodeProblemDetails(context.Response.StatusCode)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return services;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
namespace Passenger.Passengers.Features.CompletingRegisterPassenger.V1;
|
namespace Passenger.Passengers.Features.CompletingRegisterPassenger.V1;
|
||||||
|
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@ -19,25 +18,11 @@ public class CompleteRegisterPassengerEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/passenger/complete-registration", CompleteRegisterPassenger)
|
builder.MapPost($"{EndpointConfig.BaseApiPath}/passenger/complete-registration", CompleteRegisterPassenger)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Passenger")
|
|
||||||
.WithName("CompleteRegisterPassenger")
|
.WithName("CompleteRegisterPassenger")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Complete Register Passenger", "Complete Register Passenger"))
|
.WithMetadata(new SwaggerOperationAttribute("Complete Register Passenger", "Complete Register Passenger"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build())
|
||||||
.WithMetadata(
|
.Produces<CompleteRegisterPassengerResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"Register Passenger Completed",
|
|
||||||
typeof(CompleteRegisterPassengerResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
namespace Passenger.Passengers.Features.GettingPassengerById.Queries.V1;
|
namespace Passenger.Passengers.Features.GettingPassengerById.Queries.V1;
|
||||||
|
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Hellang.Middleware.ProblemDetails;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Passenger.Passengers.Dtos;
|
using Dtos;
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
|
|
||||||
public record GetPassengerByIdResponseDto(PassengerDto PassengerDto);
|
public record GetPassengerByIdResponseDto(PassengerDto PassengerDto);
|
||||||
@ -17,25 +16,11 @@ public class GetPassengerByIdEndpoint : IMinimalEndpoint
|
|||||||
{
|
{
|
||||||
builder.MapGet($"{EndpointConfig.BaseApiPath}/passenger/{{id}}", GetById)
|
builder.MapGet($"{EndpointConfig.BaseApiPath}/passenger/{{id}}", GetById)
|
||||||
.RequireAuthorization()
|
.RequireAuthorization()
|
||||||
.WithTags("Passenger")
|
|
||||||
.WithName("GetPassengerById")
|
.WithName("GetPassengerById")
|
||||||
.WithMetadata(new SwaggerOperationAttribute("Get Passenger By Id", "Get Passenger By Id"))
|
.WithMetadata(new SwaggerOperationAttribute("Get Passenger By Id", "Get Passenger By Id"))
|
||||||
.WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build())
|
.WithApiVersionSet(builder.NewApiVersionSet("Passenger").Build())
|
||||||
.WithMetadata(
|
.Produces<GetPassengerByIdResponseDto>()
|
||||||
new SwaggerResponseAttribute(
|
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||||
StatusCodes.Status200OK,
|
|
||||||
"GetPassengerById",
|
|
||||||
typeof(GetPassengerByIdResponseDto)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status400BadRequest,
|
|
||||||
"BadRequest",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.WithMetadata(
|
|
||||||
new SwaggerResponseAttribute(
|
|
||||||
StatusCodes.Status401Unauthorized,
|
|
||||||
"UnAuthorized",
|
|
||||||
typeof(StatusCodeProblemDetails)))
|
|
||||||
.HasApiVersion(1.0);
|
.HasApiVersion(1.0);
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user