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);