• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Aldaviva / Unfucked / 41640370417

03 Nov 2026 12:59PM UTC coverage: 45.954% (-0.01%) from 45.968%
41640370417

push

github

Aldaviva
HTTP: added property configuration for per-request timeouts and auto redirection following, instead of being write-once on the entire HttpClient instance. Added overloads to IWebTarget.Send<T> and similar methods (Get<T>, Post<T>, &c.) that also return the HttpResponseMessage by assigning it in a Task to an out parameter, so you can both benefit from automatically deserialized response bodies and inspect the response status code or headers.

831 of 2437 branches covered (34.1%)

56 of 108 new or added lines in 5 files covered. (51.85%)

1471 of 3201 relevant lines covered (45.95%)

152.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/HTTP/Exceptions/HttpException.cs
1
using System.Net;
2
using System.Net.Http.Headers;
3
using System.Text;
4

5
namespace Unfucked.HTTP.Exceptions;
6

7
#pragma warning disable CS9113 // Parameter is unread. - the superclass constructor overload that reads it only exists in .NET 6, not .NET Standard 2.0
8
/// <summary>
9
/// Base exception class for all network, filtering, deserialization, and HTTP status code errors
10
/// </summary>
11
public abstract class HttpException(HttpStatusCode? status, string message, Exception? cause, HttpExceptionParams exceptionParams): HttpRequestException(MessageChain(message, cause), cause
×
12
#if NET5_0_OR_GREATER
×
13
    , status
×
14
#endif
×
15
) {
×
16

17
#pragma warning restore CS9113 // Parameter is unread.
18

19
    protected readonly HttpExceptionParams HttpExceptionParams = exceptionParams;
×
20

21
    public Uri? RequestUrl => HttpExceptionParams.RequestUrl;
×
22
    public HttpMethod Verb => HttpExceptionParams.Verb;
×
23
    public IDictionary<string, object?>? RequestProperties => HttpExceptionParams.RequestProperties;
×
24
#if NET5_0_OR_GREATER
25
    public HttpRequestOptions? RequestOptions => HttpExceptionParams.RequestOptions;
26
#endif
27

28
    private static string MessageChain(string outerMessage, Exception? cause) {
29
        StringBuilder chain = new(outerMessage);
×
30
        while (cause != null) {
×
31
            chain.Append(": ").Append(cause.Message);
×
32
            cause = cause.InnerException is var inner && cause != inner ? inner : null;
×
33
        }
34
        return chain.ToString();
×
35
    }
36

37
}
38

39
#pragma warning disable CS0618 // Type or member is obsolete - it's not obsolete in .NET Standard 2.0, which this library targets
40
public sealed record HttpExceptionParams(
×
41
    HttpMethod Verb,
×
42
    Uri? RequestUrl,
×
43
    HttpResponseHeaders? ResponseHeaders = null,
×
44
    ReadOnlyMemory<byte>? ResponseBody = null,
×
45
    IDictionary<string, object?>? RequestProperties = null
×
46
#if NET5_0_OR_GREATER
×
47
    ,
×
48
    HttpRequestOptions? RequestOptions = null
×
49
#endif
×
50
) {
×
51

52
    public static HttpExceptionParams FromRequest(HttpRequestMessage request) => new(request.Method, request.RequestUri, null, null, request.Properties
×
53
#if NET5_0_OR_GREATER
×
54
        , request.Options
×
55
#endif
×
56
    );
×
57

58
    public static async Task<HttpExceptionParams> FromResponse(HttpResponseMessage response, CancellationToken cancellationToken = default) {
59
        HttpRequestMessage? request = response.RequestMessage;
×
60

61
        ReadOnlyMemory<byte>? responseBody = null;
×
62
        try {
63
            await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
×
64
            Task<byte[]> readAsByteArrayAsync =
×
65
#if NET5_0_OR_GREATER
×
66
                response.Content.ReadAsByteArrayAsync(cancellationToken);
×
67
#else
68
                response.Content.ReadAsByteArrayAsync();
69
#endif
70
            responseBody = (await readAsByteArrayAsync.ConfigureAwait(false)).AsMemory();
×
71
        } catch (InvalidOperationException) {} catch (InvalidDataException) {
×
72
            // leave responseBody null
73
        }
×
74

75
        return new HttpExceptionParams(request?.Method ?? HttpMethod.Get, request?.RequestUri, response.Headers, responseBody, request?.Properties
×
76
#if NET5_0_OR_GREATER
×
77
            , request?.Options
×
78
#endif
×
79
        );
×
80
    }
×
81

82
}
83
#pragma warning restore CS0618 // Type or member is obsolete
84

