1.2 FrameName value type
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
104
src/FrameProcessor/Domain/FrameName.cs
Normal file
104
src/FrameProcessor/Domain/FrameName.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user