feat(client): standardize connection outcomes (#13)
quality-gate / quality (push) Successful in 59s

This commit is contained in:
KyuubiYoru
2026-07-16 10:18:41 +02:00
parent b4b6072fe1
commit 94aba8a3bb
47 changed files with 2727 additions and 178 deletions
@@ -1,6 +1,7 @@
using System.Net;
using FinalFactory.Rendezvous.Contracts;
using FinalFactory.Rendezvous.Server.Browser;
using FinalFactory.Rendezvous.Server.ConnectionOutcomes;
using FinalFactory.Rendezvous.Server.JoinAttempts;
using FinalFactory.Rendezvous.Server.Provisioning;
using FinalFactory.Rendezvous.Server.Sessions;
@@ -11,8 +12,6 @@ namespace FinalFactory.Rendezvous.Server.Http;
internal static class ContractEndpoints
{
private const int NotImplementedStatus = StatusCodes.Status501NotImplemented;
public static IEndpointRouteBuilder MapRendezvousContractEndpoints(
this IEndpointRouteBuilder endpoints)
{
@@ -83,6 +82,7 @@ internal static class ContractEndpoints
.Produces<ApiError>(StatusCodes.Status400BadRequest)
.Produces<ApiError>(StatusCodes.Status404NotFound)
.Produces<ApiError>(StatusCodes.Status409Conflict)
.Produces<ApiError>(StatusCodes.Status410Gone)
.Produces<ApiError>(StatusCodes.Status429TooManyRequests)
.Produces<ApiError>(StatusCodes.Status503ServiceUnavailable)
.WithName("CreateJoinAttempt");
@@ -95,7 +95,10 @@ internal static class ContractEndpoints
attempts.MapPost("/{attemptId}/outcome", ReportConnectionOutcome)
.Accepts<ReportConnectionOutcomeRequest>("application/json")
.Produces<ReportConnectionOutcomeResponse>()
.Produces<ApiError>(StatusCodes.Status501NotImplemented)
.Produces<ApiError>(StatusCodes.Status400BadRequest)
.Produces<ApiError>(StatusCodes.Status404NotFound)
.Produces<ApiError>(StatusCodes.Status409Conflict)
.Produces<ApiError>(StatusCodes.Status503ServiceUnavailable)
.WithName("ReportConnectionOutcome");
return endpoints;
@@ -334,16 +337,20 @@ internal static class ContractEndpoints
private static IResult ReportConnectionOutcome(
JoinAttemptId attemptId,
[FromBody] ReportConnectionOutcomeRequest request) => NotImplemented();
private static IResult NotImplemented() => Results.Json(
new ApiError
{
Code = RendezvousErrorCode.ServiceUnavailable,
Message = "The v1 contract is reserved; implementation is tracked by subsequent issues.",
},
ContractJson.Options,
statusCode: NotImplementedStatus);
[FromHeader(Name = "X-Rendezvous-Client-Punch-Capability")] string clientPunchCapability,
[FromBody] ReportConnectionOutcomeRequest request,
[FromServices] ConnectionOutcomeService outcomes,
CancellationToken cancellationToken)
{
ConnectionOutcomeServiceResult result = outcomes.Report(
attemptId,
clientPunchCapability,
request,
cancellationToken);
return result.Succeeded && result.Value is not null
? Results.Ok(result.Value)
: Error(result.Error);
}
private static bool TryAuthenticatePublisher(
string? authorizationHeader,
@@ -389,9 +396,11 @@ internal static class ContractEndpoints
{
RendezvousErrorCode.AuthenticationRequired => StatusCodes.Status401Unauthorized,
RendezvousErrorCode.Forbidden => StatusCodes.Status403Forbidden,
RendezvousErrorCode.NotFound or RendezvousErrorCode.StaleHost => StatusCodes.Status404NotFound,
RendezvousErrorCode.Conflict or RendezvousErrorCode.ReplayRejected => StatusCodes.Status409Conflict,
RendezvousErrorCode.Expired => StatusCodes.Status410Gone,
RendezvousErrorCode.NotFound => StatusCodes.Status404NotFound,
RendezvousErrorCode.Conflict
or RendezvousErrorCode.IncompatibleProtocol
or RendezvousErrorCode.ReplayRejected => StatusCodes.Status409Conflict,
RendezvousErrorCode.Expired or RendezvousErrorCode.StaleHost => StatusCodes.Status410Gone,
RendezvousErrorCode.RateLimited or RendezvousErrorCode.CapacityExceeded =>
StatusCodes.Status429TooManyRequests,
RendezvousErrorCode.ServiceUnavailable => StatusCodes.Status503ServiceUnavailable,
@@ -406,6 +415,7 @@ internal static class ContractEndpoints
RendezvousErrorCode.NotFound => "The session was not found or is not owned by this publisher.",
RendezvousErrorCode.Conflict => "The session changed concurrently; retry with current state.",
RendezvousErrorCode.Expired => "The session lease has expired.",
RendezvousErrorCode.StaleHost => "The session has no fresh host presence.",
RendezvousErrorCode.IncompatibleProtocol => "The gameplay protocol is not enabled for this game.",
RendezvousErrorCode.CapacityExceeded => "The configured session capacity is currently exhausted.",
RendezvousErrorCode.ServiceUnavailable => "Session state is temporarily unavailable.",