Files
frame-processor/src/FrameProcessor/Domain/FrameName.cs
Fritiof Hedman cfaa4e86ab 1.2 FrameName value type
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 13:57:38 +02:00

105 lines
3.1 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
namespace FrameProcessor.Domain;
/// <summary>
/// URL-friendly identifier for a frame. Valid characters are the RFC 3986
/// "unreserved" set: <c>A-Z a-z 0-9 - . _ ~</c>. Must be non-empty.
/// </summary>
[JsonConverter(typeof(FrameNameJsonConverter))]
public readonly record struct FrameName : IParsable<FrameName>, ISpanParsable<FrameName>
{
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<char> s, out FrameName result)
=> TryParseCore(s, null, out result);
private static bool TryParseCore(ReadOnlySpan<char> 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<char> 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<char> 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<FrameName>
{
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);
}