using System.Diagnostics; using DodoSSH.Contracts; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.WebUtilities; namespace DodoSSH.Api.Setup; /// /// The one shape every error this API returns takes. /// /// /// RFC 9457 with a root-level code. The prose is for whoever reads a log; the code is what the /// client branches on, which is why it is a constant in and never a literal /// at the call site. Shared rather than duplicated per feature because there are now several endpoint /// classes per file, and a second copy is how two of them end up disagreeing. /// internal static class Problems { /// An error a handler decided on. internal static ProblemHttpResult Coded(int statusCode, string code, string detail) => TypedResults.Problem( detail: detail, statusCode: statusCode, type: ProblemCodes.TypeBaseUri + code, extensions: new Dictionary(StringComparer.Ordinal) { ["code"] = code }); /// The same shape, for the one path that cannot return an . /// /// FastEndpoints answers a request whose body will not deserialise before any handler runs, through /// a builder that returns a plain object rather than a result — so cannot be /// reused and the shape has to be spelled out. Replacing the default is not cosmetic: it announces /// application/problem+json while sending something that is not a problem document, and it /// puts the failing .NET type name on the wire, which is exactly what IncludeErrorDetails = /// false on the bearer handler exists to prevent. /// internal static CodedProblem ForBindingFailure(HttpContext context, int statusCode) { ArgumentNullException.ThrowIfNull(context); return new CodedProblem( Type: ProblemCodes.TypeBaseUri + ProblemCodes.MalformedRequest, Title: ReasonPhrases.GetReasonPhrase(statusCode), Status: statusCode, // Deliberately says nothing about which member failed. The binder knows, but naming it // describes the server's types rather than the caller's request. Detail: "The request body could not be read. Check it against the contract for the server " + "version reported by GET /api/v1/meta.", Code: ProblemCodes.MalformedRequest, TraceId: Activity.Current?.Id ?? context.TraceIdentifier); } } /// A problem document written by something other than TypedResults.Problem. /// /// Member order here is member order on the wire, and the names match what /// serialises to, so a client cannot tell which /// of the two produced a given response. /// /// The problem type URI, formed under . /// The status code's reason phrase. /// The HTTP status code. /// Human-readable explanation. Never a secret and never a .NET type name. /// The stable code the client switches on. /// Correlates the response with the server's trace. internal sealed record CodedProblem( string Type, string Title, int Status, string Detail, string Code, string? TraceId);