105 lines
3.1 KiB
C#
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);
|
|
}
|