2024-05-28 15:29:18 +02:00
|
|
|
using System.Net;
|
2024-07-12 17:12:24 +02:00
|
|
|
using Foxnouns.Backend.Utils;
|
2024-05-28 15:29:18 +02:00
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
|
|
namespace Foxnouns.Backend.Middleware;
|
|
|
|
|
2024-07-08 19:03:04 +02:00
|
|
|
public class ErrorHandlerMiddleware(ILogger baseLogger, IHub sentry) : IMiddleware
|
2024-05-28 15:29:18 +02:00
|
|
|
{
|
|
|
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
await next(ctx);
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
var type = e.TargetSite?.DeclaringType ?? typeof(ErrorHandlerMiddleware);
|
|
|
|
var typeName = e.TargetSite?.DeclaringType?.FullName ?? "<unknown>";
|
|
|
|
var logger = baseLogger.ForContext(type);
|
|
|
|
|
|
|
|
if (ctx.Response.HasStarted)
|
|
|
|
{
|
|
|
|
logger.Error(e, "Error in {ClassName} ({Path}) after response started being sent", typeName,
|
|
|
|
ctx.Request.Path);
|
2024-07-08 19:03:04 +02:00
|
|
|
|
|
|
|
sentry.CaptureException(e, scope =>
|
|
|
|
{
|
|
|
|
var user = ctx.GetUser();
|
|
|
|
if (user != null)
|
|
|
|
scope.User = new SentryUser
|
|
|
|
{
|
|
|
|
Id = user.Id.ToString(),
|
|
|
|
Username = user.Username
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
2024-05-28 15:29:18 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e is ApiError ae)
|
|
|
|
{
|
|
|
|
ctx.Response.StatusCode = (int)ae.StatusCode;
|
|
|
|
ctx.Response.Headers.RequestId = ctx.TraceIdentifier;
|
|
|
|
ctx.Response.ContentType = "application/json; charset=utf-8";
|
|
|
|
if (ae is ApiError.Forbidden fe)
|
|
|
|
{
|
|
|
|
await ctx.Response.WriteAsync(JsonConvert.SerializeObject(new HttpApiError
|
|
|
|
{
|
|
|
|
Status = (int)fe.StatusCode,
|
|
|
|
Code = ErrorCode.Forbidden,
|
|
|
|
Message = fe.Message,
|
|
|
|
Scopes = fe.Scopes.Length > 0 ? fe.Scopes : null
|
|
|
|
}));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-12 17:12:24 +02:00
|
|
|
if (ae is ApiError.BadRequest br)
|
|
|
|
{
|
|
|
|
await ctx.Response.WriteAsync(br.ToJson().ToString());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-28 15:29:18 +02:00
|
|
|
await ctx.Response.WriteAsync(JsonConvert.SerializeObject(new HttpApiError
|
|
|
|
{
|
|
|
|
Status = (int)ae.StatusCode,
|
2024-05-28 17:09:50 +02:00
|
|
|
Code = ae.ErrorCode,
|
2024-05-28 15:29:18 +02:00
|
|
|
Message = ae.Message,
|
|
|
|
}));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (e is FoxnounsError fce)
|
|
|
|
{
|
|
|
|
logger.Error(fce.Inner ?? fce, "Exception in {ClassName} ({Path})", typeName, ctx.Request.Path);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
logger.Error(e, "Exception in {ClassName} ({Path})", typeName, ctx.Request.Path);
|
|
|
|
}
|
2024-07-12 17:12:24 +02:00
|
|
|
|
2024-07-08 19:03:04 +02:00
|
|
|
var errorId = sentry.CaptureException(e, scope =>
|
|
|
|
{
|
|
|
|
var user = ctx.GetUser();
|
|
|
|
if (user != null)
|
|
|
|
scope.User = new SentryUser
|
|
|
|
{
|
|
|
|
Id = user.Id.ToString(),
|
|
|
|
Username = user.Username
|
|
|
|
};
|
|
|
|
});
|
2024-05-28 15:29:18 +02:00
|
|
|
|
|
|
|
ctx.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
|
|
|
ctx.Response.Headers.RequestId = ctx.TraceIdentifier;
|
|
|
|
ctx.Response.ContentType = "application/json; charset=utf-8";
|
|
|
|
await ctx.Response.WriteAsync(JsonConvert.SerializeObject(new HttpApiError
|
|
|
|
{
|
|
|
|
Status = (int)HttpStatusCode.InternalServerError,
|
|
|
|
Code = ErrorCode.InternalServerError,
|
2024-07-08 19:03:04 +02:00
|
|
|
ErrorId = errorId.ToString(),
|
2024-05-28 15:29:18 +02:00
|
|
|
Message = "Internal server error",
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public record HttpApiError
|
|
|
|
{
|
|
|
|
public required int Status { get; init; }
|
2024-05-28 17:09:50 +02:00
|
|
|
|
2024-07-12 17:12:24 +02:00
|
|
|
[JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))]
|
2024-05-28 15:29:18 +02:00
|
|
|
public required ErrorCode Code { get; init; }
|
2024-09-04 14:25:44 +02:00
|
|
|
|
2024-07-12 17:12:24 +02:00
|
|
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
2024-07-08 19:03:04 +02:00
|
|
|
public string? ErrorId { get; init; }
|
2024-05-28 17:09:50 +02:00
|
|
|
|
2024-05-28 15:29:18 +02:00
|
|
|
public required string Message { get; init; }
|
2024-05-28 17:09:50 +02:00
|
|
|
|
2024-05-28 15:29:18 +02:00
|
|
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
|
|
public string[]? Scopes { get; init; }
|
|
|
|
}
|