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

loresoft / FluentRest / 13399786835

18 Feb 2025 08:48PM UTC coverage: 57.756% (-0.4%) from 58.107%
13399786835

push

github

pwelter34
add support for nullable, minor cleanup, improve UrlBuilder

293 of 646 branches covered (45.36%)

Branch coverage included in aggregate %.

238 of 380 new or added lines in 20 files covered. (62.63%)

3 existing lines in 3 files now uncovered.

824 of 1288 relevant lines covered (63.98%)

56.98 hits per line

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

57.73
/src/FluentRest/HttpMessageExtensions.cs
1
// Ignore Spelling: Serializer Deserialize
2

3
using System.Diagnostics.CodeAnalysis;
4

5
namespace FluentRest;
6

7
/// <summary>
8
/// Extension method for <see cref="HttpRequestMessage"/>
9
/// </summary>
10
public static class HttpMessageExtensions
11
{
12
    public static TValue GetOrAddOption<TValue>(this HttpRequestMessage requestMessage, string key, Func<string, TValue> valueFactory)
13
    {
14
        if (requestMessage is null)
992!
NEW
15
            throw new ArgumentNullException(nameof(requestMessage));
×
16
        if (key == null)
992!
NEW
17
            throw new ArgumentNullException(nameof(key));
×
18
        if (valueFactory is null)
992!
NEW
19
            throw new ArgumentNullException(nameof(valueFactory));
×
20

21
#if NET5_0_OR_GREATER
22
        var optionKey = new HttpRequestOptionsKey<TValue>(key);
992✔
23
        if (requestMessage.Options.TryGetValue(optionKey, out var value))
992✔
24
            return value;
796✔
25

26
        value = valueFactory(key);
196✔
27
        requestMessage.Options.Set(optionKey, value);
196✔
28
        return value;
196✔
29
#else
30
        if (requestMessage.Properties.TryGetValue(key, out var propertyValue))
31
            return (TValue)propertyValue;
32

33
        var factoryValue = valueFactory(key);
34
        requestMessage.Properties.Add(key, factoryValue);
35

36
        return factoryValue;
37
#endif
38
    }
39

40
    public static bool TryGetOption<TValue>(this HttpRequestMessage requestMessage, string key, [MaybeNullWhen(false)] out TValue? value)
41
    {
42
        if (requestMessage is null)
260!
NEW
43
            throw new ArgumentNullException(nameof(requestMessage));
×
44
        if (key == null)
260!
NEW
45
            throw new ArgumentNullException(nameof(key));
×
46

47
#if NET5_0_OR_GREATER
48
        var optionKey = new HttpRequestOptionsKey<TValue>(key);
260✔
49
        return requestMessage.Options.TryGetValue(optionKey, out value);
260✔
50
#else
51
        var found = requestMessage.Properties.TryGetValue(key, out var propertyValue);
52
        value  = found ? (TValue)propertyValue : default;
53
        return found;
54
#endif
55
    }
56

57
    public static void SetOption<TValue>(this HttpRequestMessage requestMessage, string key, TValue value)
58
    {
59
        if (requestMessage is null)
94!
NEW
60
            throw new ArgumentNullException(nameof(requestMessage));
×
61
        if (key == null)
94!
NEW
62
            throw new ArgumentNullException(nameof(key));
×
63

64
#if NET5_0_OR_GREATER
65
        var optionKey = new HttpRequestOptionsKey<TValue>(key);
94✔
66
        requestMessage.Options.Set(optionKey, value);
94✔
67
#else
68
        requestMessage.Properties[key] = value;
69
#endif
70
    }
94✔
71

72
    /// <summary>
73
    /// Gets the <see cref="UrlBuilder"/> from the specified <paramref name="requestMessage" /> properties dictionary.
74
    /// </summary>
75
    /// <param name="requestMessage">The request message containing the property.</param>
76
    /// <returns>
77
    /// The <see cref="UrlBuilder"/> to modify the request message URI.
78
    /// </returns>
79
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
80
    public static UrlBuilder GetUrlBuilder(this HttpRequestMessage requestMessage)
81
    {
82
        if (requestMessage is null)
618!
83
            throw new ArgumentNullException(nameof(requestMessage));
×
84

85
        return requestMessage.GetOrAddOption(FluentProperties.RequestUrlBuilder, k =>
618✔
86
            requestMessage.RequestUri is null
108✔
87
                ? new UrlBuilder()
108✔
88
                : new UrlBuilder(requestMessage.RequestUri)
108✔
89
        );
618✔
90
    }
91

92
    /// <summary>
93
    /// Sets the <see cref="UrlBuilder" /> on the specified <paramref name="requestMessage" /> properties dictionary.
94
    /// </summary>
95
    /// <param name="requestMessage">The request message containing the property.</param>
96
    /// <param name="urlBuilder">The URL bulder to set on the properties dictionary.</param>
97
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage" /> is <see langword="null" /></exception>
98
    public static void SetUrlBuilder(this HttpRequestMessage requestMessage, UrlBuilder urlBuilder)
99
    {
100
        if (requestMessage is null)
20!
101
            throw new ArgumentNullException(nameof(requestMessage));
×
102
        if (urlBuilder is null)
20!
NEW
103
            throw new ArgumentNullException(nameof(urlBuilder));
×
104

105
        requestMessage.SetOption(FluentProperties.RequestUrlBuilder, urlBuilder);
20✔
106
    }
20✔
107

108

109
    /// <summary>
110
    /// Gets the content data from the specified <paramref name="requestMessage" /> properties dictionary.
111
    /// </summary>
112
    /// <param name="requestMessage">The request message containing the property.</param>
113
    /// <returns>
114
    /// The content data to send for the request message.
115
    /// </returns>
116
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
117
    public static object? GetContentData(this HttpRequestMessage requestMessage)
118
    {
119
        if (requestMessage is null)
52!
120
            throw new ArgumentNullException(nameof(requestMessage));
×
121

122
        requestMessage.TryGetOption<object>(FluentProperties.RequestContentData, out var propertyValue);
52✔
123
        return propertyValue;
52✔
124
    }
125

126
    /// <summary>
127
    /// Sets the content data on the specified <paramref name="requestMessage" /> properties dictionary.
128
    /// </summary>
129
    /// <param name="requestMessage">The request message containing the property.</param>
130
    /// <param name="contentData">The content data to send for the request message..</param>
131
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage" /> is <see langword="null" /></exception>
132
    public static void SetContentData(this HttpRequestMessage requestMessage, object contentData)
133
    {
NEW
134
        if (requestMessage is null)
×
135
            throw new ArgumentNullException(nameof(requestMessage));
×
NEW
136
        if (contentData is null)
×
NEW
137
            throw new ArgumentNullException(nameof(contentData));
×
138

139
        requestMessage.SetOption(FluentProperties.RequestContentData, contentData);
×
140
    }
×
141

142

143
    /// <summary>
144
    /// Gets the form data property from the specified <paramref name="requestMessage" /> properties dictionary.
145
    /// </summary>
146
    /// <param name="requestMessage">The request message containing the property.</param>
147
    /// <returns>
148
    /// The dictionary of for data to send in the request message.
149
    /// </returns>
150
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
151
    public static Dictionary<string, ICollection<string>> GetFormData(this HttpRequestMessage requestMessage)
152
    {
153
        if (requestMessage is null)
120!
154
            throw new ArgumentNullException(nameof(requestMessage));
×
155

156
        return requestMessage.GetOrAddOption(FluentProperties.RequestFormData, _ => new Dictionary<string, ICollection<string>>());
168✔
157
    }
158

159

160
    /// <summary>
161
    /// Gets the completion option property from the specified <paramref name="requestMessage" /> properties dictionary.
162
    /// </summary>
163
    /// <param name="requestMessage">The request message containing the property.</param>
164
    /// <returns>
165
    /// The <see cref="HttpCompletionOption"/> to use when sending the request message.
166
    /// </returns>
167
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
168
    public static HttpCompletionOption GetCompletionOption(this HttpRequestMessage requestMessage)
169
    {
170
        if (requestMessage is null)
104!
171
            throw new ArgumentNullException(nameof(requestMessage));
×
172

173
        if (requestMessage.TryGetOption<HttpCompletionOption>(FluentProperties.HttpCompletionOption, out var value))
104!
174
            return value;
×
175

176
        return HttpCompletionOption.ResponseContentRead;
104✔
177
    }
178

179
    /// <summary>
180
    /// Sets the completion option property on the specified <paramref name="requestMessage" /> properties dictionary.
181
    /// </summary>
182
    /// <param name="requestMessage">The request message containing the property.</param>
183
    /// <param name="completionOption">The <see cref="HttpCompletionOption"/> to use when sending the request message.</param>
184
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
185
    public static void SetCompletionOption(this HttpRequestMessage requestMessage, HttpCompletionOption completionOption)
186
    {
NEW
187
        if (requestMessage is null)
×
188
            throw new ArgumentNullException(nameof(requestMessage));
×
189

190
        requestMessage.SetOption(FluentProperties.HttpCompletionOption, completionOption);
×
191
    }
×
192

193

194
    /// <summary>
195
    /// Gets the cancellation token property from the specified <paramref name="requestMessage" /> properties dictionary.
196
    /// </summary>
197
    /// <param name="requestMessage">The request message containing the property.</param>
198
    /// <returns>
199
    /// The <see cref="CancellationToken"/> to use when sending the request message.
200
    /// </returns>
201
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
202
    public static CancellationToken GetCancellationToken(this HttpRequestMessage requestMessage)
203
    {
204
        if (requestMessage is null)
104!
205
            throw new ArgumentNullException(nameof(requestMessage));
×
206

207
        if (requestMessage.TryGetOption<CancellationToken>(FluentProperties.CancellationToken, out var propertyValue))
104✔
208
            return propertyValue;
4✔
209

210
        return CancellationToken.None;
100✔
211
    }
212

213
    /// <summary>
214
    /// Sets the cancellation token property on the specified <paramref name="requestMessage" /> properties dictionary.
215
    /// </summary>
216
    /// <param name="requestMessage">The request message containing the property.</param>
217
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use when sending the request message.</param>
218
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
219
    public static void SetCancellationToken(this HttpRequestMessage requestMessage, CancellationToken cancellationToken)
220
    {
221
        if (requestMessage is null)
4!
222
            throw new ArgumentNullException(nameof(requestMessage));
×
223

224
        requestMessage.SetOption(FluentProperties.CancellationToken, cancellationToken);
4✔
225
    }
4✔
226

227

228
    /// <summary>
229
    /// Gets the content serializer property from the specified <paramref name="requestMessage" /> properties dictionary.
230
    /// </summary>
231
    /// <param name="requestMessage">The request message containing the property.</param>
232
    /// <returns>
233
    /// The <see cref="IContentSerializer"/> to use when serializing content to send in the request message.
234
    /// </returns>
235
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
236
    public static IContentSerializer GetContentSerializer(this HttpRequestMessage requestMessage)
237
    {
238
        if (requestMessage is null)
254!
239
            throw new ArgumentNullException(nameof(requestMessage));
×
240

241
        return requestMessage.GetOrAddOption(FluentProperties.ContentSerializer, _ => ContentSerializer.Current);
294✔
242
    }
243

244
    /// <summary>
245
    /// Sets the content serializer property on the specified <paramref name="requestMessage" /> properties dictionary.
246
    /// </summary>
247
    /// <param name="requestMessage">The request message containing the property.</param>
248
    /// <param name="contentSerializer">The <see cref="IContentSerializer"/> to use when serializing content to send in the request message.</param>
249
    /// <exception cref="ArgumentNullException"><paramref name="requestMessage"/> is <see langword="null"/></exception>
250
    public static void SetContentSerializer(this HttpRequestMessage requestMessage, IContentSerializer contentSerializer)
251
    {
252
        if (requestMessage is null)
66!
253
            throw new ArgumentNullException(nameof(requestMessage));
×
254

255
        requestMessage.SetOption(FluentProperties.ContentSerializer, contentSerializer ?? ContentSerializer.Current);
66!
256
    }
66✔
257

258

259
    /// <summary>
260
    /// Synchronizes the specified request message with the fluent properties.
261
    /// </summary>
262
    /// <param name="requestMessage">The request message.</param>
263
    public static void Synchronize(this HttpRequestMessage requestMessage)
264
    {
265
        if (requestMessage is null)
258!
NEW
266
            throw new ArgumentNullException(nameof(requestMessage));
×
267

268
        var urlBuilder = requestMessage.GetUrlBuilder();
258✔
269
        requestMessage.RequestUri = urlBuilder.ToUri();
258✔
270
    }
258✔
271

272

273
    /// <summary>
274
    /// Deserialize the HTTP response message asynchronously.
275
    /// </summary>
276
    /// <typeparam name="TData">The type of the data.</typeparam>
277
    /// <param name="responseMessage">The response message to deserialize.</param>
278
    /// <param name="ensureSuccess">Throw an exception if the HTTP response was unsuccessful.</param>
279
    /// <returns>
280
    /// The data object deserialized from the HTTP response message.
281
    /// </returns>
282
    /// <exception cref="HttpRequestException">Response status code does not indicate success.</exception>
283
    /// <exception cref="ArgumentNullException"><paramref name="responseMessage"/> is <see langword="null"/></exception>
284
    public static async Task<TData?> DeserializeAsync<TData>(this HttpResponseMessage responseMessage, bool ensureSuccess = true)
285
    {
286
        if (responseMessage is null)
102!
287
            throw new ArgumentNullException(nameof(responseMessage));
×
288

289
        if (ensureSuccess)
102✔
290
            await responseMessage.EnsureSuccessStatusCode(true);
102✔
291

292
        if (responseMessage.RequestMessage is null)
98!
NEW
293
            throw new ArgumentException("HttpResponseMessage request is null", nameof(responseMessage));
×
294

295
        var serializer = responseMessage.RequestMessage.GetContentSerializer();
98✔
296

297
        return await serializer
98✔
298
            .DeserializeAsync<TData>(responseMessage.Content)
98✔
299
            .ConfigureAwait(false);
98✔
300
    }
98✔
301

302
    /// <summary>
303
    /// Throws an exception if the IsSuccessStatusCode property for the HTTP response is false.
304
    /// </summary>
305
    /// <param name="responseMessage">The response message.</param>
306
    /// <param name="includeContent">if set to <c>true</c> the response content is included in the exception.</param>
307
    /// <exception cref="HttpRequestException">The HTTP response is unsuccessful.</exception>
308
    public static async Task EnsureSuccessStatusCode(this HttpResponseMessage responseMessage, bool includeContent)
309
    {
310
        if (responseMessage is null)
102!
NEW
311
            throw new ArgumentNullException(nameof(responseMessage));
×
312

313
        if (responseMessage.IsSuccessStatusCode)
102✔
314
            return;
98✔
315

316
        // will throw if response is a problem json
317
        await CheckResponseForProblem(responseMessage).ConfigureAwait(false);
4✔
318

319
        var message = $"Response status code does not indicate success: {responseMessage.StatusCode} ({responseMessage.ReasonPhrase});";
4✔
320

321
        if (!includeContent)
4!
322
            throw new HttpRequestException(message);
×
323

324
        var contentString = await responseMessage.Content.ReadAsStringAsync();
4✔
325
        if (string.IsNullOrEmpty(contentString))
4!
326
            throw new HttpRequestException(message);
4✔
327

328

329
        // add response content body to message for easier debugging
330
        message += Environment.NewLine + contentString;
×
331

332
        var exception = new HttpRequestException(message);
×
333
        exception.Data.Add("Response", contentString);
×
334

335
        throw exception;
×
336
    }
98✔
337

338
    private static async Task CheckResponseForProblem(HttpResponseMessage responseMessage)
339
    {
340
        var mediaType = responseMessage.Content.Headers.ContentType?.MediaType;
4!
341
        if (!string.Equals(mediaType, ProblemDetails.ContentType, StringComparison.OrdinalIgnoreCase))
4!
342
            return;
4✔
343

NEW
344
        if (responseMessage.RequestMessage is null)
×
NEW
345
            throw new ArgumentException("HttpResponseMessage request is null", nameof(responseMessage));
×
346

UNCOV
347
        var serializer = responseMessage.RequestMessage.GetContentSerializer();
×
348

349
        var problem = await serializer
×
350
            .DeserializeAsync<ProblemDetails>(responseMessage.Content)
×
351
            .ConfigureAwait(false);
×
352

NEW
353
        if (problem != null)
×
NEW
354
            throw new ProblemException(problem);
×
355
    }
4✔
356
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc