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