using System.Text.Json.Serialization; namespace FinalFactory.Rendezvous.Contracts; [JsonConverter(typeof(GameIdJsonConverter))] public readonly struct GameId : IEquatable { public GameId(string value) { if (!ContractValidation.IsSlug(value, ContractLimits.GameIdMaxCharacters)) { throw new ArgumentException("Game IDs must be lowercase URL-safe slugs.", nameof(value)); } Value = value; } public string Value { get; } public static bool TryParse(string? value, out GameId gameId) { if (ContractValidation.IsSlug(value, ContractLimits.GameIdMaxCharacters)) { gameId = new GameId(value!); return true; } gameId = default; return false; } public bool Equals(GameId other) => string.Equals(Value, other.Value, StringComparison.Ordinal); public override bool Equals(object? obj) => obj is GameId other && Equals(other); public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value ?? string.Empty); public override string ToString() => Value ?? string.Empty; public static bool operator ==(GameId left, GameId right) => left.Equals(right); public static bool operator !=(GameId left, GameId right) => !left.Equals(right); } [JsonConverter(typeof(EnvironmentIdJsonConverter))] public readonly struct EnvironmentId : IEquatable { public EnvironmentId(string value) { if (!ContractValidation.IsSlug(value, ContractLimits.EnvironmentIdMaxCharacters)) { throw new ArgumentException("Environment IDs must be lowercase URL-safe slugs.", nameof(value)); } Value = value; } public string Value { get; } public static bool TryParse(string? value, out EnvironmentId environmentId) { if (ContractValidation.IsSlug(value, ContractLimits.EnvironmentIdMaxCharacters)) { environmentId = new EnvironmentId(value!); return true; } environmentId = default; return false; } public bool Equals(EnvironmentId other) => string.Equals(Value, other.Value, StringComparison.Ordinal); public override bool Equals(object? obj) => obj is EnvironmentId other && Equals(other); public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value ?? string.Empty); public override string ToString() => Value ?? string.Empty; public static bool operator ==(EnvironmentId left, EnvironmentId right) => left.Equals(right); public static bool operator !=(EnvironmentId left, EnvironmentId right) => !left.Equals(right); } [JsonConverter(typeof(RegionIdJsonConverter))] public readonly struct RegionId : IEquatable { public RegionId(string value) { if (!ContractValidation.IsSlug(value, ContractLimits.RegionIdMaxCharacters)) { throw new ArgumentException("Region IDs must be lowercase URL-safe slugs.", nameof(value)); } Value = value; } public string Value { get; } public static bool TryParse(string? value, out RegionId regionId) { if (ContractValidation.IsSlug(value, ContractLimits.RegionIdMaxCharacters)) { regionId = new RegionId(value!); return true; } regionId = default; return false; } public bool Equals(RegionId other) => string.Equals(Value, other.Value, StringComparison.Ordinal); public override bool Equals(object? obj) => obj is RegionId other && Equals(other); public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value ?? string.Empty); public override string ToString() => Value ?? string.Empty; public static bool operator ==(RegionId left, RegionId right) => left.Equals(right); public static bool operator !=(RegionId left, RegionId right) => !left.Equals(right); } internal sealed class GameIdJsonConverter : StringIdentifierJsonConverter { protected override GameId Parse(string value) => new(value); protected override string Format(GameId value) => value.Value; } internal sealed class EnvironmentIdJsonConverter : StringIdentifierJsonConverter { protected override EnvironmentId Parse(string value) => new(value); protected override string Format(EnvironmentId value) => value.Value; } internal sealed class RegionIdJsonConverter : StringIdentifierJsonConverter { protected override RegionId Parse(string value) => new(value); protected override string Format(RegionId value) => value.Value; }