diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs index 9df44d6a9..9715ccb1e 100644 --- a/src/RestSharp/Request/RequestContent.cs +++ b/src/RestSharp/Request/RequestContent.cs @@ -16,6 +16,7 @@ using System.Runtime.Serialization; using RestSharp.Extensions; using static RestSharp.KnownHeaders; +// ReSharper disable InvertIf // ReSharper disable SuggestBaseTypeForParameter @@ -153,9 +154,10 @@ void AddPostParameters(ParametersCollection? postParameters) { if (Content is MultipartFormDataContent mpContent) { // we got the multipart form already instantiated, just add parameters to it foreach (var postParameter in postParameters!) { + var parameterName = postParameter.Name!; mpContent.Add( new StringContent(postParameter.Value!.ToString()!, _client.Options.Encoding, postParameter.ContentType), - postParameter.Name! + _request.MultipartFormQuoteParameters ? $"\"{parameterName}\"" : parameterName ); } } diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index 9a0c0f45f..bf6fbb5c5 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -68,6 +68,13 @@ public RestRequest(Uri resource, Method method = Method.Get) /// public bool AlwaysMultipartFormData { get; set; } + /// + /// When set to true, parameters in a multipart form data requests will be enclosed in + /// quotation marks. Default is false. Enable it if the remote endpoint requires parameters + /// to be in quotes (for example, FreshDesk API). + /// + public bool MultipartFormQuoteParameters { get; set; } + public string? FormBoundary { get; set; } /// diff --git a/src/RestSharp/Response/RestResponse.cs b/src/RestSharp/Response/RestResponse.cs index 2d97cf11c..b1b87bb10 100644 --- a/src/RestSharp/Response/RestResponse.cs +++ b/src/RestSharp/Response/RestResponse.cs @@ -61,11 +61,12 @@ public static RestResponse FromResponse(RestResponse response) [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "()}")] public class RestResponse : RestResponseBase { internal static async Task FromHttpResponse( - HttpResponseMessage httpResponse, - RestRequest request, - Encoding encoding, - CookieCollection cookieCollection, - CancellationToken cancellationToken + HttpResponseMessage httpResponse, + RestRequest request, + Encoding encoding, + CookieCollection cookieCollection, + CalculateResponseStatus calculateResponseStatus, + CancellationToken cancellationToken ) { return request.AdvancedResponseWriter?.Invoke(httpResponse) ?? await GetDefaultResponse().ConfigureAwait(false); @@ -78,7 +79,7 @@ async Task GetDefaultResponse() { #endif var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false); - var content = bytes == null ? null : httpResponse.GetResponseString(bytes, encoding); + var content = bytes == null ? null : httpResponse.GetResponseString(bytes, encoding); return new RestResponse { Content = content, @@ -87,7 +88,7 @@ async Task GetDefaultResponse() { Version = httpResponse.RequestMessage?.Version, ContentLength = httpResponse.Content.Headers.ContentLength, ContentType = httpResponse.Content.Headers.ContentType?.MediaType, - ResponseStatus = httpResponse.IsSuccessStatusCode ? ResponseStatus.Completed : ResponseStatus.Error, + ResponseStatus = calculateResponseStatus(httpResponse), ErrorException = MaybeException(), ResponseUri = httpResponse.RequestMessage!.RequestUri, Server = httpResponse.Headers.Server.ToString(), @@ -122,4 +123,6 @@ async Task GetDefaultResponse() { } } } -} \ No newline at end of file +} + +public delegate ResponseStatus CalculateResponseStatus(HttpResponseMessage httpResponse); diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index f49a48f46..3b162c42e 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using RestSharp.Extensions; - namespace RestSharp; public partial class RestClient { @@ -33,6 +31,7 @@ public async Task ExecuteAsync(RestRequest request, CancellationTo request, Options.Encoding, CookieContainer.GetCookies(internalResponse.Url), + CalculateResponseStatus, cancellationToken ) .ConfigureAwait(false) diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs index e55f63c3b..a2d0b44c2 100644 --- a/src/RestSharp/RestClient.cs +++ b/src/RestSharp/RestClient.cs @@ -35,6 +35,14 @@ public partial class RestClient : IDisposable { /// public string[] AcceptedContentTypes { get; set; } = null!; + /// + /// Function to calculate the response status. By default, the status will be Completed if it was successful, or NotFound. + /// + public CalculateResponseStatus CalculateResponseStatus { get; set; } = httpResponse + => httpResponse.IsSuccessStatusCode || httpResponse.StatusCode == HttpStatusCode.NotFound + ? ResponseStatus.Completed + : ResponseStatus.Error; + HttpClient HttpClient { get; } internal RestClientOptions Options { get; } @@ -103,8 +111,7 @@ public RestClient(HttpClient httpClient, RestClientOptions? options = null, bool public RestClient(HttpMessageHandler handler, bool disposeHandler = true) : this(new HttpClient(handler, disposeHandler), null, true) { } void ConfigureHttpClient(HttpClient httpClient) { - if (Options.Timeout > 0) - httpClient.Timeout = TimeSpan.FromMilliseconds(Options.Timeout); + if (Options.Timeout > 0) httpClient.Timeout = TimeSpan.FromMilliseconds(Options.Timeout); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Options.UserAgent); } @@ -126,8 +133,7 @@ void ConfigureHttpMessageHandler(HttpClientHandler handler) { handler.ClientCertificateOptions = ClientCertificateOption.Manual; } - if (Options.MaxRedirects.HasValue) - handler.MaxAutomaticRedirections = Options.MaxRedirects.Value; + if (Options.MaxRedirects.HasValue) handler.MaxAutomaticRedirections = Options.MaxRedirects.Value; } internal Func Encode { get; set; } = s => s.UrlEncode(); @@ -153,7 +159,7 @@ public RestClient AddDefaultParameter(Parameter parameter) { ); if (!Options.AllowMultipleDefaultParametersWithSameName && - !MultiParameterTypes.Contains(parameter.Type) && + !MultiParameterTypes.Contains(parameter.Type) && DefaultParameters.Any(x => x.Name == parameter.Name)) { throw new ArgumentException("A default parameters with the same name has already been added", nameof(parameter)); } @@ -212,4 +218,4 @@ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } -} \ No newline at end of file +} diff --git a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs index e162138a5..3033a3607 100644 --- a/test/RestSharp.Tests.Integrated/RequestFailureTests.cs +++ b/test/RestSharp.Tests.Integrated/RequestFailureTests.cs @@ -34,7 +34,7 @@ public async Task Handles_GET_Request_Errors_With_Response_Type() { [Fact] public async Task Throws_on_unsuccessful_call() { var client = new RestClient(new RestClientOptions(_fixture.Server.Url) { ThrowOnAnyError = true }); - var request = new RestRequest("status?code=404"); + var request = new RestRequest("status?code=500"); var task = () => client.ExecuteAsync(request); await task.Should().ThrowExactlyAsync(); @@ -42,20 +42,37 @@ public async Task Throws_on_unsuccessful_call() { [Fact] public async Task GetAsync_throws_on_unsuccessful_call() { - var request = new RestRequest("status?code=404"); + var request = new RestRequest("status?code=500"); var task = () => _client.GetAsync(request); await task.Should().ThrowExactlyAsync(); } [Fact] - public async Task GetAsync_generic_throws_on_unsuccessful_call() { + public async Task GetAsync_completes_on_404() { var request = new RestRequest("status?code=404"); + var response = await _client.GetAsync(request); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + response.ResponseStatus.Should().Be(ResponseStatus.Completed); + } + + [Fact] + public async Task GetAsync_generic_throws_on_unsuccessful_call() { + var request = new RestRequest("status?code=500"); + var task = () => _client.GetAsync(request); await task.Should().ThrowExactlyAsync(); } + [Fact] + public async Task GetAsync_returns_null_on_404() { + var request = new RestRequest("status?code=404"); + + var response = await _client.GetAsync(request); + response.Should().BeNull(); + } + class Response { public string Message { get; set; } } diff --git a/test/RestSharp.Tests/UrlBuilderTests.cs b/test/RestSharp.Tests/UrlBuilderTests.cs index 9421d115c..bdd336c2d 100644 --- a/test/RestSharp.Tests/UrlBuilderTests.cs +++ b/test/RestSharp.Tests/UrlBuilderTests.cs @@ -357,4 +357,15 @@ public void Should_update_parameter_if_it_already_exists() { Assert.Equal(expected, output); } -} \ No newline at end of file + + [Fact] + public void Should_use_ipv6_address() { + var baseUrl = new Uri("https://[fe80::290:e8ff:fe8b:2537%en10]:8443"); + var client = new RestClient(baseUrl); + var request = new RestRequest("api/v1/auth"); + var actual = client.BuildUri(request); + + actual.HostNameType.Should().Be(UriHostNameType.IPv6); + actual.AbsoluteUri.Should().Be("https://[fe80::290:e8ff:fe8b:2537]:8443/api/v1/auth"); + } +}