Skip to content

Commit

Permalink
Fixes (#1750)
Browse files Browse the repository at this point in the history
* * Quoting form parameters (optional)
* Completed on 404 (can override)
* Test for IPv6
  • Loading branch information
alexeyzimarev committed Feb 11, 2022
1 parent 21feeb0 commit fc27aef
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 21 deletions.
4 changes: 3 additions & 1 deletion src/RestSharp/Request/RequestContent.cs
Expand Up @@ -16,6 +16,7 @@
using System.Runtime.Serialization;
using RestSharp.Extensions;
using static RestSharp.KnownHeaders;
// ReSharper disable InvertIf

// ReSharper disable SuggestBaseTypeForParameter

Expand Down Expand Up @@ -149,9 +150,10 @@ class RequestContent : IDisposable {
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
);
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/RestSharp/Request/RestRequest.cs
Expand Up @@ -68,6 +68,13 @@ public RestRequest(Uri resource, Method method = Method.Get)
/// </summary>
public bool AlwaysMultipartFormData { get; set; }

/// <summary>
/// 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).
/// </summary>
public bool MultipartFormQuoteParameters { get; set; }

public string? FormBoundary { get; set; }

/// <summary>
Expand Down
19 changes: 11 additions & 8 deletions src/RestSharp/Response/RestResponse.cs
Expand Up @@ -61,11 +61,12 @@ public static RestResponse<T> FromResponse(RestResponse response)
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "()}")]
public class RestResponse : RestResponseBase {
internal static async Task<RestResponse> 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);

Expand All @@ -78,7 +79,7 @@ CancellationToken cancellationToken
#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,
Expand All @@ -87,7 +88,7 @@ CancellationToken cancellationToken
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(),
Expand Down Expand Up @@ -122,4 +123,6 @@ CancellationToken cancellationToken
}
}
}
}
}

public delegate ResponseStatus CalculateResponseStatus(HttpResponseMessage httpResponse);
3 changes: 1 addition & 2 deletions src/RestSharp/RestClient.Async.cs
Expand Up @@ -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 {
Expand All @@ -33,6 +31,7 @@ public partial class RestClient {
request,
Options.Encoding,
CookieContainer.GetCookies(internalResponse.Url),
CalculateResponseStatus,
cancellationToken
)
.ConfigureAwait(false)
Expand Down
18 changes: 12 additions & 6 deletions src/RestSharp/RestClient.cs
Expand Up @@ -35,6 +35,14 @@ public partial class RestClient : IDisposable {
/// </summary>
public string[] AcceptedContentTypes { get; set; } = null!;

/// <summary>
/// Function to calculate the response status. By default, the status will be Completed if it was successful, or NotFound.
/// </summary>
public CalculateResponseStatus CalculateResponseStatus { get; set; } = httpResponse
=> httpResponse.IsSuccessStatusCode || httpResponse.StatusCode == HttpStatusCode.NotFound
? ResponseStatus.Completed
: ResponseStatus.Error;

HttpClient HttpClient { get; }

internal RestClientOptions Options { get; }
Expand Down Expand Up @@ -103,8 +111,7 @@ public partial class RestClient : IDisposable {
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);
}

Expand All @@ -126,8 +133,7 @@ public partial class RestClient : IDisposable {
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}

if (Options.MaxRedirects.HasValue)
handler.MaxAutomaticRedirections = Options.MaxRedirects.Value;
if (Options.MaxRedirects.HasValue) handler.MaxAutomaticRedirections = Options.MaxRedirects.Value;
}

internal Func<string, string> Encode { get; set; } = s => s.UrlEncode();
Expand All @@ -153,7 +159,7 @@ public partial class RestClient : IDisposable {
);

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));
}
Expand Down Expand Up @@ -212,4 +218,4 @@ internal void AssignAcceptedContentTypes()
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
23 changes: 20 additions & 3 deletions test/RestSharp.Tests.Integrated/RequestFailureTests.cs
Expand Up @@ -34,28 +34,45 @@ public class RequestFailureTests {
[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<Response>(request);
await task.Should().ThrowExactlyAsync<HttpRequestException>();
}

[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<HttpRequestException>();
}

[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<Response>(request);
await task.Should().ThrowExactlyAsync<HttpRequestException>();
}

[Fact]
public async Task GetAsync_returns_null_on_404() {
var request = new RestRequest("status?code=404");

var response = await _client.GetAsync<Response>(request);
response.Should().BeNull();
}

class Response {
public string Message { get; set; }
}
Expand Down
13 changes: 12 additions & 1 deletion test/RestSharp.Tests/UrlBuilderTests.cs
Expand Up @@ -357,4 +357,15 @@ public void GET_with_Invalid_Url_string_throws_exception()

Assert.Equal(expected, output);
}
}

[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");
}
}

0 comments on commit fc27aef

Please sign in to comment.