using System; using System.Text.Json; using System.Text.Json.Serialization; namespace Acp; /// /// A JSON-RPC request id: a number, a string, or absent. Used to correlate a response with the /// request that produced it. Numbers and strings are kept distinct so equality/hashing match the /// wire value exactly. /// [JsonConverter(typeof(RequestIdJsonConverter))] public readonly struct RequestId : IEquatable { public enum IdKind { None, Number, String } public IdKind Kind { get; } public long Number { get; } public string? Text { get; } private RequestId(IdKind kind, long number, string? text) { Kind = kind; Number = number; Text = text; } public bool IsValid => Kind != IdKind.None; public static RequestId Of(long number) => new(IdKind.Number, number, null); public static RequestId Of(string text) => new(IdKind.String, 0, text); public bool Equals(RequestId other) => Kind == other.Kind && Kind switch { IdKind.Number => Number == other.Number, IdKind.String => Text == other.Text, _ => true, }; public override bool Equals(object? obj) => obj is RequestId other && Equals(other); public override int GetHashCode() => Kind switch { IdKind.Number => Number.GetHashCode(), IdKind.String => Text?.GetHashCode() ?? 0, _ => 0, }; public override string ToString() => Kind switch { IdKind.Number => Number.ToString(), IdKind.String => Text ?? string.Empty, _ => "(none)", }; } internal sealed class RequestIdJsonConverter : JsonConverter { public override RequestId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch { JsonTokenType.Number => RequestId.Of(reader.GetInt64()), JsonTokenType.String => RequestId.Of(reader.GetString()!), JsonTokenType.Null => default, _ => throw new JsonException($"Invalid JSON-RPC id token: {reader.TokenType}"), }; public override void Write(Utf8JsonWriter writer, RequestId value, JsonSerializerOptions options) { switch (value.Kind) { case RequestId.IdKind.Number: writer.WriteNumberValue(value.Number); break; case RequestId.IdKind.String: writer.WriteStringValue(value.Text); break; default: writer.WriteNullValue(); break; } } }