cd8c78d165
CI - Build & Test / Backend (.NET) (push) Successful in 45s
CI - Build & Test / Backend integration (PostgreSQL/Toxiproxy) (push) Failing after 1m0s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m49s
CI - Build & Test / Security Check (push) Successful in 7s
CI - Build & Test / Deploy Nexus (push) Has been skipped
138 lines
4.6 KiB
C#
138 lines
4.6 KiB
C#
using System.Diagnostics;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.Filters;
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
|
|
namespace Nexus.Api.Http;
|
|
|
|
public static class NexusProblemCodes
|
|
{
|
|
public const string ValidationFailed = "validation_failed";
|
|
public const string Unauthenticated = "unauthenticated";
|
|
public const string Forbidden = "forbidden";
|
|
public const string NotFound = "not_found";
|
|
public const string Conflict = "conflict";
|
|
public const string UnsupportedCapability = "unsupported_capability";
|
|
public const string DependencyUnavailable = "dependency_unavailable";
|
|
public const string Timeout = "timeout";
|
|
public const string RateLimited = "rate_limited";
|
|
public const string InternalError = "internal_error";
|
|
|
|
public static string ForStatus(int statusCode) => statusCode switch
|
|
{
|
|
StatusCodes.Status400BadRequest or StatusCodes.Status422UnprocessableEntity => ValidationFailed,
|
|
StatusCodes.Status401Unauthorized => Unauthenticated,
|
|
StatusCodes.Status403Forbidden => Forbidden,
|
|
StatusCodes.Status404NotFound => NotFound,
|
|
StatusCodes.Status409Conflict => Conflict,
|
|
StatusCodes.Status429TooManyRequests => RateLimited,
|
|
StatusCodes.Status501NotImplemented => UnsupportedCapability,
|
|
StatusCodes.Status502BadGateway or StatusCodes.Status503ServiceUnavailable => DependencyUnavailable,
|
|
StatusCodes.Status504GatewayTimeout => Timeout,
|
|
_ => InternalError
|
|
};
|
|
}
|
|
|
|
public static class NexusProblemDetailsDefaults
|
|
{
|
|
public static void Apply(ProblemDetails problem, HttpContext httpContext)
|
|
{
|
|
var statusCode = problem.Status
|
|
?? (httpContext.Response.StatusCode >= 400
|
|
? httpContext.Response.StatusCode
|
|
: StatusCodes.Status500InternalServerError);
|
|
|
|
problem.Status = statusCode;
|
|
problem.Type ??= $"https://httpstatuses.com/{statusCode}";
|
|
problem.Title ??= ReasonPhrases.GetReasonPhrase(statusCode);
|
|
problem.Extensions.TryAdd("code", NexusProblemCodes.ForStatus(statusCode));
|
|
problem.Extensions.TryAdd(
|
|
"traceId",
|
|
Activity.Current?.Id ?? httpContext.TraceIdentifier);
|
|
}
|
|
|
|
public static ProblemDetails? FromLegacyError(
|
|
object? value,
|
|
int? statusCode,
|
|
HttpContext httpContext)
|
|
{
|
|
if (value is null || statusCode is null || statusCode < 400)
|
|
return null;
|
|
|
|
var errorProperty = value.GetType().GetProperty(
|
|
"error",
|
|
System.Reflection.BindingFlags.Public
|
|
| System.Reflection.BindingFlags.Instance
|
|
| System.Reflection.BindingFlags.IgnoreCase);
|
|
if (errorProperty?.GetValue(value) is not string detail
|
|
|| string.IsNullOrWhiteSpace(detail))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var problem = new ProblemDetails
|
|
{
|
|
Status = statusCode,
|
|
Detail = detail
|
|
};
|
|
Apply(problem, httpContext);
|
|
return problem;
|
|
}
|
|
}
|
|
|
|
public static class NexusHttpResults
|
|
{
|
|
public static IResult Problem(
|
|
int statusCode,
|
|
string detail,
|
|
string? code = null,
|
|
string? title = null,
|
|
IDictionary<string, object?>? extensions = null)
|
|
{
|
|
var values = extensions is null
|
|
? new Dictionary<string, object?>()
|
|
: new Dictionary<string, object?>(extensions);
|
|
values.TryAdd("code", code ?? NexusProblemCodes.ForStatus(statusCode));
|
|
|
|
return Results.Problem(
|
|
statusCode: statusCode,
|
|
title: title,
|
|
detail: detail,
|
|
extensions: values);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures MVC-produced ProblemDetails values use the same extensions as the
|
|
/// global exception and status-code writers.
|
|
/// </summary>
|
|
public sealed class NexusProblemDetailsFilter : IResultFilter
|
|
{
|
|
public void OnResultExecuting(ResultExecutingContext context)
|
|
{
|
|
if (context.Result is not ObjectResult objectResult)
|
|
return;
|
|
|
|
if (objectResult.Value is ProblemDetails problem)
|
|
{
|
|
NexusProblemDetailsDefaults.Apply(problem, context.HttpContext);
|
|
return;
|
|
}
|
|
|
|
var legacyProblem = NexusProblemDetailsDefaults.FromLegacyError(
|
|
objectResult.Value,
|
|
objectResult.StatusCode,
|
|
context.HttpContext);
|
|
if (legacyProblem is null)
|
|
return;
|
|
|
|
objectResult.Value = legacyProblem;
|
|
objectResult.ContentTypes.Clear();
|
|
objectResult.ContentTypes.Add("application/problem+json");
|
|
}
|
|
|
|
public void OnResultExecuted(ResultExecutedContext context)
|
|
{
|
|
}
|
|
}
|