using System;
using GameCult.Caching;
namespace GameCult.Networking
{
///
/// Decides whether a connected peer may put or delete a record in the database a
/// serves. Transport admission (the connection key, a completed handshake) is
/// never a grant; this is the one owner of that decision.
///
public interface ICultNetRemoteWriteAuthority
{
///
/// Decides one peer write. Called once per received put or delete, after the schema is resolved and before the
/// shard is, so a refusal applies, publishes, logs and forwards nothing.
///
CultNetRemoteWriteDecision Decide(CultNetRemoteWriteRequest request);
}
///
/// The kind of write a peer asked for.
///
public enum CultNetRemoteWriteOperation
{
/// Put a record.
Put,
/// Delete a record.
Delete
}
///
/// One peer write, as the authority sees it.
///
public sealed class CultNetRemoteWriteRequest
{
///
/// Creates a request.
///
public CultNetRemoteWriteRequest(
CultNetServerPeer peer,
CultNetRemoteWriteOperation operation,
string schemaId,
CultRecordKey recordKey,
string? shardId,
long? shardEpoch)
{
Peer = peer;
Operation = operation;
SchemaId = schemaId;
RecordKey = recordKey;
ShardId = shardId;
ShardEpoch = shardEpoch;
}
/// Gets the peer connection the message arrived on (endpoint and transport only).
public CultNetServerPeer Peer { get; }
/// Gets the operation.
public CultNetRemoteWriteOperation Operation { get; }
/// Gets the canonical schema id the database resolved for the write, never the alias the peer sent.
public string SchemaId { get; }
/// Gets the record key.
public CultRecordKey RecordKey { get; }
/// Gets the shard id as the peer sent it.
public string? ShardId { get; }
/// Gets the shard epoch as the peer sent it.
public long? ShardEpoch { get; }
}
///
/// An authority's answer to one .
///
public sealed class CultNetRemoteWriteDecision
{
private CultNetRemoteWriteDecision(bool isGranted, string? reason)
{
IsGranted = isGranted;
Reason = reason;
}
/// Gets whether the write may proceed.
public bool IsGranted { get; }
/// Gets the refusal reason, when there is one.
public string? Reason { get; }
/// Grants the write.
public static CultNetRemoteWriteDecision Grant() => new(true, null);
/// Refuses the write.
public static CultNetRemoteWriteDecision Refuse(string? reason = null) => new(false, reason);
}
// Thrown by the server's authorize callback through the database's remote apply when the authority refuses; the
// server's handlers are its one catcher.
internal sealed class CultNetRemoteWriteRefusedException : Exception
{
public CultNetRemoteWriteRefusedException(CultNetRemoteWriteRequest request, string? reason)
: base(reason ?? "the write was not authorized")
{
Request = request;
Reason = reason;
}
public CultNetRemoteWriteRequest Request { get; }
public string? Reason { get; }
}
}