From 61572cc7647d26aa03c5c8604cd3ba6f9a36be48 Mon Sep 17 00:00:00 2001 From: Rima Falko Date: Sun, 30 Mar 2025 00:53:11 +0100 Subject: [PATCH] fix-in-telegram-bridge-text-or-number-value-dictionary-converter-can-throw-json-error-on-json-write-operations Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Models/TextOrNumberValue.cs | 44 ++++++------- .../TextOrNumberValueDictionaryConverter.cs | 63 ++++++++++--------- 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/Sources/Falko.Talkie.Bridges.Telegram/Models/TextOrNumberValue.cs b/Sources/Falko.Talkie.Bridges.Telegram/Models/TextOrNumberValue.cs index eae488de..4aa75f1a 100644 --- a/Sources/Falko.Talkie.Bridges.Telegram/Models/TextOrNumberValue.cs +++ b/Sources/Falko.Talkie.Bridges.Telegram/Models/TextOrNumberValue.cs @@ -5,49 +5,51 @@ namespace Talkie.Bridges.Telegram.Models; public readonly struct TextOrNumberValue { - public static readonly TextOrNumberValue Empty = default; + private const string EmptyString = "empty"; - private readonly string? _text; + private readonly string? _textValue; - private readonly long _number; + private readonly long _numberValue; - private readonly bool _containsTextOrNumber; + private readonly bool _hasValue; - public TextOrNumberValue(string text) + public TextOrNumberValue(string textValue) { - ArgumentNullException.ThrowIfNull(text); + ArgumentNullException.ThrowIfNull(textValue); - _text = text; - _containsTextOrNumber = true; + _textValue = textValue; + _hasValue = true; } - public TextOrNumberValue(long number) + public TextOrNumberValue(long numberValue) { - _number = number; - _containsTextOrNumber = true; + _numberValue = numberValue; + _hasValue = true; } - public bool IsEmpty => _containsTextOrNumber is false; + public static TextOrNumberValue Empty => default; - [MemberNotNullWhen(true, nameof(_text))] - public bool ContainsText() => _text is not null; + public bool IsEmpty => _hasValue is false; - [MemberNotNullWhen(false, nameof(_text))] - public bool ContainsNumber() => _containsTextOrNumber && _text is null; + [MemberNotNullWhen(true, nameof(_textValue))] + public bool ContainsText() => _textValue is not null; - public string GetText() => ContainsText() ? _text : throw new InvalidOperationException("Text is not set."); + [MemberNotNullWhen(false, nameof(_textValue))] + public bool ContainsNumber() => _hasValue && _textValue is null; - public long GetNumber() => ContainsNumber() ? _number : throw new InvalidOperationException("Number is not set."); + public string GetText() => ContainsText() ? _textValue : throw new InvalidOperationException("Text is not set."); + + public long GetNumber() => ContainsNumber() ? _numberValue : throw new InvalidOperationException("Number is not set."); public bool TryGetText([MaybeNullWhen(false)] out string text) { - text = _text; + text = _textValue; return ContainsText(); } public bool TryGetNumber(out long number) { - number = _number; + number = _numberValue; return ContainsNumber(); } @@ -63,6 +65,6 @@ public override string ToString() return number.ToString(CultureInfo.InvariantCulture); } - return nameof(Empty); + return EmptyString; } } diff --git a/Sources/Falko.Talkie.Bridges.Telegram/Serialization/TextOrNumberValueDictionaryConverter.cs b/Sources/Falko.Talkie.Bridges.Telegram/Serialization/TextOrNumberValueDictionaryConverter.cs index 0eaae133..86f18279 100644 --- a/Sources/Falko.Talkie.Bridges.Telegram/Serialization/TextOrNumberValueDictionaryConverter.cs +++ b/Sources/Falko.Talkie.Bridges.Telegram/Serialization/TextOrNumberValueDictionaryConverter.cs @@ -2,77 +2,84 @@ using System.Text.Json; using System.Text.Json.Serialization; using Talkie.Bridges.Telegram.Models; +using Talkie.Sequences; namespace Talkie.Bridges.Telegram.Serialization; -internal sealed class TextOrNumberValueDictionaryConverter - : JsonConverter> +internal sealed class TextOrNumberValueDictionaryConverter : JsonConverter> { - public override IReadOnlyDictionary Read(ref Utf8JsonReader reader, + public override IReadOnlyDictionary Read + ( + ref Utf8JsonReader reader, Type typeToConvert, - JsonSerializerOptions options) + JsonSerializerOptions options + ) { if (reader.TokenType is not JsonTokenType.StartObject) { - throw new JsonException("Expected start object"); + throw new JsonException("Expected start object."); } - var dictionary = new Dictionary(); + var pairs = new Sequence>(); while (reader.Read()) { if (reader.TokenType is JsonTokenType.EndObject) { - return dictionary.ToFrozenDictionary(); + return pairs.ToFrozenDictionary(); } if (reader.TokenType is not JsonTokenType.PropertyName) { - throw new JsonException("Expected property name"); + throw new JsonException("Expected property name."); } - var key = reader.GetString() ?? throw new JsonException("Key is null"); + var key = reader.GetString() ?? throw new JsonException("Key is null."); if (reader.Read() is false) { - throw new JsonException("Expected string value"); + throw new JsonException("Expected property value."); } - if (reader.TokenType is JsonTokenType.String) + var value = reader.TokenType switch { - var value = new TextOrNumberValue(reader.GetString() ?? throw new JsonException("Value is null")); + // We don't check for null here, because it will be checked in the constructor + JsonTokenType.String => new TextOrNumberValue(reader.GetString()!), + JsonTokenType.Number => new TextOrNumberValue(reader.GetInt64()), + _ => throw new JsonException($"Unexpected value type for property '{key}', expected string or number.") + }; - dictionary.Add(key, value); - } - else if (reader.TokenType is JsonTokenType.Number) - { - var value = new TextOrNumberValue(reader.GetInt64()); - - dictionary.Add(key, value); - } + pairs.Add(new KeyValuePair(key, value)); } - throw new JsonException("Expected end object"); + throw new JsonException("Expected end object."); } - public override void Write(Utf8JsonWriter writer, + public override void Write + ( + Utf8JsonWriter writer, IReadOnlyDictionary dictionary, - JsonSerializerOptions options) + JsonSerializerOptions options + ) { writer.WriteStartObject(); - foreach (var (key, value) in dictionary) + foreach (var pair in dictionary) { + var value = pair.Value; + if (value.TryGetNumber(out var number)) { - writer.WriteNumber(key, number); + writer.WriteNumber(pair.Key, number); } else if (value.TryGetText(out var text)) { - writer.WriteString(key, text); + writer.WriteString(pair.Key, text); + } + else + { + throw new JsonException($"Value for key '{pair.Key}' does not contain a number or text, or empty."); } - - throw new JsonException("Value is empty"); } writer.WriteEndObject();