85
/// <summary>
86
/// Unsuccessful HTTP status code
87
/// </summary>
88
public class WebApplicationException(HttpStatusCode status, string reasonPhrase, string message, HttpExceptionParams exceptionParams)
NEW
89
    : HttpException(status, message, null, exceptionParams!) {
×
90

91
    public string ReasonPhrase => reasonPhrase;
×
92
    public HttpResponseHeaders ResponseHeaders => HttpExceptionParams.ResponseHeaders!;
×
93
    public ReadOnlyMemory<byte>? ResponseBody => HttpExceptionParams.ResponseBody;
×
94

95
    public
96
#if NET5_0_OR_GREATER
97
        new
98
#endif
99
        HttpStatusCode StatusCode => status;
×
100

101
    /// <inheritdoc />
NEW
102
    public WebApplicationException(HttpStatusCode status, string reasonPhrase, HttpExceptionParams exceptionParams): this(status, reasonPhrase,
×
NEW
103
        $"{(int) status} {(reasonPhrase.HasText ? reasonPhrase : status.ToString())} from {exceptionParams?.RequestUrl}", exceptionParams!) {}
×
104

105
}
106

107
/// <summary>400–499</summary>
108
public class ClientErrorException(HttpStatusCode status, string reasonPhrase, HttpExceptionParams exceptionParams): WebApplicationException(status, reasonPhrase, exceptionParams);
×
109

110
/// <summary>400</summary>
111
public sealed class BadRequestException(string? reasonPhrase, HttpExceptionParams exceptionParams): ClientErrorException(HttpStatusCode.BadRequest, reasonPhrase ?? "Bad Request", exceptionParams);
×
112

113
/// <summary>401</summary>
114
public sealed class NotAuthorizedException(string? reasonPhrase, HttpExceptionParams exceptionParams)
115
    : ClientErrorException(HttpStatusCode.Unauthorized, reasonPhrase ?? "Unauthorized", exceptionParams);
×
116

117
/// <summary>403</summary>
118
public sealed class ForbiddenException(string? reasonPhrase, HttpExceptionParams exceptionParams): ClientErrorException(HttpStatusCode.Forbidden, reasonPhrase ?? "Forbidden", exceptionParams);
×
119

120
/// <summary>404</summary>
121
public sealed class NotFoundException(string? reasonPhrase, HttpExceptionParams exceptionParams): ClientErrorException(HttpStatusCode.NotFound, reasonPhrase ?? "Not Found", exceptionParams);
×
122

123
/// <summary>405</summary>
124
public sealed class NotAllowedException(string? reasonPhrase, HttpExceptionParams exceptionParams)
125
    : ClientErrorException(HttpStatusCode.MethodNotAllowed, reasonPhrase ?? "Method Not Allowed", exceptionParams);
×
126

127
/// <summary>406</summary>
128
public sealed class NotAcceptableException(string? reasonPhrase, HttpExceptionParams exceptionParams)
129
    : ClientErrorException(HttpStatusCode.NotAcceptable, reasonPhrase ?? "Not Acceptable", exceptionParams);
×
130

131
/// <summary>415</summary>
132
public sealed class NotSupportedException(string? reasonPhrase, HttpExceptionParams exceptionParams)
133
    : ClientErrorException(HttpStatusCode.UnsupportedMediaType, reasonPhrase ?? "Unsupported Media Type", exceptionParams);
×
134

135
/// <summary>500–599</summary>
NEW
136
public class ServerErrorException(HttpStatusCode status, string reasonPhrase, HttpExceptionParams exceptionParams): WebApplicationException(status, reasonPhrase, exceptionParams);
×
137

138
/// <summary>500</summary>
139
public sealed class InternalServerErrorException(string? reasonPhrase, HttpExceptionParams exceptionParams)
140
    : ServerErrorException(HttpStatusCode.InternalServerError, reasonPhrase ?? "Internal Server Error", exceptionParams);
×
141

142
/// <summary>503</summary>
143
public sealed class ServiceUnavailableException(string? reasonPhrase, HttpExceptionParams exceptionParams)
144
    : ServerErrorException(HttpStatusCode.ServiceUnavailable, reasonPhrase ?? "Service Unavailable", exceptionParams);
×
145

146
/// <summary>300–399</summary>
147
public sealed class RedirectionException(HttpStatusCode status, Uri? destination, string reasonPhrase, HttpExceptionParams exceptionParams)
NEW
148
    : WebApplicationException(status, reasonPhrase, $"{(int) status} {(reasonPhrase.HasText ? reasonPhrase : status.ToString())} from {exceptionParams?.RequestUrl} to {destination}",
×
NEW
149
        exceptionParams!) {
×
150

151
    public Uri? Destination { get; } = destination;
×
152

153
}
154

155
/// <summary>Network, filter, or deserialization error</summary>
156
public sealed class ProcessingException(Exception cause, HttpExceptionParams exceptionParams): HttpException(null,
×
157
    $"Network, filter, or deserialization error during HTTP {exceptionParams.Verb} request to {exceptionParams.RequestUrl?.AbsoluteUri}", cause, exceptionParams) {
×
158

159
#if NET5_0_OR_GREATER
160
    // ReSharper disable once MemberCanBeMadeStatic.Global - unhides parent property, defeating the purpose of hiding it
161
    [Obsolete(
162
        $"{nameof(ProcessingException)}s never have status codes, so this property always returns null. They represent failures in network I/O or response deserialization (such as timeouts, refused connections, or malformed JSON), rather than non-200-class status codes, which are instead represented by {nameof(WebApplicationException)}.")]
163
    public new HttpStatusCode? StatusCode => null;
164
#endif
165

166
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc