From 73e25b8e9f4eb6a84452830ca8fb822057c5aaa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timofei=20J=C3=A4=C3=A4ger?= <41389318+Panuchi@users.noreply.github.com> Date: Fri, 11 Aug 2023 16:07:30 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=20=D0=BD=D0=B5=D0=B8=D0=B7=D0=B2=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=BD=D1=8B=D1=85=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D1=8D=D0=BD=D0=B0=D0=BC=20(#1568)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: inyutin-maxim --- VkNet.Tests/Categories/Fave/FaveGetTests.cs | 16 + VkNet.Tests/Enum/EnumSkipErrorTest.cs | 62 +++ VkNet.Tests/Infrastructure/BaseTest.cs | 1 + VkNet.Tests/TestData/Categories/Fave/Get.json | 388 ++++++++++++------ .../Categories/SkipEnum/CheckStatus.json | 7 + .../Categories/SkipEnum/Get_CheckType.json | 27 ++ VkNet/Enums/StringEnums/AppType.cs | 5 +- VkNet/Enums/StringEnums/AsrStatus.cs | 5 +- .../IStreamingContextTypeDictionary.cs | 28 ++ .../StreamingContextExtensions.cs | 54 +++ .../StreamingContextTypeDataDictionary.cs | 16 + VkNet/Model/Ads/AdsAccount.cs | 2 +- VkNet/Model/Apps/App.cs | 2 +- VkNet/Model/Attachments/Link.cs | 10 +- VkNet/Model/ClientInfo.cs | 2 +- VkNet/Model/Results/Asr/AudioRecordingTask.cs | 2 +- VkNet/Model/Video/VideoAds.cs | 2 +- .../TolerantStringEnumConverter.cs | 38 ++ VkNet/Utils/Utilities.cs | 61 +++ VkNet/VkApi.cs | 17 +- VkNet/VkNet.csproj | 2 +- 21 files changed, 602 insertions(+), 145 deletions(-) create mode 100644 VkNet.Tests/Enum/EnumSkipErrorTest.cs create mode 100644 VkNet.Tests/TestData/Categories/SkipEnum/CheckStatus.json create mode 100644 VkNet.Tests/TestData/Categories/SkipEnum/Get_CheckType.json create mode 100644 VkNet/Infrastructure/IStreamingContextTypeDictionary.cs create mode 100644 VkNet/Infrastructure/StreamingContextExtensions.cs create mode 100644 VkNet/Infrastructure/StreamingContextTypeDataDictionary.cs create mode 100644 VkNet/Utils/JsonConverter/TolerantStringEnumConverter.cs diff --git a/VkNet.Tests/Categories/Fave/FaveGetTests.cs b/VkNet.Tests/Categories/Fave/FaveGetTests.cs index c6de77101..be50834d2 100644 --- a/VkNet.Tests/Categories/Fave/FaveGetTests.cs +++ b/VkNet.Tests/Categories/Fave/FaveGetTests.cs @@ -21,6 +21,22 @@ public void Get() Count = 1 }); + faves[1] + .Link.OldId.Should() + .Be(null); + + faves[1] + .Link.LinkId.Should() + .Be("6_759823298_736260903"); + + faves[2] + .Link.OldId.Should() + .Be(6736260903); + + faves[2] + .Link.LinkId.Should() + .Be("6736260903"); + var fave = faves.FirstOrDefault(); faves.Should() diff --git a/VkNet.Tests/Enum/EnumSkipErrorTest.cs b/VkNet.Tests/Enum/EnumSkipErrorTest.cs new file mode 100644 index 000000000..72e862182 --- /dev/null +++ b/VkNet.Tests/Enum/EnumSkipErrorTest.cs @@ -0,0 +1,62 @@ +using System.Linq; +using FluentAssertions; +using VkNet.Enums.StringEnums; +using VkNet.Tests.Infrastructure; +using Xunit; + +namespace VkNet.Tests.Enum; + +public class EnumSkipErrorTest : CategoryBaseTest +{ + /// + protected override string Folder => "SkipEnum"; + + [Fact] + public void CheckStatus() + { + Url = "https://api.vk.com/method/asr.checkStatus"; + + ReadCategoryJsonPath(nameof(CheckStatus)); + + if (Api.DeserializationErrorHandler is not true) + { + return; + } + + var result = Api.Asr.CheckStatus("7ee0fa8e-64ac-4391-af7a-5c98a6330866"); + + result.Id.Should() + .Be("7ee0fa8e-64ac-4391-af7a-5c98a6330866"); + + result.Text.Should() + .Be("Это тестовая запись для сервиса распознавания речи ВКонтакте."); + + result.Status.Should() + .Be(null); + } + + [Fact] + public void Get_CheckType() + { + Url = "https://api.vk.com/method/apps.get"; + ReadCategoryJsonPath(nameof(Get_CheckType)); + + if (Api.DeserializationErrorHandler is not true) + { + return; + } + + var app = Api.Apps.Get(new() + { + AppIds = new ulong[] + { + 4268118 + }, + Platform = AppPlatforms.Web + }); + + app.Apps.First() + .Type.Should() + .Be(null); + } +} \ No newline at end of file diff --git a/VkNet.Tests/Infrastructure/BaseTest.cs b/VkNet.Tests/Infrastructure/BaseTest.cs index 0f9dba5bc..fbdb120a6 100644 --- a/VkNet.Tests/Infrastructure/BaseTest.cs +++ b/VkNet.Tests/Infrastructure/BaseTest.cs @@ -111,6 +111,7 @@ public BaseTest() Api.RestClient = Mocker.Get(); Api.NeedValidationHandler = Mocker.Get(); Api.CaptchaSolver = Mocker.Get(); + Api.DeserializationErrorHandler = false; SetupCaptchaHandler(); Api.Authorize(Mocker.Get()); diff --git a/VkNet.Tests/TestData/Categories/Fave/Get.json b/VkNet.Tests/TestData/Categories/Fave/Get.json index 6963ab706..8d8c0726e 100644 --- a/VkNet.Tests/TestData/Categories/Fave/Get.json +++ b/VkNet.Tests/TestData/Categories/Fave/Get.json @@ -1,129 +1,263 @@ { - "response": { - "count": 4, - "items": [ - { - "added_date": 1562970573, - "seen": true, - "type": "post", - "post": { - "id": 207704, - "from_id": -55326998, - "owner_id": -55326998, - "date": 1562646443, - "post_type": "post", - "text": "", - "marked_as_ads": 0, - "attachments": [ - { - "type": "video", - "video": { - "id": 456239930, - "owner_id": -55326998, - "title": "4M2kcGc.mp4", - "duration": 12, - "description": "", - "date": 1562646443, - "comments": 0, - "views": 39498, - "width": 480, - "height": 480, - "image": [ - { - "height": 96, - "url": "https://sun9-61.u.32c/yg2M_MRAZZY.jpg", - "width": 130, - "with_padding": 1 - }, - { - "height": 120, - "url": "https://sun9-4.us.32b/bfzla8S6g_4.jpg", - "width": 160, - "with_padding": 1 - }, - { - "height": 240, - "url": "https://sun9-63.u.32a/ky2nxyv82VI.jpg", - "width": 320, - "with_padding": 1 - }, - { - "height": 450, - "url": "https://sun9-43.u.324/LALGA7kf3qg.jpg", - "width": 800, - "with_padding": 1 - }, - { - "height": 720, - "url": "https://sun9-35.u.325/q368FFrz2kg.jpg", - "width": 720 - }, - { - "height": 320, - "url": "https://sun9-70.u.326/BcntYKWo0bo.jpg", - "width": 320 - }, - { - "height": 720, - "url": "https://sun9-21.u.327/1aRYqqoIN2o.jpg", - "width": 720 - }, - { - "height": 1024, - "url": "https://sun9-62.u.328/gio4QnS0soo.jpg", - "width": 1024 - }, - { - "height": 4096, - "url": "https://sun9-37.u.329/aMXqZRPxyyY.jpg", - "width": 4096 - } - ], - "is_favorite": false, - "access_key": "927e08188414e646c6", - "files": { - "mp4_240": "https://cs1-72v4/3gIq3Z0shgApNHWOc0A", - "mp4_360": "https://cs564636/_hXn_uS4_R_PpRO3zCg", - "mp4_480": "https://cs564404/_hXn_uS4_R_PpRO3zCg" - }, - "player": "https://vk.com/vi/57e_GQ3DKNZUGI4TAMQ", - "can_add": 0, - "track_code": "video_457a7debi1ikCHslDsIugi5WOgz_YUYgbyNyBxoztWG97SDPRDTWebkKeSYI9Be7Fg", - "type": "video" - } - } - ], - "post_source": { - "type": "api", - "platform": "android" - }, - "comments": { - "count": 0, - "can_post": 1, - "groups_can_post": true - }, - "likes": { - "count": 421, - "user_likes": 0, - "can_like": 1, - "can_publish": 1 - }, - "reposts": { - "count": 10, - "user_reposted": 0 - }, - "views": { - "count": 71781 - }, - "is_favorite": true - }, - "tags": [ - { - "id": 39503027, - "name": "Важное" - } - ] - } - ] - } + "response": { + "count": 4, + "items": [ + { + "added_date": 1562970573, + "seen": true, + "type": "post", + "post": { + "id": 207704, + "from_id": -55326998, + "owner_id": -55326998, + "date": 1562646443, + "post_type": "post", + "text": "", + "marked_as_ads": 0, + "attachments": [ + { + "type": "video", + "video": { + "id": 456239930, + "owner_id": -55326998, + "title": "4M2kcGc.mp4", + "duration": 12, + "description": "", + "date": 1562646443, + "comments": 0, + "views": 39498, + "width": 480, + "height": 480, + "image": [ + { + "height": 96, + "url": "https://sun9-61.u.32c/yg2M_MRAZZY.jpg", + "width": 130, + "with_padding": 1 + }, + { + "height": 120, + "url": "https://sun9-4.us.32b/bfzla8S6g_4.jpg", + "width": 160, + "with_padding": 1 + }, + { + "height": 240, + "url": "https://sun9-63.u.32a/ky2nxyv82VI.jpg", + "width": 320, + "with_padding": 1 + }, + { + "height": 450, + "url": "https://sun9-43.u.324/LALGA7kf3qg.jpg", + "width": 800, + "with_padding": 1 + }, + { + "height": 720, + "url": "https://sun9-35.u.325/q368FFrz2kg.jpg", + "width": 720 + }, + { + "height": 320, + "url": "https://sun9-70.u.326/BcntYKWo0bo.jpg", + "width": 320 + }, + { + "height": 720, + "url": "https://sun9-21.u.327/1aRYqqoIN2o.jpg", + "width": 720 + }, + { + "height": 1024, + "url": "https://sun9-62.u.328/gio4QnS0soo.jpg", + "width": 1024 + }, + { + "height": 4096, + "url": "https://sun9-37.u.329/aMXqZRPxyyY.jpg", + "width": 4096 + } + ], + "is_favorite": false, + "access_key": "927e08188414e646c6", + "files": { + "mp4_240": "https://cs1-72v4/3gIq3Z0shgApNHWOc0A", + "mp4_360": "https://cs564636/_hXn_uS4_R_PpRO3zCg", + "mp4_480": "https://cs564404/_hXn_uS4_R_PpRO3zCg" + }, + "player": "https://vk.com/vi/57e_GQ3DKNZUGI4TAMQ", + "can_add": 0, + "track_code": "video_457a7debi1ikCHslDsIugi5WOgz_YUYgbyNyBxoztWG97SDPRDTWebkKeSYI9Be7Fg", + "type": "video" + } + } + ], + "post_source": { + "type": "api", + "platform": "android" + }, + "comments": { + "count": 0, + "can_post": 1, + "groups_can_post": true + }, + "likes": { + "count": 421, + "user_likes": 0, + "can_like": 1, + "can_publish": 1 + }, + "reposts": { + "count": 10, + "user_reposted": 0 + }, + "views": { + "count": 71781 + }, + "is_favorite": true + }, + "tags": [ + { + "id": 39503027, + "name": "Важное" + } + ] + }, + { + "added_date": 1675098325, + "seen": true, + "type": "link", + "link": { + "url": "vk.com", + "title": "vk.com", + "caption": "vk.com", + "photo": { + "album_id": -28, + "date": 1689759891, + "id": 457463131, + "owner_id": 2000039507, + "sizes": [ + { + "height": 75, + "type": "s", + "width": 75, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=75x75&quality=96&sign=4bcfde0f744972fcb69ac3674333468d&c_uniq_tag=dUVGDAYd7EeWGjGpJM2PtW4OjDt_cvB21RZVP4Ar6Bs&type=album" + }, + { + "height": 130, + "type": "m", + "width": 130, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=130x130&quality=96&sign=b99997266b617a0b43a0cde8fe9f6847&c_uniq_tag=2cQReT2gEo6lzsT1w-IbDKH7yNNfgVvf6fWqbVIk7Sc&type=album" + }, + { + "height": 256, + "type": "x", + "width": 256, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=256x256&quality=96&sign=9b0a451f1c5f548101d8aa2affa1048c&c_uniq_tag=vHMW4dexjzf1qHjkczDDZVU0inIxqInSPzvYvVY-W5Q&type=album" + }, + { + "height": 130, + "type": "o", + "width": 130, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=130x130&quality=96&sign=b99997266b617a0b43a0cde8fe9f6847&c_uniq_tag=2cQReT2gEo6lzsT1w-IbDKH7yNNfgVvf6fWqbVIk7Sc&type=album" + }, + { + "height": 200, + "type": "p", + "width": 200, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=200x200&quality=96&sign=46689ebd72a528f2c37399086c7f3c21&c_uniq_tag=dw6qXye8wIDW03xXE0n5UtN88HZhBAfXbvNqjr5Zetk&type=album" + }, + { + "height": 256, + "type": "q", + "width": 256, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=256x256&quality=96&sign=9b0a451f1c5f548101d8aa2affa1048c&c_uniq_tag=vHMW4dexjzf1qHjkczDDZVU0inIxqInSPzvYvVY-W5Q&type=album" + }, + { + "height": 256, + "type": "r", + "width": 256, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=256x256&quality=96&sign=9b0a451f1c5f548101d8aa2affa1048c&c_uniq_tag=vHMW4dexjzf1qHjkczDDZVU0inIxqInSPzvYvVY-W5Q&type=album" + } + ], + "text": "", + "user_id": 100, + "has_tags": false + }, + "target": "internal", + "is_favorite": true, + "id": "6_759823298_736260903" + }, + "tags": [] + }, + { + "added_date": 1675098325, + "seen": true, + "type": "link", + "link": { + "url": "vk.com", + "title": "vk.com", + "caption": "vk.com", + "photo": { + "album_id": -28, + "date": 1689759891, + "id": 457463131, + "owner_id": 2000039507, + "sizes": [ + { + "height": 75, + "type": "s", + "width": 75, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=75x75&quality=96&sign=4bcfde0f744972fcb69ac3674333468d&c_uniq_tag=dUVGDAYd7EeWGjGpJM2PtW4OjDt_cvB21RZVP4Ar6Bs&type=album" + }, + { + "height": 130, + "type": "m", + "width": 130, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=130x130&quality=96&sign=b99997266b617a0b43a0cde8fe9f6847&c_uniq_tag=2cQReT2gEo6lzsT1w-IbDKH7yNNfgVvf6fWqbVIk7Sc&type=album" + }, + { + "height": 256, + "type": "x", + "width": 256, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=256x256&quality=96&sign=9b0a451f1c5f548101d8aa2affa1048c&c_uniq_tag=vHMW4dexjzf1qHjkczDDZVU0inIxqInSPzvYvVY-W5Q&type=album" + }, + { + "height": 130, + "type": "o", + "width": 130, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=130x130&quality=96&sign=b99997266b617a0b43a0cde8fe9f6847&c_uniq_tag=2cQReT2gEo6lzsT1w-IbDKH7yNNfgVvf6fWqbVIk7Sc&type=album" + }, + { + "height": 200, + "type": "p", + "width": 200, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=200x200&quality=96&sign=46689ebd72a528f2c37399086c7f3c21&c_uniq_tag=dw6qXye8wIDW03xXE0n5UtN88HZhBAfXbvNqjr5Zetk&type=album" + }, + { + "height": 256, + "type": "q", + "width": 256, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=256x256&quality=96&sign=9b0a451f1c5f548101d8aa2affa1048c&c_uniq_tag=vHMW4dexjzf1qHjkczDDZVU0inIxqInSPzvYvVY-W5Q&type=album" + }, + { + "height": 256, + "type": "r", + "width": 256, + "url": "https://sun9-22.userapi.com/impg/6Tk01fuwML7y5flRKmH7OTUqQaqiFWaNwtNbhA/fICHMoUxBZY.jpg?size=256x256&quality=96&sign=9b0a451f1c5f548101d8aa2affa1048c&c_uniq_tag=vHMW4dexjzf1qHjkczDDZVU0inIxqInSPzvYvVY-W5Q&type=album" + } + ], + "text": "", + "user_id": 100, + "has_tags": false + }, + "target": "internal", + "is_favorite": true, + "id": "6736260903" + }, + "tags": [] + } + ] + } } \ No newline at end of file diff --git a/VkNet.Tests/TestData/Categories/SkipEnum/CheckStatus.json b/VkNet.Tests/TestData/Categories/SkipEnum/CheckStatus.json new file mode 100644 index 000000000..275214816 --- /dev/null +++ b/VkNet.Tests/TestData/Categories/SkipEnum/CheckStatus.json @@ -0,0 +1,7 @@ +{ + "response": { + "id": "7ee0fa8e-64ac-4391-af7a-5c98a6330866", + "status": "asdadadas", + "text": "Это тестовая запись для сервиса распознавания речи ВКонтакте." + } +} \ No newline at end of file diff --git a/VkNet.Tests/TestData/Categories/SkipEnum/Get_CheckType.json b/VkNet.Tests/TestData/Categories/SkipEnum/Get_CheckType.json new file mode 100644 index 000000000..7d8a764bc --- /dev/null +++ b/VkNet.Tests/TestData/Categories/SkipEnum/Get_CheckType.json @@ -0,0 +1,27 @@ +{ + "response": { + "count": 1, + "items": [ + { + "id": 4268118, + "title": "raventestapp", + "icon_256": "http://vk.com/images/dquestion_l.png", + "icon_128": "http://cs624017.vk.me/v624017123/47c4e/9Wx9pRA33yk.jpg", + "icon_200": "http://cs624017.vk.me/v624017123/47c4e/9Wx9pRA33yk.jpg", + "icon_100": "http://cs624017.vk.me/v624017123/47c4e/9Wx9pRA33yk.jpg", + "icon_75": "http://cs624017.vk.me/v624017123/47c4c/HaqH9hwhWQs.jpg", + "icon_50": "http://cs624017.vk.me/v624017123/47c4c/HaqH9hwhWQs.jpg", + "icon_25": "http://cs624017.vk.me/v624017123/47c4c/HaqH9hwhWQs.jpg", + "banner_186": "http://vk.com/images/dquestion_x.gif", + "banner_896": "http://vk.com/images/dquestion_t.png", + "type": "sadasdadas", + "author_url": "http://vk.com/club103292418", + "author_group": 103292418, + "members_count": 3, + "install_url": "http://m.vk.com/app4268118?api_view=7658df7dd4b391d7746beb3f1d6791", + "leaderboard_type": 1, + "installed": true + } + ] + } +} \ No newline at end of file diff --git a/VkNet/Enums/StringEnums/AppType.cs b/VkNet/Enums/StringEnums/AppType.cs index a70d8ba98..52488f1da 100644 --- a/VkNet/Enums/StringEnums/AppType.cs +++ b/VkNet/Enums/StringEnums/AppType.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using VkNet.Utils.JsonConverter; namespace VkNet.Enums.StringEnums; @@ -8,7 +7,7 @@ namespace VkNet.Enums.StringEnums; /// Тип приложения. /// [StringEnum] -[JsonConverter(typeof(StringEnumConverter), typeof(SnakeCaseNamingStrategy))] +[JsonConverter(typeof(TolerantStringEnumConverter))] public enum AppType { /// diff --git a/VkNet/Enums/StringEnums/AsrStatus.cs b/VkNet/Enums/StringEnums/AsrStatus.cs index 72ea4979b..ae9f4a166 100644 --- a/VkNet/Enums/StringEnums/AsrStatus.cs +++ b/VkNet/Enums/StringEnums/AsrStatus.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using VkNet.Utils.JsonConverter; namespace VkNet.Enums.StringEnums; @@ -8,7 +7,7 @@ namespace VkNet.Enums.StringEnums; /// Права пользователя в рекламном кабинете. /// [StringEnum] -[JsonConverter(typeof(StringEnumConverter), typeof(SnakeCaseNamingStrategy))] +[JsonConverter(typeof(TolerantStringEnumConverter))] public enum AsrStatus { /// diff --git a/VkNet/Infrastructure/IStreamingContextTypeDictionary.cs b/VkNet/Infrastructure/IStreamingContextTypeDictionary.cs new file mode 100644 index 000000000..86c303879 --- /dev/null +++ b/VkNet/Infrastructure/IStreamingContextTypeDictionary.cs @@ -0,0 +1,28 @@ +using System; +using JetBrains.Annotations; + +namespace VkNet.Infrastructure; + +/// +/// Интерфейс словаря контекста +/// +public interface IStreamingContextTypeDataDictionary +{ + + /// + /// Добавить данные + /// + /// Тип + /// Данные + public void AddData(Type type, [CanBeNull] object data); + + /// + /// Попытка получения данных + /// + /// Тип + /// Данные + /// + /// Успешность получения данных + /// + public bool TryGetData(Type type, [CanBeNull] out object data); +} \ No newline at end of file diff --git a/VkNet/Infrastructure/StreamingContextExtensions.cs b/VkNet/Infrastructure/StreamingContextExtensions.cs new file mode 100644 index 000000000..fad4cea84 --- /dev/null +++ b/VkNet/Infrastructure/StreamingContextExtensions.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.Serialization; +using JetBrains.Annotations; + +namespace VkNet.Infrastructure; + +/// +/// Класс для кэширования данных в StreamingContext +/// +public static class StreamingContextExtensions +{ + + /// + /// Добавить данные в контекст + /// + /// Контекст данных + /// Тип + /// Данные + /// + /// Контекст + /// + public static StreamingContext AddTypeData(this StreamingContext context, Type type, [CanBeNull] object data) + { + var dictionary = context.Context switch + { + null => new StreamingContextTypeDataDictionary(), + IStreamingContextTypeDataDictionary d => d, + _ => throw new InvalidOperationException($"context.Context is already populated with {context.Context}") + }; + + dictionary.AddData(type, data); + return new(context.State, dictionary); + } + + /// + /// Попытка получения данных из контекста + /// + /// Контекст + /// Тип + /// Данные + /// + /// Успешность получения данных + /// + public static bool TryGetTypeData(this StreamingContext context, Type type, [CanBeNull] out object data) + { + if (context.Context is IStreamingContextTypeDataDictionary dictionary) + { + return dictionary.TryGetData(type, out data); + } + + data = null; + return false; + } +} \ No newline at end of file diff --git a/VkNet/Infrastructure/StreamingContextTypeDataDictionary.cs b/VkNet/Infrastructure/StreamingContextTypeDataDictionary.cs new file mode 100644 index 000000000..7a351ebf8 --- /dev/null +++ b/VkNet/Infrastructure/StreamingContextTypeDataDictionary.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace VkNet.Infrastructure; + +/// +internal class StreamingContextTypeDataDictionary : IStreamingContextTypeDataDictionary +{ + private readonly Dictionary _dictionary = new (); + + /// + public void AddData(Type type, object data) => _dictionary.Add(type, data); + + /// + public bool TryGetData(Type type, out object data) => _dictionary.TryGetValue(type, out data); +} \ No newline at end of file diff --git a/VkNet/Model/Ads/AdsAccount.cs b/VkNet/Model/Ads/AdsAccount.cs index d84b34185..9459595db 100644 --- a/VkNet/Model/Ads/AdsAccount.cs +++ b/VkNet/Model/Ads/AdsAccount.cs @@ -42,5 +42,5 @@ public class AdsAccount /// Права пользователя в рекламном кабинете. /// [JsonProperty(propertyName: "access_role")] - public AccessRole AccessRole { get; set; } + public AccessRole? AccessRole { get; set; } } \ No newline at end of file diff --git a/VkNet/Model/Apps/App.cs b/VkNet/Model/Apps/App.cs index 45c81ced3..519498909 100644 --- a/VkNet/Model/Apps/App.cs +++ b/VkNet/Model/Apps/App.cs @@ -65,7 +65,7 @@ public class App /// Тип приложения:. /// [JsonProperty("type")] - public AppType Type { get; set; } + public AppType? Type { get; set; } /// /// Категория приложения. diff --git a/VkNet/Model/Attachments/Link.cs b/VkNet/Model/Attachments/Link.cs index 568ec937f..c32ce32e4 100644 --- a/VkNet/Model/Attachments/Link.cs +++ b/VkNet/Model/Attachments/Link.cs @@ -15,20 +15,20 @@ public class Link : MediaAttachment /// protected override string Alias => "link"; - private long? _id; + private long? _oldId; /// /// Возвращает идентификатор Link в числовом значении /// - public new long? Id + public long? OldId { get { if (long.TryParse(LinkId, out var temporaryId)) { - _id = temporaryId; + _oldId = temporaryId; } - return _id; + return _oldId; } - set => _id = value; + set => _oldId = value; } /// diff --git a/VkNet/Model/ClientInfo.cs b/VkNet/Model/ClientInfo.cs index 7f43323ef..68691a1da 100644 --- a/VkNet/Model/ClientInfo.cs +++ b/VkNet/Model/ClientInfo.cs @@ -16,7 +16,7 @@ public class ClientInfo /// Массив кнопок, которые поддерживает клиент. /// [JsonProperty("button_actions")] - public IEnumerable ButtonActions { get; set; } + public KeyboardButtonActionType[] ButtonActions { get; set; } /// /// Поддерживается ли клавиатура ботов клиентом. diff --git a/VkNet/Model/Results/Asr/AudioRecordingTask.cs b/VkNet/Model/Results/Asr/AudioRecordingTask.cs index 370126b6e..3c848a0f6 100644 --- a/VkNet/Model/Results/Asr/AudioRecordingTask.cs +++ b/VkNet/Model/Results/Asr/AudioRecordingTask.cs @@ -20,7 +20,7 @@ public class AudioRecordingTask /// Статус задачи на обработку аудиозаписи. /// [JsonProperty("status")] - public AsrStatus Status { get; set; } + public AsrStatus? Status { get; set; } /// /// Расшифровка текста. Имеет значение, если параметр status имеет значение finished. diff --git a/VkNet/Model/Video/VideoAds.cs b/VkNet/Model/Video/VideoAds.cs index 6587184ff..1bb08cc05 100644 --- a/VkNet/Model/Video/VideoAds.cs +++ b/VkNet/Model/Video/VideoAds.cs @@ -48,7 +48,7 @@ public class VideoAds /// /// [JsonProperty("sections")] - public ReadOnlyCollection Sections { get; set; } + public VideoAdsSection[] Sections { get; set; } /// /// TODO: Undocumented diff --git a/VkNet/Utils/JsonConverter/TolerantStringEnumConverter.cs b/VkNet/Utils/JsonConverter/TolerantStringEnumConverter.cs new file mode 100644 index 000000000..3d97e71d6 --- /dev/null +++ b/VkNet/Utils/JsonConverter/TolerantStringEnumConverter.cs @@ -0,0 +1,38 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using VkNet.Infrastructure; + +namespace VkNet.Utils.JsonConverter; + +/// +public class TolerantStringEnumConverter : StringEnumConverter +{ + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + try + { + NamingStrategy = new SnakeCaseNamingStrategy(); + return base.ReadJson(reader, objectType, existingValue, serializer); + } + catch(System.Exception e) + { + var parameter = serializer.Context.TryGetTypeData(typeof(TolerantStringEnumConverter), out var s) ? (bool?)s : null; + + if (IsNullableType(objectType) && parameter is true) + { + //_logger.LogError($"\nПОЖАЛУЙСТА ОТКРОЙТЕ ISSUE: {$"https://github.com/vknet/vk/issues/new?title=Add%20new%20value%20to%20{objectType.GenericTypeArguments.FirstOrDefault()}&body={reader.Value}"}\n" + e.Message); + return null; + } + + throw; + } + } + + private static bool IsNullableType(Type t) + { + return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>); + } +} \ No newline at end of file diff --git a/VkNet/Utils/Utilities.cs b/VkNet/Utils/Utilities.cs index c0c5db894..15b573ae7 100644 --- a/VkNet/Utils/Utilities.cs +++ b/VkNet/Utils/Utilities.cs @@ -215,6 +215,67 @@ private static string ToCamelCase(this string str) => str.Split(new[] char.ToUpperInvariant(s[0]) + s.Substring(1, s.Length - 1)) .Aggregate(string.Empty, (s1, s2) => s1 + s2); + private static bool? IsNullableType(Type t) + { + if (t is null) + { + return null; + } + return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + /// + /// Проверка на StringEnum + /// + /// Исходный тип + /// + /// Признак строкового перечисления + /// + public static bool IsNullableStringEnum(Type t) + { + var isNull = IsNullableType(t.GenericTypeArguments.FirstOrDefault()); + StringEnumAttribute myAttribute = null; + if (isNull is null) + { + isNull = true; + } + if(isNull is false) + { + myAttribute = (StringEnumAttribute) Attribute.GetCustomAttribute(t.GenericTypeArguments.FirstOrDefault(), + typeof(StringEnumAttribute)); + } + + // Get instance of the attribute. + + return myAttribute is not null; + } + + /// + /// Проверка на StringEnum + /// + /// Исходный тип + /// + /// Признак строкового перечисления + /// + public static bool IsTolerantEnum(Type t) + { + var isNull = IsNullableType(t.GenericTypeArguments.FirstOrDefault()); + JsonConverterAttribute myAttribute = null; + if (isNull is null) + { + isNull = true; + } + if(isNull is false) + { + myAttribute = (JsonConverterAttribute) Attribute.GetCustomAttribute(t.GenericTypeArguments.FirstOrDefault(), + typeof(JsonConverterAttribute)); + } + + // Get instance of the attribute. + + return myAttribute is not null; + } + /// /// Проверка на StringEnum /// diff --git a/VkNet/VkApi.cs b/VkNet/VkApi.cs index 9662413d6..4f14c7db9 100644 --- a/VkNet/VkApi.cs +++ b/VkNet/VkApi.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -74,6 +75,11 @@ public class VkApi : IVkApi /// private ILogger _logger; + /// + /// Обработчик ошибок десериализации + /// + public bool? DeserializationErrorHandler { get; set; } + #pragma warning disable S1104 // Fields should not have public accessibility /// /// Rest Client @@ -270,13 +276,22 @@ public T Call(string methodName, VkParameters parameters, bool skipAuthorizat { var answer = CallBase(methodName, parameters, skipAuthorization); + var context = new StreamingContext(StreamingContextStates.All) + .AddTypeData(typeof(TolerantStringEnumConverter), DeserializationErrorHandler); + + JsonConvert.DefaultSettings = () => new() + { + Context = context + }; + var settings = new JsonSerializerSettings { Converters = new List(), - ContractResolver = new DefaultContractResolver + ContractResolver = new DefaultContractResolver() { NamingStrategy = new SnakeCaseNamingStrategy() }, + Context = context, MaxDepth = null, ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; diff --git a/VkNet/VkNet.csproj b/VkNet/VkNet.csproj index 68156c7c3..80e7c54c1 100644 --- a/VkNet/VkNet.csproj +++ b/VkNet/VkNet.csproj @@ -50,7 +50,7 @@ - +