Files
Rendezvous/src/FinalFactory.Rendezvous.Server/Provisioning/GamePolicyRegistry.cs
T
KyuubiYoru 47382ddadc
quality-gate / quality (push) Successful in 50s
feat: add tenant provisioning and key lifecycle (#5)
Closes #5
2026-07-16 05:13:35 +02:00

119 lines
5.2 KiB
C#

using FinalFactory.Rendezvous.Contracts;
namespace FinalFactory.Rendezvous.Server.Provisioning;
internal sealed class GamePolicyRegistry
{
private readonly Dictionary<(GameId Game, EnvironmentId Environment), GamePolicy> _policies;
private GamePolicyRegistry(
Dictionary<(GameId Game, EnvironmentId Environment), GamePolicy> policies) =>
_policies = policies;
public bool HasEnabledPolicies => _policies.Values.Any(static policy => policy.Enabled);
public IEnumerable<GamePolicy> EnabledPolicies =>
_policies.Values.Where(static policy => policy.Enabled);
public static GamePolicyRegistry Create(IEnumerable<GamePolicyOptions> options)
{
GamePolicyOptions[] configuredPolicies = options.ToArray();
if (configuredPolicies.Length > ProvisioningLimits.MaxGamePolicies)
{
throw new ProvisioningConfigurationException(
$"At most {ProvisioningLimits.MaxGamePolicies} game policies may be configured.");
}
Dictionary<(GameId Game, EnvironmentId Environment), GamePolicy> policies = [];
foreach (GamePolicyOptions policyOptions in configuredPolicies)
{
Validate(policyOptions);
GamePolicy policy = new(policyOptions);
if (!policies.TryAdd((policy.GameId, policy.EnvironmentId), policy))
{
throw new ProvisioningConfigurationException(
$"Duplicate game/environment policy: {policy.GameId}/{policy.EnvironmentId}.");
}
}
return new GamePolicyRegistry(policies);
}
public bool TryGet(
GameId gameId,
EnvironmentId environmentId,
out GamePolicy? policy)
{
if (_policies.TryGetValue((gameId, environmentId), out GamePolicy? candidate)
&& candidate.Enabled)
{
policy = candidate;
return true;
}
policy = null;
return false;
}
private static void Validate(GamePolicyOptions options)
{
if (!GameId.TryParse(options.GameId, out _)
|| !EnvironmentId.TryParse(options.EnvironmentId, out _))
{
throw new ProvisioningConfigurationException(
"Game policies require valid game and environment IDs.");
}
if (options.ProtocolVersions.Count is 0 or > ProvisioningLimits.MaxProtocolVersionsPerPolicy
|| options.ProtocolVersions.Contains(0)
|| options.ProtocolVersions.Count != options.ProtocolVersions.Distinct().Count())
{
throw new ProvisioningConfigurationException(
$"Policy {options.GameId}/{options.EnvironmentId} requires unique non-zero protocol versions.");
}
if (options.Regions.Count is 0 or > ProvisioningLimits.MaxRegionsPerPolicy
|| options.Regions.Any(static region => !RegionId.TryParse(region, out _))
|| options.Regions.Count != options.Regions.Distinct(StringComparer.Ordinal).Count())
{
throw new ProvisioningConfigurationException(
$"Policy {options.GameId}/{options.EnvironmentId} requires unique valid regions.");
}
if (options.VisibilityModes.Count == 0
|| options.VisibilityModes.Any(static mode => !Enum.IsDefined(mode))
|| options.VisibilityModes.Count != options.VisibilityModes.Distinct().Count()
|| options.PublisherTrustModes.Count == 0
|| options.PublisherTrustModes.Any(static mode => !Enum.IsDefined(mode))
|| options.PublisherTrustModes.Count != options.PublisherTrustModes.Distinct().Count())
{
throw new ProvisioningConfigurationException(
$"Policy {options.GameId}/{options.EnvironmentId} requires valid visibility and trust modes.");
}
if (options.MetadataMaxBytes is < 2 or > ContractLimits.MetadataMaxBytes
|| options.MetadataMaxKeys is < 0 or > ContractLimits.MetadataMaxKeys
|| options.MetadataValueMaxBytes.Count > options.MetadataMaxKeys
|| options.MetadataValueMaxBytes.Any(static item =>
string.IsNullOrWhiteSpace(item.Key)
|| !ContractValidation.IsUtf8LengthWithin(item.Key, ContractLimits.MetadataKeyMaxBytes)
|| item.Value is < 0 or > ContractLimits.MetadataValueMaxBytes)
|| options.RequiredMetadataKeys.Any(key =>
!options.MetadataValueMaxBytes.ContainsKey(key)))
{
throw new ProvisioningConfigurationException(
$"Policy {options.GameId}/{options.EnvironmentId} has an invalid metadata schema.");
}
if (options.MaxListingsPerPrincipal is < 1 or > ProvisioningLimits.MaxListingsPerPrincipal
|| options.MaxAnonymousListingsPerAddress < 0
|| options.MaxAnonymousListingsPerAddress > options.MaxListingsPerPrincipal
|| options.MaxActiveJoinAttempts is < 1
or > ProvisioningLimits.MaxActiveJoinAttemptsPerPolicy
|| !Enum.IsDefined(options.FallbackPolicy))
{
throw new ProvisioningConfigurationException(
$"Policy {options.GameId}/{options.EnvironmentId} has invalid quotas or fallback policy.");
}
}
}