feat(client): standardize connection outcomes (#13)
quality-gate / quality (push) Successful in 59s
quality-gate / quality (push) Successful in 59s
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
using FinalFactory.Rendezvous.Contracts;
|
||||
using LiteNetLib;
|
||||
|
||||
namespace FinalFactory.Rendezvous.Client;
|
||||
|
||||
public enum RendezvousConnectionOutcomeSource
|
||||
{
|
||||
RendezvousService = 1,
|
||||
LocalTraversal = 2,
|
||||
RemoteHost = 3,
|
||||
Caller = 4,
|
||||
Lifecycle = 5,
|
||||
}
|
||||
|
||||
public enum RendezvousConnectionFailureCategory
|
||||
{
|
||||
None = 0,
|
||||
Directory = 1,
|
||||
Compatibility = 2,
|
||||
Authorization = 3,
|
||||
Capacity = 4,
|
||||
HostPresence = 5,
|
||||
Service = 6,
|
||||
Mediation = 7,
|
||||
NatTraversal = 8,
|
||||
DirectConnection = 9,
|
||||
Lifecycle = 10,
|
||||
}
|
||||
|
||||
public enum RendezvousConnectionPhase
|
||||
{
|
||||
Directory = 1,
|
||||
Authorization = 2,
|
||||
Mediation = 3,
|
||||
NatTraversal = 4,
|
||||
DirectConnection = 5,
|
||||
Complete = 6,
|
||||
}
|
||||
|
||||
public sealed class RendezvousConnectionOutcome
|
||||
{
|
||||
private readonly NetworkEndpoint? _dedicatedFallback;
|
||||
|
||||
private RendezvousConnectionOutcome(
|
||||
ConnectionOutcomeKind kind,
|
||||
RendezvousConnectionOutcomeSource source,
|
||||
RendezvousConnectionFailureCategory category,
|
||||
RendezvousConnectionPhase phase,
|
||||
TimeSpan elapsed,
|
||||
RendezvousErrorCode? serviceError,
|
||||
NetworkEndpoint? dedicatedFallback,
|
||||
NetPeer? peer)
|
||||
{
|
||||
if (elapsed < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(elapsed));
|
||||
}
|
||||
|
||||
if (dedicatedFallback is not null
|
||||
&& !ContractValidation.IsNetworkEndpointValid(dedicatedFallback))
|
||||
{
|
||||
throw new ArgumentException("The dedicated fallback endpoint is invalid.", nameof(dedicatedFallback));
|
||||
}
|
||||
|
||||
Kind = kind;
|
||||
Source = source;
|
||||
Category = category;
|
||||
Phase = phase;
|
||||
Elapsed = elapsed;
|
||||
ServiceError = serviceError;
|
||||
_dedicatedFallback = RendezvousEndpoint.Copy(dedicatedFallback);
|
||||
Peer = peer;
|
||||
}
|
||||
|
||||
public ConnectionOutcomeKind Kind { get; }
|
||||
public RendezvousConnectionOutcomeSource Source { get; }
|
||||
public RendezvousConnectionFailureCategory Category { get; }
|
||||
public RendezvousConnectionPhase Phase { get; }
|
||||
public TimeSpan Elapsed { get; }
|
||||
public RendezvousErrorCode? ServiceError { get; }
|
||||
public NetworkEndpoint? DedicatedFallback => RendezvousEndpoint.Copy(_dedicatedFallback);
|
||||
public NetPeer? Peer { get; }
|
||||
public bool IsSuccess => Kind == ConnectionOutcomeKind.Connected;
|
||||
public bool HasDedicatedFallback => _dedicatedFallback is not null;
|
||||
|
||||
public static RendezvousConnectionOutcome FromServiceError(
|
||||
RendezvousErrorCode error,
|
||||
TimeSpan elapsed,
|
||||
NetworkEndpoint? dedicatedFallback = null)
|
||||
{
|
||||
if (error == RendezvousErrorCode.None)
|
||||
{
|
||||
throw new ArgumentException("A service failure outcome requires an error.", nameof(error));
|
||||
}
|
||||
|
||||
(ConnectionOutcomeKind kind, RendezvousConnectionFailureCategory category, RendezvousConnectionPhase phase) =
|
||||
error switch
|
||||
{
|
||||
RendezvousErrorCode.NotFound => (
|
||||
ConnectionOutcomeKind.DirectoryNotFound,
|
||||
RendezvousConnectionFailureCategory.Directory,
|
||||
RendezvousConnectionPhase.Directory),
|
||||
RendezvousErrorCode.Expired => (
|
||||
ConnectionOutcomeKind.AttemptExpired,
|
||||
RendezvousConnectionFailureCategory.Authorization,
|
||||
RendezvousConnectionPhase.Authorization),
|
||||
RendezvousErrorCode.IncompatibleProtocol => (
|
||||
ConnectionOutcomeKind.IncompatibleProtocol,
|
||||
RendezvousConnectionFailureCategory.Compatibility,
|
||||
RendezvousConnectionPhase.Directory),
|
||||
RendezvousErrorCode.AuthenticationRequired
|
||||
or RendezvousErrorCode.Forbidden
|
||||
or RendezvousErrorCode.ReplayRejected => (
|
||||
ConnectionOutcomeKind.Unauthorized,
|
||||
RendezvousConnectionFailureCategory.Authorization,
|
||||
RendezvousConnectionPhase.Authorization),
|
||||
RendezvousErrorCode.RateLimited
|
||||
or RendezvousErrorCode.CapacityExceeded => (
|
||||
ConnectionOutcomeKind.RateLimited,
|
||||
RendezvousConnectionFailureCategory.Capacity,
|
||||
RendezvousConnectionPhase.Authorization),
|
||||
RendezvousErrorCode.StaleHost => (
|
||||
ConnectionOutcomeKind.NoHostPresence,
|
||||
RendezvousConnectionFailureCategory.HostPresence,
|
||||
RendezvousConnectionPhase.Mediation),
|
||||
RendezvousErrorCode.ServiceUnavailable => (
|
||||
ConnectionOutcomeKind.ServiceUnavailable,
|
||||
RendezvousConnectionFailureCategory.Service,
|
||||
RendezvousConnectionPhase.Authorization),
|
||||
_ => (
|
||||
ConnectionOutcomeKind.ServiceRejected,
|
||||
RendezvousConnectionFailureCategory.Service,
|
||||
RendezvousConnectionPhase.Authorization),
|
||||
};
|
||||
return new(
|
||||
kind,
|
||||
RendezvousConnectionOutcomeSource.RendezvousService,
|
||||
category,
|
||||
phase,
|
||||
elapsed,
|
||||
error,
|
||||
dedicatedFallback,
|
||||
null);
|
||||
}
|
||||
|
||||
public static ConnectionElapsedBucket BucketElapsed(TimeSpan elapsed)
|
||||
{
|
||||
if (elapsed < TimeSpan.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(elapsed));
|
||||
}
|
||||
|
||||
return elapsed.TotalSeconds switch
|
||||
{
|
||||
< 1 => ConnectionElapsedBucket.UnderOneSecond,
|
||||
< 5 => ConnectionElapsedBucket.OneToFiveSeconds,
|
||||
< 15 => ConnectionElapsedBucket.FiveToFifteenSeconds,
|
||||
< 30 => ConnectionElapsedBucket.FifteenToThirtySeconds,
|
||||
_ => ConnectionElapsedBucket.ThirtySecondsOrMore,
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"[RendezvousConnectionOutcome {Kind}; {Source}; credentials redacted]";
|
||||
|
||||
internal static RendezvousConnectionOutcome Create(
|
||||
ConnectionOutcomeKind kind,
|
||||
RendezvousConnectionOutcomeSource source,
|
||||
RendezvousConnectionFailureCategory category,
|
||||
RendezvousConnectionPhase phase,
|
||||
TimeSpan elapsed,
|
||||
NetworkEndpoint? dedicatedFallback = null,
|
||||
NetPeer? peer = null) => new(
|
||||
kind,
|
||||
source,
|
||||
category,
|
||||
phase,
|
||||
elapsed,
|
||||
null,
|
||||
dedicatedFallback,
|
||||
peer);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user