Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Allow treating unsuccessful status code as non-error, avoid setting e…
…xception
  • Loading branch information
alexeyzimarev committed Nov 19, 2025
commit f8e891aa3e6c73065409f0d73a5067cd89e0e312
4 changes: 2 additions & 2 deletions src/RestSharp/Extensions/HttpResponseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
namespace RestSharp.Extensions;

static class HttpResponseExtensions {
public static Exception? MaybeException(this HttpResponseMessage httpResponse)
=> httpResponse.IsSuccessStatusCode
public static Exception? MaybeException(this HttpResponseMessage httpResponse, bool throwOnUnsuccessfulStatusCode)
=> httpResponse.IsSuccessStatusCode || !throwOnUnsuccessfulStatusCode
? null
#if NET
: new HttpRequestException($"Request failed with status code {httpResponse.StatusCode}", null, httpResponse.StatusCode);
Expand Down
1 change: 0 additions & 1 deletion src/RestSharp/Options/ReadOnlyRestClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ namespace RestSharp;
public partial class ReadOnlyRestClientOptions {
public IReadOnlyCollection<Interceptor>? Interceptors { get; private set; }

// partial void CopyAdditionalProperties(RestClientOptions inner);
partial void CopyAdditionalProperties(RestClientOptions inner) => Interceptors = GetInterceptors(inner);

static ReadOnlyCollection<Interceptor>? GetInterceptors(RestClientOptions? options) {
Expand Down
6 changes: 6 additions & 0 deletions src/RestSharp/Options/RestClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba
/// </summary>
public bool ThrowOnAnyError { get; set; }

/// <summary>
/// When set to false, the client doesn't throw an exception when the response status code is not successful.
/// Default is true.
/// </summary>
public bool ErrorWhenUnsuccessfulStatusCode { get; set; } = true;

/// <summary>
/// Set to true to allow multiple default parameters with the same name. Default is false.
/// This setting doesn't apply to headers as multiple header values for the same key is allowed.
Expand Down
20 changes: 9 additions & 11 deletions src/RestSharp/Response/RestResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
// limitations under the License.

using System.Diagnostics;
using System.Text;
using RestSharp.Extensions;

// ReSharper disable SuggestBaseTypeForParameter
Expand All @@ -39,12 +38,11 @@ public partial class RestResponse<T>(RestRequest request) : RestResponse(request
[DebuggerDisplay($"{{{nameof(DebuggerDisplay)}()}}")]
public class RestResponse(RestRequest request) : RestResponseBase(request) {
internal static async Task<RestResponse> FromHttpResponse(
HttpResponseMessage httpResponse,
RestRequest request,
Encoding encoding,
CookieCollection? cookieCollection,
CalculateResponseStatus calculateResponseStatus,
CancellationToken cancellationToken
HttpResponseMessage httpResponse,
RestRequest request,
ReadOnlyRestClientOptions options,
CookieCollection? cookieCollection,
CancellationToken cancellationToken
) {
return request.AdvancedResponseWriter?.Invoke(httpResponse, request) ?? await GetDefaultResponse().ConfigureAwait(false);

Expand All @@ -56,7 +54,7 @@ async Task<RestResponse> GetDefaultResponse() {
#endif

var bytes = stream == null ? null : await stream.ReadAsBytes(cancellationToken).ConfigureAwait(false);
var content = bytes == null ? null : await httpResponse.GetResponseString(bytes, encoding);
var content = bytes == null ? null : await httpResponse.GetResponseString(bytes, options.Encoding);

return new(request) {
Content = content,
Expand All @@ -65,11 +63,11 @@ async Task<RestResponse> GetDefaultResponse() {
ContentLength = httpResponse.Content?.Headers.ContentLength,
ContentType = httpResponse.Content?.Headers.ContentType?.MediaType,
Cookies = cookieCollection,
ErrorException = httpResponse.MaybeException(),
ErrorException = httpResponse.MaybeException(options.ErrorWhenUnsuccessfulStatusCode),
Headers = httpResponse.Headers.GetHeaderParameters(),
IsSuccessStatusCode = httpResponse.IsSuccessStatusCode,
RawBytes = bytes,
ResponseStatus = calculateResponseStatus(httpResponse),
ResponseStatus = options.CalculateResponseStatus(httpResponse),
ResponseUri = httpResponse.RequestMessage?.RequestUri,
RootElement = request.RootElement,
Server = httpResponse.Headers.Server.ToString(),
Expand All @@ -83,4 +81,4 @@ async Task<RestResponse> GetDefaultResponse() {
public RestResponse() : this(new()) { }
}

public delegate ResponseStatus CalculateResponseStatus(HttpResponseMessage httpResponse);
public delegate ResponseStatus CalculateResponseStatus(HttpResponseMessage httpResponse);
5 changes: 2 additions & 3 deletions src/RestSharp/RestClient.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ public async Task<RestResponse> ExecuteAsync(RestRequest request, CancellationTo
? await RestResponse.FromHttpResponse(
internalResponse.ResponseMessage!,
request,
Options.Encoding,
Options,
internalResponse.CookieContainer?.GetCookies(internalResponse.Url),
Options.CalculateResponseStatus,
cancellationToken
)
.ConfigureAwait(false)
Expand All @@ -49,7 +48,7 @@ public async Task<RestResponse> ExecuteAsync(RestRequest request, CancellationTo
request.CompletionOption = HttpCompletionOption.ResponseHeadersRead;
var response = await ExecuteRequestAsync(request, cancellationToken).ConfigureAwait(false);

var exception = response.Exception ?? response.ResponseMessage?.MaybeException();
var exception = response.Exception ?? response.ResponseMessage?.MaybeException(Options.ErrorWhenUnsuccessfulStatusCode);

if (exception != null) {
return Options.ThrowOnAnyError ? throw exception : null;
Expand Down
15 changes: 15 additions & 0 deletions test/RestSharp.Tests.Integrated/RequestFailureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ public async Task Handles_GET_Request_Errors_With_Response_Type() {
var response = await _client.ExecuteAsync<SuccessResponse>(request);

response.StatusCode.Should().Be(HttpStatusCode.NotFound);
response.IsSuccessStatusCode.Should().BeFalse();
response.ErrorException.Should().NotBeNull();
response.Data.Should().Be(null);
}

[Fact]
public async Task Does_not_throw_on_unsuccessful_status_code_with_option() {
using var client = new RestClient(new RestClientOptions(server.Url!) { ErrorWhenUnsuccessfulStatusCode = false });
var request = new RestRequest("status?code=404");
var response = await client.ExecuteAsync<SuccessResponse>(request);

response.StatusCode.Should().Be(HttpStatusCode.NotFound);
response.IsSuccessStatusCode.Should().BeFalse();
response.ErrorException.Should().BeNull();
response.Data.Should().Be(null);
}

Expand All @@ -29,6 +43,7 @@ public async Task Throws_on_unsuccessful_call() {
using var client = new RestClient(new RestClientOptions(server.Url!) { ThrowOnAnyError = true });
var request = new RestRequest("status?code=500");

// ReSharper disable once AccessToDisposedClosure
var task = () => client.ExecuteAsync<SuccessResponse>(request);
await task.Should().ThrowExactlyAsync<HttpRequestException>();
}
Expand Down
Loading