diff --git a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs index 5fee38558..233fd0143 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateRetweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,18 +31,19 @@ public class CreateRetweetRequestTest [Fact] public async Task Send_Test() { - var responseText = File.ReadAllText("Resources/Responses/CreateRetweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateRetweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet"), url); - Assert.Contains(@"""tweet_id"":""12345""", json); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet"), request.RequestUri); + Assert.Contains(@"""tweet_id"":""12345""", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateRetweetRequest { diff --git a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs index ad08aec06..b01e95a7f 100644 --- a/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/CreateTweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,20 +31,21 @@ public class CreateTweetRequestTest [Fact] public async Task Send_Test() { - var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/tTsjMKyhajZvK4q76mpIBg/CreateTweet"), url); - Assert.Contains(@"""tweet_text"":""tetete""", json); - Assert.DoesNotContain(@"""reply"":", json); - Assert.DoesNotContain(@"""media"":", json); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/tTsjMKyhajZvK4q76mpIBg/CreateTweet"), request.RequestUri); + Assert.Contains(@"""tweet_text"":""tetete""", request.JsonString); + Assert.DoesNotContain(@"""reply"":", request.JsonString); + Assert.DoesNotContain(@"""media"":", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateTweetRequest { @@ -65,17 +61,18 @@ public async Task Send_Test() [Fact] public async Task Send_ReplyTest() { - var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Contains(@"""reply"":{""exclude_reply_user_ids"":[""11111"",""22222""],""in_reply_to_tweet_id"":""12345""}", json); + var request = Assert.IsType(x); + Assert.Contains(@"""reply"":{""exclude_reply_user_ids"":[""11111"",""22222""],""in_reply_to_tweet_id"":""12345""}", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateTweetRequest { @@ -90,17 +87,18 @@ public async Task Send_ReplyTest() [Fact] public async Task Send_MediaTest() { - var responseText = File.ReadAllText("Resources/Responses/CreateTweet_CircleTweet.json"); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/CreateTweet_CircleTweet.json"); - var mock = new Mock(); + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Contains(@"""media"":{""media_entities"":[{""media_id"":""11111"",""tagged_users"":[]},{""media_id"":""22222"",""tagged_users"":[]}],""possibly_sensitive"":false}", json); + var request = Assert.IsType(x); + Assert.Contains(@"""media"":{""media_entities"":[{""media_id"":""11111"",""tagged_users"":[]},{""media_id"":""22222"",""tagged_users"":[]}],""possibly_sensitive"":false}", request.JsonString); }) - .ReturnsAsync(responseText); + .ReturnsAsync(apiResponse); var request = new CreateTweetRequest { diff --git a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs index 9a52489d3..eceafb83a 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteRetweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,15 +31,19 @@ public class DeleteRetweetRequestTest [Fact] public async Task Send_Test() { - var mock = new Mock(); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/DeleteRetweet.json"); + + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet"), url); - Assert.Contains(@"""source_tweet_id"":""12345""", json); - }); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet"), request.RequestUri); + Assert.Contains(@"""source_tweet_id"":""12345""", request.JsonString); + }) + .ReturnsAsync(apiResponse); var request = new DeleteRetweetRequest { diff --git a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs index 3db62ccec..6cd25806b 100644 --- a/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs +++ b/OpenTween.Tests/Api/GraphQL/DeleteTweetRequestTest.cs @@ -19,11 +19,6 @@ // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Moq; using OpenTween.Connection; @@ -36,15 +31,19 @@ public class DeleteTweetRequestTest [Fact] public async Task Send_Test() { - var mock = new Mock(); + using var apiResponse = await TestUtils.CreateApiResponse("Resources/Responses/DeleteTweet.json"); + + var mock = new Mock(); mock.Setup(x => - x.PostJsonAsync(It.IsAny(), It.IsAny()) + x.SendAsync(It.IsAny()) ) - .Callback((url, json) => + .Callback(x => { - Assert.Equal(new("https://twitter.com/i/api/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet"), url); - Assert.Contains(@"""tweet_id"":""12345""", json); - }); + var request = Assert.IsType(x); + Assert.Equal(new("https://twitter.com/i/api/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet"), request.RequestUri); + Assert.Contains(@"""tweet_id"":""12345""", request.JsonString); + }) + .ReturnsAsync(apiResponse); var request = new DeleteTweetRequest { diff --git a/OpenTween.Tests/Connection/ApiResponseTest.cs b/OpenTween.Tests/Connection/ApiResponseTest.cs index a1ff5eee1..ae3a8d8de 100644 --- a/OpenTween.Tests/Connection/ApiResponseTest.cs +++ b/OpenTween.Tests/Connection/ApiResponseTest.cs @@ -115,5 +115,50 @@ public async Task ReadAsJsonXml_InvalidJsonTest() ); Assert.Equal("### Invalid JSON Response ###", ex.ResponseText); } + + [Fact] + public async Task ReadAsLazyJson_Test() + { + using var responseContent = new StringContent("""{"foo":123}"""); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + using var lazyJson = response.ReadAsLazyJson(); + Assert.Equal(new() { Foo = 123 }, await lazyJson.LoadJsonAsync()); + } + + [Fact] + public async Task ReadAsLazyJson_DisposeTest() + { + using var responseContent = new StringContent("""{"foo":123}"""); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + using var lazyJson = response.ReadAsLazyJson(); + response.Dispose(); // ApiResponse を先に破棄しても LazyJson に影響しないことをテストする + + Assert.Equal(new() { Foo = 123 }, await lazyJson.LoadJsonAsync()); + } + + [Fact] + public async Task ReadAsString_Test() + { + using var responseContent = new StringContent("foo"); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = responseContent, + }; + using var response = new ApiResponse(responseMessage); + + Assert.Equal("foo", await response.ReadAsString()); + } } } diff --git a/OpenTween.Tests/Connection/PostJsonRequestTest.cs b/OpenTween.Tests/Connection/PostJsonRequestTest.cs new file mode 100644 index 000000000..d8b5f283e --- /dev/null +++ b/OpenTween.Tests/Connection/PostJsonRequestTest.cs @@ -0,0 +1,48 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTween.Connection +{ + public class PostJsonRequestTest + { + [Fact] + public async Task CreateMessage_Test() + { + var request = new PostJsonRequest + { + RequestUri = new("aaa/bbb.json", UriKind.Relative), + JsonString = """{"foo":12345}""", + }; + + var baseUri = new Uri("https://example.com/v1/"); + using var requestMessage = request.CreateMessage(baseUri); + + Assert.Equal(HttpMethod.Post, requestMessage.Method); + Assert.Equal(new("https://example.com/v1/aaa/bbb.json"), requestMessage.RequestUri); + Assert.Equal("""{"foo":12345}""", await requestMessage.Content.ReadAsStringAsync()); + } + } +} diff --git a/OpenTween.Tests/OpenTween.Tests.csproj b/OpenTween.Tests/OpenTween.Tests.csproj index 70754bdeb..dbaad23f1 100644 --- a/OpenTween.Tests/OpenTween.Tests.csproj +++ b/OpenTween.Tests/OpenTween.Tests.csproj @@ -59,6 +59,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/OpenTween.Tests/Resources/Responses/DeleteRetweet.json b/OpenTween.Tests/Resources/Responses/DeleteRetweet.json new file mode 100644 index 000000000..f125b0470 --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/DeleteRetweet.json @@ -0,0 +1,14 @@ +{ + "data": { + "unretweet": { + "source_tweet_results": { + "result": { + "rest_id": "1234567890123456789", + "legacy": { + "full_text": "foo" + } + } + } + } + } +} diff --git a/OpenTween.Tests/Resources/Responses/DeleteTweet.json b/OpenTween.Tests/Resources/Responses/DeleteTweet.json new file mode 100644 index 000000000..919d638ea --- /dev/null +++ b/OpenTween.Tests/Resources/Responses/DeleteTweet.json @@ -0,0 +1,7 @@ +{ + "data": { + "delete_tweet": { + "tweet_results": {} + } + } +} diff --git a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs index 35b5060e2..16df55b24 100644 --- a/OpenTween/Api/GraphQL/CreateRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateRetweetRequest.cs @@ -22,14 +22,7 @@ #nullable enable using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Connection; using OpenTween.Models; @@ -49,14 +42,20 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var response = await apiConnection.PostJsonAsync(EndpointUri, json); - var responseBytes = Encoding.UTF8.GetBytes(response); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); - var rootElm = XElement.Load(jsonReader); ErrorResponse.ThrowIfError(rootElm); var tweetIdStr = rootElm.XPathSelectElement("/data/create_retweet/retweet_results/result/rest_id")?.Value ?? throw CreateParseError(); diff --git a/OpenTween/Api/GraphQL/CreateTweetRequest.cs b/OpenTween/Api/GraphQL/CreateTweetRequest.cs index 46e1075ac..5caf3ac22 100644 --- a/OpenTween/Api/GraphQL/CreateTweetRequest.cs +++ b/OpenTween/Api/GraphQL/CreateTweetRequest.cs @@ -23,14 +23,9 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; -using System.Text; using System.Threading.Tasks; -using System.Xml; -using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Api.DataModel; using OpenTween.Connection; @@ -153,14 +148,20 @@ public string CreateRequestBody() return JsonUtils.SerializeJsonByDataContract(body); } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var response = await apiConnection.PostJsonAsync(EndpointUri, json); - var responseBytes = Encoding.UTF8.GetBytes(response); - using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); - var rootElm = XElement.Load(jsonReader); ErrorResponse.ThrowIfError(rootElm); var tweetElm = rootElm.XPathSelectElement("/data/create_tweet/tweet_results/result") ?? throw CreateParseError(); diff --git a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs index 00a0b8368..500ed372e 100644 --- a/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteRetweetRequest.cs @@ -22,10 +22,6 @@ #nullable enable using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using OpenTween.Connection; using OpenTween.Models; @@ -45,11 +41,21 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var responseText = await apiConnection.PostJsonAsync(EndpointUri, json); - ErrorResponse.ThrowIfError(responseText); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); + + ErrorResponse.ThrowIfError(rootElm); } } } diff --git a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs index adccacec0..db3b28c63 100644 --- a/OpenTween/Api/GraphQL/DeleteTweetRequest.cs +++ b/OpenTween/Api/GraphQL/DeleteTweetRequest.cs @@ -22,10 +22,6 @@ #nullable enable using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using OpenTween.Connection; using OpenTween.Models; @@ -45,11 +41,21 @@ public string CreateRequestBody() """; } - public async Task Send(IApiConnectionLegacy apiConnection) + public async Task Send(IApiConnection apiConnection) { - var json = this.CreateRequestBody(); - var responseText = await apiConnection.PostJsonAsync(EndpointUri, json); - ErrorResponse.ThrowIfError(responseText); + var request = new PostJsonRequest + { + RequestUri = EndpointUri, + JsonString = this.CreateRequestBody(), + }; + + using var response = await apiConnection.SendAsync(request) + .ConfigureAwait(false); + + var rootElm = await response.ReadAsJsonXml() + .ConfigureAwait(false); + + ErrorResponse.ThrowIfError(rootElm); } } } diff --git a/OpenTween/Connection/ApiResponse.cs b/OpenTween/Connection/ApiResponse.cs index 3eabcb661..ee1560058 100644 --- a/OpenTween/Connection/ApiResponse.cs +++ b/OpenTween/Connection/ApiResponse.cs @@ -38,6 +38,7 @@ public sealed class ApiResponse : IDisposable public bool IsDisposed { get; private set; } private readonly HttpResponseMessage responseMessage; + private bool preventDisposeResponse; public ApiResponse(HttpResponseMessage responseMessage) => this.responseMessage = responseMessage; @@ -47,7 +48,9 @@ public void Dispose() if (this.IsDisposed) return; - this.responseMessage.Dispose(); + if (!this.preventDisposeResponse) + this.responseMessage.Dispose(); + this.IsDisposed = true; } @@ -95,5 +98,20 @@ public async Task ReadAsJsonXml() throw new TwitterApiException("Invalid JSON", ex) { ResponseText = responseText }; } } + + public LazyJson ReadAsLazyJson() + { + this.preventDisposeResponse = true; + + return new(this.responseMessage); + } + + public async Task ReadAsString() + { + using var content = this.responseMessage.Content; + + return await content.ReadAsStringAsync() + .ConfigureAwait(false); + } } } diff --git a/OpenTween/Connection/PostJsonRequest.cs b/OpenTween/Connection/PostJsonRequest.cs new file mode 100644 index 000000000..715d54182 --- /dev/null +++ b/OpenTween/Connection/PostJsonRequest.cs @@ -0,0 +1,46 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2023 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.Net.Http; +using System.Text; + +namespace OpenTween.Connection +{ + public class PostJsonRequest : IHttpRequest + { + public required Uri RequestUri { get; set; } + + public required string JsonString { get; set; } + + public string? EndpointName { get; set; } + + public HttpRequestMessage CreateMessage(Uri baseUri) + => new() + { + Method = HttpMethod.Post, + RequestUri = new(baseUri, this.RequestUri), + Content = new StringContent(this.JsonString, Encoding.UTF8, "application/json"), + }; + } +} diff --git a/OpenTween/Connection/TwitterApiConnection.cs b/OpenTween/Connection/TwitterApiConnection.cs index 091a32032..35a8e0b94 100644 --- a/OpenTween/Connection/TwitterApiConnection.cs +++ b/OpenTween/Connection/TwitterApiConnection.cs @@ -368,67 +368,31 @@ await TwitterApiConnection.CheckStatusCode(response) public async Task PostJsonAsync(Uri uri, string json) { - var requestUri = new Uri(RestApiBase, uri); - using var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - - using var postContent = new StringContent(json, Encoding.UTF8, "application/json"); - request.Content = postContent; - - try + var request = new PostJsonRequest { - using var response = await this.Http.SendAsync(request) - .ConfigureAwait(false); + RequestUri = uri, + JsonString = json, + }; - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); + using var response = await this.SendAsync(request) + .ConfigureAwait(false); - return await response.Content.ReadAsStringAsync() - .ConfigureAwait(false); - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } + return await response.ReadAsString() + .ConfigureAwait(false); } public async Task> PostJsonAsync(Uri uri, string json) { - var requestUri = new Uri(RestApiBase, uri); - var request = new HttpRequestMessage(HttpMethod.Post, requestUri); - - using var postContent = new StringContent(json, Encoding.UTF8, "application/json"); - request.Content = postContent; - - HttpResponseMessage? response = null; - try + var request = new PostJsonRequest { - response = await this.Http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) - .ConfigureAwait(false); - - await TwitterApiConnection.CheckStatusCode(response) - .ConfigureAwait(false); + RequestUri = uri, + JsonString = json, + }; - var result = new LazyJson(response); - response = null; + using var response = await this.SendAsync(request) + .ConfigureAwait(false); - return result; - } - catch (HttpRequestException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - catch (OperationCanceledException ex) - { - throw TwitterApiException.CreateFromException(ex); - } - finally - { - response?.Dispose(); - } + return response.ReadAsLazyJson(); } public async Task DeleteAsync(Uri uri)