diff --git a/src/RxTelegram.Bot/Api/BaseTelegramBot.cs b/src/RxTelegram.Bot/Api/BaseTelegramBot.cs index a61e34f..1baaa7c 100644 --- a/src/RxTelegram.Bot/Api/BaseTelegramBot.cs +++ b/src/RxTelegram.Bot/Api/BaseTelegramBot.cs @@ -10,6 +10,7 @@ using RxTelegram.Bot.Exceptions; using RxTelegram.Bot.Interface.Validation; using RxTelegram.Bot.Utils; +using RxTelegram.Bot.Utils.Converter; namespace RxTelegram.Bot.Api; @@ -46,7 +47,7 @@ public static JsonSerializerSettings JsonSerializerSettings new InputFileConverter(), new ChatIdConverter(), new ChatBoostSourceConverter(), - new StringEnumConverter(new SnakeCaseNamingStrategy()) + new UnknownStringEnumConverter(new SnakeCaseNamingStrategy()) }; return _jsonSerializerSettings = new JsonSerializerSettings { diff --git a/src/RxTelegram.Bot/Interface/BaseTypes/Enums/MessageEntityType.cs b/src/RxTelegram.Bot/Interface/BaseTypes/Enums/MessageEntityType.cs index 87729b7..51872a4 100644 --- a/src/RxTelegram.Bot/Interface/BaseTypes/Enums/MessageEntityType.cs +++ b/src/RxTelegram.Bot/Interface/BaseTypes/Enums/MessageEntityType.cs @@ -91,4 +91,9 @@ public enum MessageEntityType /// Inline custom emoji stickers /// CustomEmoji, + + /// + /// block quotation + /// + Blockquote } diff --git a/src/RxTelegram.Bot/Utils/ChatIdConverter.cs b/src/RxTelegram.Bot/Utils/Converter/ChatIdConverter.cs similarity index 96% rename from src/RxTelegram.Bot/Utils/ChatIdConverter.cs rename to src/RxTelegram.Bot/Utils/Converter/ChatIdConverter.cs index c6d4c2a..b616451 100644 --- a/src/RxTelegram.Bot/Utils/ChatIdConverter.cs +++ b/src/RxTelegram.Bot/Utils/Converter/ChatIdConverter.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json.Linq; using RxTelegram.Bot.Interface.BaseTypes; -namespace RxTelegram.Bot.Utils; +namespace RxTelegram.Bot.Utils.Converter; public class ChatIdConverter : JsonConverter { diff --git a/src/RxTelegram.Bot/Utils/InputFileConverter.cs b/src/RxTelegram.Bot/Utils/Converter/InputFileConverter.cs similarity index 95% rename from src/RxTelegram.Bot/Utils/InputFileConverter.cs rename to src/RxTelegram.Bot/Utils/Converter/InputFileConverter.cs index 8513840..dbae295 100644 --- a/src/RxTelegram.Bot/Utils/InputFileConverter.cs +++ b/src/RxTelegram.Bot/Utils/Converter/InputFileConverter.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using RxTelegram.Bot.Interface.BaseTypes.Requests.Attachments; -namespace RxTelegram.Bot.Utils; +namespace RxTelegram.Bot.Utils.Converter; internal class InputFileConverter : JsonConverter { diff --git a/src/RxTelegram.Bot/Utils/Converter/UnknownStringEnumConverter.cs b/src/RxTelegram.Bot/Utils/Converter/UnknownStringEnumConverter.cs new file mode 100644 index 0000000..27dac60 --- /dev/null +++ b/src/RxTelegram.Bot/Utils/Converter/UnknownStringEnumConverter.cs @@ -0,0 +1,33 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +namespace RxTelegram.Bot.Utils.Converter; + +public class UnknownStringEnumConverter(NamingStrategy namingStrategy) : StringEnumConverter(namingStrategy) +{ + public override object ReadJson(JsonReader reader, Type enumType, object existingValue, JsonSerializer serializer) + { + try + { + return base.ReadJson(reader, enumType, existingValue, serializer); + } + catch (Exception) when (enumType.IsEnum || + enumType.IsGenericType && + enumType.GetGenericTypeDefinition() == typeof(Nullable<>) && + enumType.GenericTypeArguments[0].IsEnum) + { + // Return null if it is a nullable enum and -1 if it is an enum to ensure that new values do not brake the bot + return reader.TokenType switch + { + JsonToken.Integer => Enum.ToObject(enumType, -1), + JsonToken.Null => null, + JsonToken.None => null, + JsonToken.String when enumType.IsGenericType && enumType.GetGenericTypeDefinition() == typeof(Nullable<>) => null, + JsonToken.String => Enum.ToObject(enumType, -1), + _ => throw new ArgumentOutOfRangeException() + }; + } + } +} diff --git a/src/UnitTests/JsonConverters/UnknownStringEnumConverterTest.cs b/src/UnitTests/JsonConverters/UnknownStringEnumConverterTest.cs new file mode 100644 index 0000000..e09d88e --- /dev/null +++ b/src/UnitTests/JsonConverters/UnknownStringEnumConverterTest.cs @@ -0,0 +1,75 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using NUnit.Framework; +using RxTelegram.Bot.Interface.BaseTypes.Enums; +using RxTelegram.Bot.Utils.Converter; + +namespace RxTelegram.Bot.UnitTests.JsonConverters; + +[TestFixture] +public class UnknownStringEnumConverterTest +{ + private readonly JsonSerializerSettings _jsonSettings = new() + { + Converters = + { + new + UnknownStringEnumConverter(new + SnakeCaseNamingStrategy()) + } + }; + + [Test] + [TestCase(@"""blockquote""", MessageEntityType.Blockquote)] + [TestCase(@"""Pre""", MessageEntityType.Pre)] + [TestCase(@"""code""", MessageEntityType.Code)] + [TestCase(@"""mention""", MessageEntityType.Mention)] + [TestCase(@"""url""", MessageEntityType.Url)] + [TestCase(@"""email""", MessageEntityType.Email)] + [TestCase(@"""bold""", MessageEntityType.Bold)] + [TestCase(@"""italic""", MessageEntityType.Italic)] + [TestCase(@"""spoiler""", MessageEntityType.Spoiler)] + [TestCase(@"""hashtag""", MessageEntityType.Hashtag)] + [TestCase(@"""bot_command""", MessageEntityType.BotCommand)] + [TestCase(@"""custom_emoji""", MessageEntityType.CustomEmoji)] + [TestCase(@"""text_link""", MessageEntityType.TextLink)] + [TestCase(@"""text_mention""", MessageEntityType.TextMention)] + [TestCase(@"""unknown""", MessageEntityType.Unknown)] + [TestCase(@"""doesnt_exist""", null)] + [TestCase("", null)] + [TestCase(" ", null)] + public void ReadJson_WithUnknownStringEnum_ReturnsEnumValueOrNull(string json, MessageEntityType? expected) + { + // Act + var result = JsonConvert.DeserializeObject(json, _jsonSettings); + + // Assert + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + [TestCase(@"""blockquote""", (int)MessageEntityType.Blockquote)] + [TestCase(@"""Pre""", (int)MessageEntityType.Pre)] + [TestCase(@"""code""", (int)MessageEntityType.Code)] + [TestCase(@"""mention""", (int)MessageEntityType.Mention)] + [TestCase(@"""url""", (int)MessageEntityType.Url)] + [TestCase(@"""email""", (int)MessageEntityType.Email)] + [TestCase(@"""bold""", (int)MessageEntityType.Bold)] + [TestCase(@"""italic""", (int)MessageEntityType.Italic)] + [TestCase(@"""spoiler""", (int)MessageEntityType.Spoiler)] + [TestCase(@"""hashtag""", (int)MessageEntityType.Hashtag)] + [TestCase(@"""bot_command""", (int)MessageEntityType.BotCommand)] + [TestCase(@"""custom_emoji""", (int)MessageEntityType.CustomEmoji)] + [TestCase(@"""text_link""", (int)MessageEntityType.TextLink)] + [TestCase(@"""text_mention""", (int)MessageEntityType.TextMention)] + [TestCase(@"""unknown""", (int)MessageEntityType.Unknown)] + [TestCase(@"""doesnt_exist""", -1)] + public void ReadJson_WithUnknownStringEnum_ReturnsEnumValueOrNegativeValue(string json, int expected) + { + // Act + var result = JsonConvert.DeserializeObject(json, _jsonSettings); + + // Assert + Assert.That((int)result, Is.EqualTo(expected)); + } +}