using System.Text.Json; using System.Text.Json.Serialization; namespace FrameProcessor.Domain; /// /// URL-friendly identifier for a frame. Valid characters are the RFC 3986 /// "unreserved" set: A-Z a-z 0-9 - . _ ~. Must be non-empty. /// [JsonConverter(typeof(FrameNameJsonConverter))] public readonly record struct FrameName : IParsable, ISpanParsable { private readonly string? _value; private FrameName(string value) { _value = value; } public string Value => _value ?? string.Empty; public override string ToString() => Value; public static bool TryParse(string? s, out FrameName result) { if (s is null) { result = default; return false; } return TryParseCore(s.AsSpan(), s, out result); } public static bool TryParse(ReadOnlySpan s, out FrameName result) => TryParseCore(s, null, out result); private static bool TryParseCore(ReadOnlySpan s, string? original, out FrameName result) { result = default; if (s.IsEmpty) { return false; } foreach (var c in s) { if (!IsUnreserved(c)) { return false; } } result = new FrameName(original ?? new string(s)); return true; } public static FrameName Parse(string s) { ArgumentNullException.ThrowIfNull(s); return TryParse(s, out var result) ? result : throw new FormatException( $"'{s}' is not a valid frame name. Allowed characters: A-Z a-z 0-9 - . _ ~ (non-empty)."); } public static FrameName Parse(string s, IFormatProvider? provider) => Parse(s); public static bool TryParse(string? s, IFormatProvider? provider, out FrameName result) => TryParse(s, out result); public static FrameName Parse(ReadOnlySpan s, IFormatProvider? provider) => TryParse(s, out var result) ? result : throw new FormatException( $"'{new string(s)}' is not a valid frame name. Allowed characters: A-Z a-z 0-9 - . _ ~ (non-empty)."); public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, out FrameName result) => TryParse(s, out result); private static bool IsUnreserved(char c) => (uint)(c - 'A') <= 25 || (uint)(c - 'a') <= 25 || (uint)(c - '0') <= 9 || c is '-' or '.' or '_' or '~'; } internal sealed class FrameNameJsonConverter : JsonConverter { public override FrameName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var s = reader.GetString(); if (!FrameName.TryParse(s, out var name)) { throw new JsonException( $"'{s}' is not a valid frame name. Allowed characters: A-Z a-z 0-9 - . _ ~ (non-empty)."); } return name; } public override void Write(Utf8JsonWriter writer, FrameName value, JsonSerializerOptions options) => writer.WriteStringValue(value.Value); }