// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.ComponentModel; using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; using EpicGames.Core; namespace EpicGames.Horde.Agents { /// /// Normalized hostname of an agent /// [JsonSchemaString] [LogValueType] [TypeConverter(typeof(AgentIdTypeConverter))] [JsonConverter(typeof(AgentIdJsonConverter))] public readonly struct AgentId : IEquatable, IComparable { /// /// The text representing this id /// readonly string _name; /// /// Constructor /// /// Hostname of the agent public AgentId(string name) { if (name.Length == 0) { throw new ArgumentException("Agent name may not be empty"); } char[] result = new char[name.Length]; for (int idx = 0; idx < name.Length; idx++) { char character = name[idx]; if ((character >= 'A' && character <= 'Z') || (character >= '0' && character <= '9') || character == '_' || character == '-') { result[idx] = character; } else if (character >= 'a' && character <= 'z') { result[idx] = (char)('A' + (character - 'a')); } else if (character == '.') { if (idx == 0) { throw new ArgumentException("Agent name may not begin with the '.' character"); } else if (idx == name.Length - 1) { throw new ArgumentException("Agent name may not end with the '.' character"); } else if (name[idx - 1] == '.') { throw new ArgumentException("Agent name may not contain two consecutive '.' characters"); } else { result[idx] = character; } } else { throw new ArgumentException($"Character '{character}' in agent '{name}' is invalid"); } } _name = new string(result); } /// public override bool Equals(object? obj) { return obj is AgentId id && Equals(id); } /// public override int GetHashCode() { return (_name ?? String.Empty).GetHashCode(StringComparison.Ordinal); } /// public int CompareTo(AgentId other) => String.Compare(_name, other._name, StringComparison.Ordinal); /// public bool Equals(AgentId other) { return String.Equals(_name, other._name, StringComparison.Ordinal); } /// public override string ToString() { return _name ?? String.Empty; } #pragma warning disable CS1591 public static bool operator ==(AgentId left, AgentId right) => left.Equals(right); public static bool operator !=(AgentId left, AgentId right) => !left.Equals(right); public static bool operator <(AgentId left, AgentId right) => left.CompareTo(right) < 0; public static bool operator <=(AgentId left, AgentId right) => left.CompareTo(right) <= 0; public static bool operator >(AgentId left, AgentId right) => left.CompareTo(right) > 0; public static bool operator >=(AgentId left, AgentId right) => left.CompareTo(right) >= 0; #pragma warning restore CS1591 } /// /// Type converter from strings to PropertyFilter objects /// sealed class AgentIdTypeConverter : TypeConverter { /// public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } /// public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return new AgentId((string)value); } /// public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) { return destinationType == typeof(string); } /// public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { if (destinationType == typeof(string)) { return value?.ToString(); } else { return null; } } } /// /// Class which serializes AgentId objects to JSON /// public sealed class AgentIdJsonConverter : JsonConverter { /// public override AgentId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new AgentId(reader.GetString() ?? String.Empty); /// public override void Write(Utf8JsonWriter writer, AgentId value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); } }