Files
MoShiro/backend/src/MoShiro.Backend.Api/Common/Problems.cs
T

96 lines
4.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using Fleet.Core;
using Microsoft.AspNetCore.Http;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace MoShiro.Backend.Api;
/// <summary>错误码到 HTTP 状态的映射:只在 Api 层做一次。</summary>
public static class ErrorCodeHttpMap
{
// 必须声明在 s_map 之前:静态字段按文本顺序初始化,BuildMap 会用到它
public static readonly ErrorCode NotImplemented = new("not_implemented");
private static readonly Dictionary<string, int> s_map = BuildMap();
public static int StatusOf(ErrorCode code) => s_map.GetValueOrDefault(code.Value, StatusCodes.Status500InternalServerError);
public static string TitleOf(int status) => status switch
{
400 => "Validation failed",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not found",
409 => "Conflict",
410 => "Gone",
412 => "Precondition failed",
413 => "Payload too large",
422 => "Unprocessable request",
429 => "Too many requests",
501 => "Not implemented",
503 => "Service unavailable",
_ => "Internal error",
};
private static Dictionary<string, int> BuildMap()
{
var map = new Dictionary<string, int>(StringComparer.Ordinal);
void Add(int status, params ErrorCode[] codes)
{
foreach (ErrorCode code in codes)
{
map[code.Value] = status;
}
}
Add(400, MoShiroErrors.ValidationFailed, MoShiroErrors.BudgetExceeded, FleetErrors.InvalidParams);
Add(401, MoShiroErrors.Unauthorized, MoShiroErrors.CsrfFailed, FleetErrors.Unauthorized);
Add(403, MoShiroErrors.Forbidden, MoShiroErrors.SitePrivate, FleetErrors.Forbidden, FleetErrors.ProofInvalid);
Add(
404, MoShiroErrors.NotFound, MoShiroErrors.AgentNotFound, MoShiroErrors.ProbeNotFound, MoShiroErrors.TaskNotFound, MoShiroErrors.TestNotFound, MoShiroErrors.ChunkNotFound,
MoShiroErrors.TokenNotFound, MoShiroErrors.UnknownKind, MoShiroErrors.UnknownPlan, FleetErrors.NotFound);
Add(409, MoShiroErrors.NameConflict, MoShiroErrors.ProbeRevoked, MoShiroErrors.ProbeOffline, MoShiroErrors.TestBusy, MoShiroErrors.AgentNotActive, MoShiroErrors.AgentOffline, FleetErrors.NameConflict, FleetErrors.NodeOffline);
Add(410, MoShiroErrors.RawExpired, MoShiroErrors.JoinTokenExpired, FleetErrors.JoinTokenExpired);
Add(412, MoShiroErrors.PreconditionFailed);
Add(413, MoShiroErrors.PayloadTooLarge);
Add(422, MoShiroErrors.ProbeRequired, MoShiroErrors.RangeTooLarge, MoShiroErrors.InvalidStep, MoShiroErrors.InvalidRange, MoShiroErrors.UnknownMetric, MoShiroErrors.UnknownPreset, MoShiroErrors.TooManyMetrics, FleetErrors.Unsupported);
Add(429, MoShiroErrors.TooManyRequests, MoShiroErrors.TooManyStreams);
Add(503, MoShiroErrors.StorageUnavailable, FleetErrors.Timeout);
map[NotImplemented.Value] = 501;
return map;
}
}
/// <summary>所有 4xx/5xx 响应都使用 problem+json;5xx 不泄露异常信息,只提供 trace_id。</summary>
public static class Problems
{
public const string ContentType = "application/problem+json";
public static IResult From(HttpContext http, FleetError error)
{
int status = ErrorCodeHttpMap.StatusOf(error.Code);
IReadOnlyDictionary<string, string[]>? fields = error is ValidationError v
? v.Fields.ToDictionary(f => f.Key, f => new[] { f.Value })
: null;
return Create(http, status, error.Code.Value, status >= 500 ? null : error.Message, fields);
}
public static IResult Create(HttpContext http, ErrorCode code, string? detail) =>
Create(http, ErrorCodeHttpMap.StatusOf(code), code.Value, detail, null);
public static IResult Create(HttpContext http, int status, string code, string? detail, IReadOnlyDictionary<string, string[]>? errors = null)
{
string? trace = Activity.Current?.Id ?? http.TraceIdentifier;
var problem = new ProblemDto("about:blank", ErrorCodeHttpMap.TitleOf(status), status, code, detail, trace, errors);
return TypedResults.Json(problem, ApiJsonContext.Default.ProblemDto, ContentType, status);
}
public static IResult RetryAfter(HttpContext http, FleetError error, int seconds)
{
http.Response.Headers.RetryAfter = seconds.ToString(System.Globalization.CultureInfo.InvariantCulture);
return From(http, error);
}
}