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

Jericho / StrongGrid / 1200

18 Apr 2024 01:42PM UTC coverage: 73.71% (-0.8%) from 74.544%
1200

push

appveyor

Jericho
Merge branch 'release/0.107.0'

2829 of 3838 relevant lines covered (73.71%)

82.64 hits per line

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

45.81
/Source/StrongGrid/Extensions/Internal.cs
1
using Pathoschild.Http.Client;
2
using StrongGrid.Json;
3
using StrongGrid.Models;
4
using StrongGrid.Utilities;
5
using System;
6
using System.Collections;
7
using System.Collections.Generic;
8
using System.ComponentModel;
9
using System.IO;
10
using System.IO.Compression;
11
using System.Linq;
12
using System.Net.Http;
13
using System.Net.Http.Headers;
14
using System.Reflection;
15
using System.Runtime.Serialization;
16
using System.Text;
17
using System.Text.Json;
18
using System.Text.Json.Serialization;
19
using System.Threading;
20
using System.Threading.Tasks;
21
using static StrongGrid.Utilities.DiagnosticHandler;
22

23
namespace StrongGrid
24
{
25
        /// <summary>
26
        /// Internal extension methods.
27
        /// </summary>
28
        internal static class Internal
29
        {
30
                internal enum UnixTimePrecision
31
                {
32
                        Seconds = 0,
33
                        Milliseconds = 1
34
                }
35

36
                private static readonly DateTime EPOCH = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
1✔
37
                private static readonly int DEFAULT_DEGREE_OF_PARALLELISM = Environment.ProcessorCount > 1 ? Environment.ProcessorCount / 2 : 1;
1✔
38

39
                /// <summary>
40
                /// Converts a 'unix time', which is expressed as the number of seconds (or milliseconds) since
41
                /// midnight on January 1st 1970, to a .Net <see cref="DateTime" />.
42
                /// </summary>
43
                /// <param name="unixTime">The unix time.</param>
44
                /// <param name="precision">The precision of the provided unix time.</param>
45
                /// <returns>
46
                /// The <see cref="DateTime" />.
47
                /// </returns>
48
                internal static DateTime FromUnixTime(this long unixTime, UnixTimePrecision precision = UnixTimePrecision.Seconds)
49
                {
50
                        if (precision == UnixTimePrecision.Seconds) return EPOCH.AddSeconds(unixTime);
322✔
51
                        if (precision == UnixTimePrecision.Milliseconds) return EPOCH.AddMilliseconds(unixTime);
×
52
                        throw new Exception($"Unknown precision: {precision}");
×
53
                }
54

55
                /// <summary>
56
                /// Converts a .Net <see cref="DateTime" /> into a 'Unix time', which is expressed as the number
57
                /// of seconds (or milliseconds) since midnight on January 1st 1970.
58
                /// </summary>
59
                /// <param name="date">The date.</param>
60
                /// <param name="precision">The desired precision.</param>
61
                /// <returns>
62
                /// The numer of seconds/milliseconds since midnight on January 1st 1970.
63
                /// </returns>
64
                internal static long ToUnixTime(this DateTime date, UnixTimePrecision precision = UnixTimePrecision.Seconds)
65
                {
66
                        var diff = date.ToUniversalTime() - EPOCH;
30✔
67
                        if (precision == UnixTimePrecision.Seconds) return Convert.ToInt64(diff.TotalSeconds);
60✔
68
                        if (precision == UnixTimePrecision.Milliseconds) return Convert.ToInt64(diff.TotalMilliseconds);
×
69
                        throw new Exception($"Unknown precision: {precision}");
×
70
                }
71

72
                /// <summary>
73
                /// Reads the content of the HTTP response as string asynchronously.
74
                /// </summary>
75
                /// <param name="httpContent">The content.</param>
76
                /// <param name="encoding">The encoding. You can leave this parameter null and the encoding will be
77
                /// automatically calculated based on the charset in the response. Also, UTF-8
78
                /// encoding will be used if the charset is absent from the response, is blank
79
                /// or contains an invalid value.</param>
80
                /// <param name="cancellationToken">The cancellation token.</param>
81
                /// <returns>The string content of the response.</returns>
82
                /// <remarks>
83
                /// This method is an improvement over the built-in ReadAsStringAsync method
84
                /// because it can handle invalid charset returned in the response. For example
85
                /// you may be sending a request to an API that returns a blank charset or a
86
                /// misspelled one like 'utf8' instead of the correctly spelled 'utf-8'. The
87
                /// built-in method throws an exception if an invalid charset is specified
88
                /// while this method uses the UTF-8 encoding in that situation.
89
                ///
90
                /// My motivation for writing this extension method was to work around a situation
91
                /// where the 3rd party API I was sending requests to would sometimes return 'utf8'
92
                /// as the charset and an exception would be thrown when I called the ReadAsStringAsync
93
                /// method to get the content of the response into a string because the .Net HttpClient
94
                /// would attempt to determine the proper encoding to use but it would fail due to
95
                /// the fact that the charset was misspelled. I contacted the vendor, asking them
96
                /// to either omit the charset or fix the misspelling but they didn't feel the need
97
                /// to fix this issue because:
98
                /// "in some programming languages, you can use the syntax utf8 instead of utf-8".
99
                /// In other words, they are happy to continue using the misspelled value which is
100
                /// supported by "some" programming languages instead of using the properly spelled
101
                /// value which is supported by all programming languages.
102
                /// </remarks>
103
                /// <example>
104
                /// <code>
105
                /// var httpRequest = new HttpRequestMessage
106
                /// {
107
                ///     Method = HttpMethod.Get,
108
                ///     RequestUri = new Uri("https://api.vendor.com/v1/endpoint")
109
                /// };
110
                /// var httpClient = new HttpClient();
111
                /// var response = await httpClient.SendAsync(httpRequest, CancellationToken.None).ConfigureAwait(false);
112
                /// var responseContent = await response.Content.ReadAsStringAsync(null).ConfigureAwait(false);
113
                /// </code>
114
                /// </example>
115
                internal static async Task<string> ReadAsStringAsync(this HttpContent httpContent, Encoding encoding, CancellationToken cancellationToken = default)
116
                {
117
                        var content = string.Empty;
118

119
                        if (httpContent != null)
120
                        {
121
#if NET5_0_OR_GREATER
122
                                var contentStream = await httpContent.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
123
#else
124
                                var contentStream = await httpContent.ReadAsStreamAsync().ConfigureAwait(false);
125
#endif
126
                                encoding ??= httpContent.GetEncoding(Encoding.UTF8);
127

128
                                // This is important: we must make a copy of the response stream otherwise we would get an
129
                                // exception on subsequent attempts to read the content of the stream
130
                                using (var ms = Utils.MemoryStreamManager.GetStream())
131
                                {
132
                                        const int DefaultBufferSize = 81920;
133
                                        await contentStream.CopyToAsync(ms, DefaultBufferSize, cancellationToken).ConfigureAwait(false);
134
                                        ms.Position = 0;
135
                                        using (var sr = new StreamReader(ms, encoding))
136
                                        {
137
#if NET7_0_OR_GREATER
138
                                                content = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
139
#else
140
                                                content = await sr.ReadToEndAsync().ConfigureAwait(false);
141
#endif
142
                                        }
143

144
                                        // Rewind the stream (if permitted)
145
                                        if (contentStream.CanSeek) contentStream.Position = 0;
146
                                }
147
                        }
148

149
                        return content;
150
                }
151

152
                /// <summary>
153
                /// Gets the encoding.
154
                /// </summary>
155
                /// <param name="content">The content.</param>
156
                /// <param name="defaultEncoding">The default encoding.</param>
157
                /// <returns>
158
                /// The encoding.
159
                /// </returns>
160
                /// <remarks>
161
                /// This method tries to get the encoding based on the charset or uses the
162
                /// 'defaultEncoding' if the charset is empty or contains an invalid value.
163
                /// </remarks>
164
                /// <example>
165
                ///   <code>
166
                /// var httpRequest = new HttpRequestMessage
167
                /// {
168
                /// Method = HttpMethod.Get,
169
                /// RequestUri = new Uri("https://my.api.com/v1/myendpoint")
170
                /// };
171
                /// var httpClient = new HttpClient();
172
                /// var response = await httpClient.SendAsync(httpRequest, CancellationToken.None).ConfigureAwait(false);
173
                /// var encoding = response.Content.GetEncoding(Encoding.UTF8);
174
                /// </code>
175
                /// </example>
176
                internal static Encoding GetEncoding(this HttpContent content, Encoding defaultEncoding)
177
                {
178
                        var encoding = defaultEncoding;
484✔
179
                        try
180
                        {
181
                                var charset = content?.Headers?.ContentType?.CharSet;
484✔
182
                                if (!string.IsNullOrEmpty(charset))
484✔
183
                                {
184
                                        encoding = Encoding.GetEncoding(charset);
419✔
185
                                }
186
                        }
484✔
187
                        catch
×
188
                        {
189
                                encoding = defaultEncoding;
×
190
                        }
×
191

192
                        return encoding;
484✔
193
                }
194

195
                /// <summary>Asynchronously retrieve the JSON encoded response body and convert it to an object of the desired type.</summary>
196
                /// <typeparam name="T">The response model to deserialize into.</typeparam>
197
                /// <param name="response">The response.</param>
198
                /// <param name="propertyName">The name of the JSON property (or null if not applicable) where the desired data is stored.</param>
199
                /// <param name="throwIfPropertyIsMissing">Indicates if an exception should be thrown when the specified JSON property is missing from the response.</param>
200
                /// <param name="options">Options to control behavior Converter during parsing.</param>
201
                /// <returns>Returns the strongly typed object.</returns>
202
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
203
                internal static Task<T> AsObject<T>(this IResponse response, string propertyName = null, bool throwIfPropertyIsMissing = true, JsonSerializerOptions options = null)
204
                {
205
                        return response.Message.Content.AsObject<T>(propertyName, throwIfPropertyIsMissing, options);
192✔
206
                }
207

208
                /// <summary>Asynchronously retrieve the JSON encoded response body and convert it to an object of the desired type.</summary>
209
                /// <typeparam name="T">The response model to deserialize into.</typeparam>
210
                /// <param name="request">The request.</param>
211
                /// <param name="propertyName">The name of the JSON property (or null if not applicable) where the desired data is stored.</param>
212
                /// <param name="throwIfPropertyIsMissing">Indicates if an exception should be thrown when the specified JSON property is missing from the response.</param>
213
                /// <param name="options">Options to control behavior Converter during parsing.</param>
214
                /// <returns>Returns the strongly typed object.</returns>
215
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
216
                internal static async Task<T> AsObject<T>(this IRequest request, string propertyName = null, bool throwIfPropertyIsMissing = true, JsonSerializerOptions options = null)
217
                {
218
                        var response = await request.AsResponse().ConfigureAwait(false);
219
                        return await response.AsObject<T>(propertyName, throwIfPropertyIsMissing, options).ConfigureAwait(false);
220
                }
221

222
                /// <summary>Asynchronously retrieve the JSON encoded content and convert it to a 'PaginatedResponse' object.</summary>
223
                /// <typeparam name="T">The response model to deserialize into.</typeparam>
224
                /// <param name="response">The response.</param>
225
                /// <param name="propertyName">The name of the JSON property (or null if not applicable) where the desired data is stored.</param>
226
                /// <param name="options">Options to control behavior Converter during parsing.</param>
227
                /// <returns>Returns the paginated response.</returns>
228
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
229
                internal static Task<PaginatedResponse<T>> AsPaginatedResponse<T>(this IResponse response, string propertyName = null, JsonSerializerOptions options = null)
230
                {
231
                        return response.Message.Content.AsPaginatedResponse<T>(propertyName, options);
2✔
232
                }
233

234
                /// <summary>Asynchronously retrieve the JSON encoded content and convert it to a 'PaginatedResponse' object.</summary>
235
                /// <typeparam name="T">The response model to deserialize into.</typeparam>
236
                /// <param name="request">The request.</param>
237
                /// <param name="propertyName">The name of the JSON property (or null if not applicable) where the desired data is stored.</param>
238
                /// <param name="options">Options to control behavior Converter during parsing.</param>
239
                /// <returns>Returns the paginated response.</returns>
240
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
241
                internal static async Task<PaginatedResponse<T>> AsPaginatedResponse<T>(this IRequest request, string propertyName = null, JsonSerializerOptions options = null)
242
                {
243
                        var response = await request.AsResponse().ConfigureAwait(false);
244
                        return await response.AsPaginatedResponse<T>(propertyName, options).ConfigureAwait(false);
245
                }
246

247
                /// <summary>Get a raw JSON document representation of the response.</summary>
248
                /// <exception cref="ApiException">An error occurred processing the response.</exception>
249
                internal static Task<JsonDocument> AsRawJsonDocument(this IResponse response, string propertyName = null, bool throwIfPropertyIsMissing = true)
250
                {
251
                        return response.Message.Content.AsRawJsonDocument(propertyName, throwIfPropertyIsMissing);
5✔
252
                }
253

254
                /// <summary>Get a raw JSON document representation of the response.</summary>
255
                /// <exception cref="ApiException">An error occurred processing the response.</exception>
256
                internal static async Task<JsonDocument> AsRawJsonDocument(this IRequest request, string propertyName = null, bool throwIfPropertyIsMissing = true)
257
                {
258
                        var response = await request.AsResponse().ConfigureAwait(false);
259
                        return await response.AsRawJsonDocument(propertyName, throwIfPropertyIsMissing).ConfigureAwait(false);
260
                }
261

262
                /// <summary>Set the body content of the HTTP request.</summary>
263
                /// <typeparam name="T">The type of object to serialize into a JSON string.</typeparam>
264
                /// <param name="request">The request.</param>
265
                /// <param name="body">The value to serialize into the HTTP body content.</param>
266
                /// <param name="omitCharSet">
267
                /// Indicates if the charset should be omitted from the 'Content-Type' request header.
268
                /// The vast majority of SendGrid's endpoints require this parameter to be false but one
269
                /// notable exception is 'Contacts.Upsert' in the new marketing campaigns API.
270
                /// SendGrid has not documented when it should be true/false, I only figured it out when
271
                /// getting a "invalid content-type: application/json; charset=utf-8" exception which was
272
                /// solved by omitting the "charset".
273
                /// </param>
274
                /// <returns>Returns the request builder for chaining.</returns>
275
                /// <remarks>
276
                /// This method is equivalent to IRequest.AsBody&lt;T&gt;(T body) because omitting the media type
277
                /// causes the first formatter in MediaTypeFormatterCollection to be used by default and the first
278
                /// formatter happens to be the JSON formatter. However, I don't feel good about relying on the
279
                /// default ordering of the items in the MediaTypeFormatterCollection.
280
                /// </remarks>
281
                internal static IRequest WithJsonBody<T>(this IRequest request, T body, bool omitCharSet = false)
282
                {
283
                        return request.WithBody(bodyBuilder =>
109✔
284
                        {
109✔
285
                                var httpContent = bodyBuilder.Model(body, new MediaTypeHeaderValue("application/json"));
109✔
286

109✔
287
                                if (omitCharSet && !string.IsNullOrEmpty(httpContent.Headers.ContentType.CharSet))
109✔
288
                                {
109✔
289
                                        httpContent.Headers.ContentType.CharSet = string.Empty;
109✔
290
                                }
109✔
291

109✔
292
                                return httpContent;
109✔
293
                        });
109✔
294
                }
295

296
                /// <summary>
297
                /// Impersonate a user when making a call to the SendGrid API.
298
                /// </summary>
299
                /// <param name="request">The request.</param>
300
                /// <param name="username">The user to impersonate.</param>
301
                /// <returns>Returns the request builder for chaining.</returns>
302
                internal static IRequest OnBehalfOf(this IRequest request, string username)
303
                {
304
                        return string.IsNullOrEmpty(username) ? request : request.WithHeader("on-behalf-of", username);
191✔
305
                }
306

307
                /// <summary>Asynchronously retrieve the response body as a <see cref="string"/>.</summary>
308
                /// <param name="response">The response.</param>
309
                /// <param name="encoding">The encoding. You can leave this parameter null and the encoding will be
310
                /// automatically calculated based on the charset in the response. Also, UTF-8
311
                /// encoding will be used if the charset is absent from the response, is blank
312
                /// or contains an invalid value.</param>
313
                /// <returns>Returns the response body, or <c>null</c> if the response has no body.</returns>
314
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
315
                internal static Task<string> AsString(this IResponse response, Encoding encoding)
316
                {
317
                        return response.Message.Content.ReadAsStringAsync(encoding);
×
318
                }
319

320
                /// <summary>Asynchronously retrieve the response body as a <see cref="string"/>.</summary>
321
                /// <param name="request">The request.</param>
322
                /// <param name="encoding">The encoding. You can leave this parameter null and the encoding will be
323
                /// automatically calculated based on the charset in the response. Also, UTF-8
324
                /// encoding will be used if the charset is absent from the response, is blank
325
                /// or contains an invalid value.</param>
326
                /// <returns>Returns the response body, or <c>null</c> if the response has no body.</returns>
327
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
328
                internal static async Task<string> AsString(this IRequest request, Encoding encoding)
329
                {
330
                        IResponse response = await request.AsResponse().ConfigureAwait(false);
331
                        return await response.AsString(encoding).ConfigureAwait(false);
332
                }
333

334
                /// <summary>
335
                ///  Converts the value of the current System.TimeSpan object to its equivalent string
336
                ///  representation by using a human readable format.
337
                /// </summary>
338
                /// <param name="timeSpan">The time span.</param>
339
                /// <returns>Returns the human readable representation of the TimeSpan.</returns>
340
                internal static string ToDurationString(this TimeSpan timeSpan)
341
                {
342
                        static void AppendFormatIfNecessary(StringBuilder stringBuilder, string timePart, int value)
343
                        {
344
                                if (value <= 0) return;
345
                                stringBuilder.AppendFormat($" {value} {timePart}{(value > 1 ? "s" : string.Empty)}");
346
                        }
347

348
                        // In case the TimeSpan is extremely short
349
                        if (timeSpan.TotalMilliseconds <= 1) return "1 millisecond";
×
350

351
                        var result = new StringBuilder();
×
352
                        AppendFormatIfNecessary(result, "day", timeSpan.Days);
×
353
                        AppendFormatIfNecessary(result, "hour", timeSpan.Hours);
×
354
                        AppendFormatIfNecessary(result, "minute", timeSpan.Minutes);
×
355
                        AppendFormatIfNecessary(result, "second", timeSpan.Seconds);
×
356
                        AppendFormatIfNecessary(result, "millisecond", timeSpan.Milliseconds);
×
357
                        return result.ToString().Trim();
×
358
                }
359

360
                /// <summary>
361
                /// Ensure that a string starts with a given prefix.
362
                /// </summary>
363
                /// <param name="value">The value.</param>
364
                /// <param name="prefix">The prefix.</param>
365
                /// <returns>The value including the prefix.</returns>
366
                internal static string EnsureStartsWith(this string value, string prefix)
367
                {
368
                        return !string.IsNullOrEmpty(value) && value.StartsWith(prefix) ? value : string.Concat(prefix, value);
×
369
                }
370

371
                /// <summary>
372
                /// Ensure that a string ends with a given suffix.
373
                /// </summary>
374
                /// <param name="value">The value.</param>
375
                /// <param name="suffix">The sufix.</param>
376
                /// <returns>The value including the suffix.</returns>
377
                internal static string EnsureEndsWith(this string value, string suffix)
378
                {
379
                        return !string.IsNullOrEmpty(value) && value.EndsWith(suffix) ? value : string.Concat(value, suffix);
×
380
                }
381

382
                internal static JsonElement? GetProperty(this JsonElement element, string name, bool throwIfMissing = true)
383
                {
384
                        var parts = name.Split('/');
×
385
                        if (!element.TryGetProperty(parts[0], out var property))
×
386
                        {
387
                                if (throwIfMissing) throw new ArgumentException($"Unable to find '{name}'", nameof(name));
×
388
                                else return null;
×
389
                        }
390

391
                        foreach (var part in parts.Skip(1))
×
392
                        {
393
                                if (!property.TryGetProperty(part, out property))
×
394
                                {
395
                                        if (throwIfMissing) throw new ArgumentException($"Unable to find '{name}'", nameof(name));
×
396
                                        else return null;
×
397
                                }
398
                        }
399

400
                        return property;
×
401
                }
×
402

403
                internal static T GetPropertyValue<T>(this JsonElement element, string name, T defaultValue)
404
                {
405
                        return GetPropertyValue<T>(element, new[] { name }, defaultValue, false);
×
406
                }
407

408
                internal static T GetPropertyValue<T>(this JsonElement element, string[] names, T defaultValue)
409
                {
410
                        return GetPropertyValue<T>(element, names, defaultValue, false);
×
411
                }
412

413
                internal static T GetPropertyValue<T>(this JsonElement element, string name)
414
                {
415
                        return GetPropertyValue<T>(element, new[] { name }, default, true);
×
416
                }
417

418
                internal static T GetPropertyValue<T>(this JsonElement element, string[] names)
419
                {
420
                        return GetPropertyValue<T>(element, names, default, true);
×
421
                }
422

423
                /// <summary>
424
                /// Retrieve the permissions (AKA "scopes") assigned to the current user.
425
                /// </summary>
426
                /// <param name="client">The client.</param>
427
                /// <param name="excludeBillingScopes">Indicates if billing permissions should be excluded from the result.</param>
428
                /// <param name="cancellationToken">The cancellation token.</param>
429
                /// <returns>An array of permisisons assigned to the current user.</returns>
430
                internal static async Task<string[]> GetCurrentScopes(this Pathoschild.Http.Client.IClient client, bool excludeBillingScopes, CancellationToken cancellationToken = default)
431
                {
432
                        // Get the current user's permissions
433
                        var scopes = await client
434
                                .GetAsync("scopes")
435
                                .WithCancellationToken(cancellationToken)
436
                                .AsObject<string[]>("scopes")
437
                                .ConfigureAwait(false);
438

439
                        if (excludeBillingScopes)
440
                        {
441
                                // In most cases it's important to exclude billing scopes
442
                                // because they are mutually exclusive from all others.
443
                                scopes = scopes
444
                                        .Where(p => !p.StartsWith("billing.", StringComparison.OrdinalIgnoreCase))
445
                                        .ToArray();
446
                        }
447

448
                        return scopes;
449
                }
450

451
                internal static Task<TResult[]> ForEachAsync<T, TResult>(this IEnumerable<T> items, Func<T, Task<TResult>> action) => ForEachAsync(items, action, DEFAULT_DEGREE_OF_PARALLELISM);
×
452

453
                internal static Task<TResult[]> ForEachAsync<T, TResult>(this IEnumerable<T> items, Func<T, int, Task<TResult>> action) => ForEachAsync(items, action, DEFAULT_DEGREE_OF_PARALLELISM);
2✔
454

455
                internal static async Task<TResult[]> ForEachAsync<T, TResult>(this IEnumerable<T> items, Func<T, Task<TResult>> action, int maxDegreeOfParalellism)
456
                {
457
                        var allTasks = new List<Task<TResult>>();
458
                        using (var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism))
459
                        {
460
                                foreach (var item in items)
461
                                {
462
                                        await throttler.WaitAsync();
463
                                        allTasks.Add(
464
                                                Task.Run(async () =>
465
                                                {
466
                                                        try
467
                                                        {
468
                                                                return await action(item).ConfigureAwait(false);
469
                                                        }
470
                                                        finally
471
                                                        {
472
                                                                throttler.Release();
473
                                                        }
474
                                                }));
475
                                }
476

477
                                var results = await Task.WhenAll(allTasks).ConfigureAwait(false);
478
                                return results;
479
                        }
480
                }
481

482
                internal static async Task<TResult[]> ForEachAsync<T, TResult>(this IEnumerable<T> items, Func<T, int, Task<TResult>> action, int maxDegreeOfParalellism)
483
                {
484
                        var allTasks = new List<Task<TResult>>();
485
                        using (var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism))
486
                        {
487
                                foreach (var (item, index) in items.Select((value, i) => (value, i)))
488
                                {
489
                                        await throttler.WaitAsync();
490
                                        allTasks.Add(
491
                                                Task.Run(async () =>
492
                                                {
493
                                                        try
494
                                                        {
495
                                                                return await action(item, index).ConfigureAwait(false);
496
                                                        }
497
                                                        finally
498
                                                        {
499
                                                                throttler.Release();
500
                                                        }
501
                                                }));
502
                                }
503

504
                                var results = await Task.WhenAll(allTasks).ConfigureAwait(false);
505
                                return results;
506
                        }
507
                }
508

509
                internal static Task ForEachAsync<T>(this IEnumerable<T> items, Func<T, Task> action) => ForEachAsync(items, action, DEFAULT_DEGREE_OF_PARALLELISM);
×
510

511
                internal static Task ForEachAsync<T>(this IEnumerable<T> items, Func<T, int, Task> action) => ForEachAsync(items, action, DEFAULT_DEGREE_OF_PARALLELISM);
×
512

513
                internal static async Task ForEachAsync<T>(this IEnumerable<T> items, Func<T, Task> action, int maxDegreeOfParalellism)
514
                {
515
                        var allTasks = new List<Task>();
516
                        using (var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism))
517
                        {
518
                                foreach (var item in items)
519
                                {
520
                                        await throttler.WaitAsync();
521
                                        allTasks.Add(
522
                                                Task.Run(async () =>
523
                                                {
524
                                                        try
525
                                                        {
526
                                                                await action(item).ConfigureAwait(false);
527
                                                        }
528
                                                        finally
529
                                                        {
530
                                                                throttler.Release();
531
                                                        }
532
                                                }));
533
                                }
534

535
                                await Task.WhenAll(allTasks).ConfigureAwait(false);
536
                        }
537
                }
538

539
                internal static async Task ForEachAsync<T>(this IEnumerable<T> items, Func<T, int, Task> action, int maxDegreeOfParalellism)
540
                {
541
                        var allTasks = new List<Task>();
542
                        using (var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism))
543
                        {
544
                                foreach (var (item, index) in items.Select((value, i) => (value, i)))
545
                                {
546
                                        await throttler.WaitAsync();
547
                                        allTasks.Add(
548
                                                Task.Run(async () =>
549
                                                {
550
                                                        try
551
                                                        {
552
                                                                await action(item, index).ConfigureAwait(false);
553
                                                        }
554
                                                        finally
555
                                                        {
556
                                                                throttler.Release();
557
                                                        }
558
                                                }));
559
                                }
560

561
                                await Task.WhenAll(allTasks).ConfigureAwait(false);
562
                        }
563
                }
564

565
                /// <summary>
566
                /// Gets the attribute of the specified type.
567
                /// </summary>
568
                /// <typeparam name="T">The type of the desired attribute.</typeparam>
569
                /// <param name="enumVal">The enum value.</param>
570
                /// <returns>The attribute.</returns>
571
                internal static T GetAttributeOfType<T>(this Enum enumVal)
572
                        where T : Attribute
573
                {
574
                        return enumVal.GetType()
130✔
575
                                .GetTypeInfo()
130✔
576
                                .DeclaredMembers
130✔
577
                                .SingleOrDefault(x => x.Name == enumVal.ToString())
130✔
578
                                ?.GetCustomAttribute<T>(false);
130✔
579
                }
580

581
                /// <summary>
582
                /// Indicates if an object contain a numerical value.
583
                /// </summary>
584
                /// <param name="value">The object.</param>
585
                /// <returns>A boolean indicating if the object contains a numerical value.</returns>
586
                internal static bool IsNumber(this object value)
587
                {
588
                        return value is sbyte
2✔
589
                                   || value is byte
2✔
590
                                   || value is short
2✔
591
                                   || value is ushort
2✔
592
                                   || value is int
2✔
593
                                   || value is uint
2✔
594
                                   || value is long
2✔
595
                                   || value is ulong
2✔
596
                                   || value is float
2✔
597
                                   || value is double
2✔
598
                                   || value is decimal;
2✔
599
                }
600

601
                /// <summary>
602
                /// Returns the first value for a specified header stored in the System.Net.Http.Headers.HttpHeaderscollection.
603
                /// </summary>
604
                /// <param name="headers">The HTTP headers.</param>
605
                /// <param name="name">The specified header to return value for.</param>
606
                /// <returns>A string.</returns>
607
                internal static string GetValue(this HttpHeaders headers, string name)
608
                {
609
                        if (headers == null) return null;
289✔
610

611
                        if (headers.TryGetValues(name, out IEnumerable<string> values))
289✔
612
                        {
613
                                return values.FirstOrDefault();
286✔
614
                        }
615

616
                        return null;
3✔
617
                }
618

619
                internal static IEnumerable<KeyValuePair<string, string>> ParseQuerystring(this Uri uri)
620
                {
621
                        var querystringParameters = uri
13✔
622
                                .Query.TrimStart('?')
13✔
623
                                .Split(new char[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
13✔
624
                                .Select(value => value.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries))
13✔
625
                                .Select(splitValue =>
13✔
626
                                {
13✔
627
                                        if (splitValue.Length == 1)
13✔
628
                                        {
13✔
629
                                                return new KeyValuePair<string, string>(splitValue[0].Trim(), null);
13✔
630
                                        }
13✔
631
                                        else
13✔
632
                                        {
13✔
633
                                                return new KeyValuePair<string, string>(splitValue[0].Trim(), splitValue[1].Trim());
13✔
634
                                        }
13✔
635
                                });
13✔
636

637
                        return querystringParameters;
13✔
638
                }
639

640
                internal static DiagnosticInfo GetDiagnosticInfo(this IResponse response)
641
                {
642
                        var diagnosticId = response.Message.RequestMessage.Headers.GetValue(DiagnosticHandler.DIAGNOSTIC_ID_HEADER_NAME);
4✔
643
                        DiagnosticHandler.DiagnosticsInfo.TryGetValue(diagnosticId, out DiagnosticInfo diagnosticInfo);
4✔
644
                        return diagnosticInfo;
4✔
645
                }
646

647
                internal static async Task<(bool IsError, string Message)> GetErrorMessageAsync(this HttpResponseMessage message)
648
                {
649
                        // Default error message
650
                        var errorMessage = $"{(int)message.StatusCode}: {message.ReasonPhrase}";
651

652
                        /*
653
                                In case of an error, the SendGrid API returns a JSON string that looks like this:
654
                                {
655
                                        "errors": [
656
                                {
657
                                                        "message": "An error has occurred",
658
                                                        "field": null,
659
                                                        "help": null
660
                                                }
661
                                        ]
662
                                }
663

664
                                The documentation says that it should look like this:
665
                                {
666
                                        "errors": [
667
                                                {
668
                                                        "message": <string>,
669
                                                        "field": <string>,
670
                                                        "error_id": <string>
671
                                                }
672
                                        ]
673
                                }
674

675
                                The documentation for "Add or Update a Contact" under the "New Marketing Campaigns" section says that it looks like this:
676
                                {
677
                                        "errors": [
678
                                                {
679
                                                        "message": <string>,
680
                                                        "field": <string>,
681
                                                        "error_id": <string>,
682
                                                        "parameter": <string>
683
                                                }
684
                                        ]
685
                                }
686

687
                                I have also seen cases where the JSON string looks like this:
688
                                {
689
                                        "error": "Name already exists"
690
                                }
691
                        */
692

693
                        var responseContent = await message.Content.ReadAsStringAsync(null).ConfigureAwait(false);
694

695
                        if (!string.IsNullOrEmpty(responseContent))
696
                        {
697
                                try
698
                                {
699
                                        var rootJsonElement = JsonDocument.Parse(responseContent).RootElement;
700

701
                                        if (rootJsonElement.ValueKind == JsonValueKind.Object)
702
                                        {
703
                                                var foundErrors = rootJsonElement.TryGetProperty("errors", out JsonElement jsonErrors);
704
                                                var foundError = rootJsonElement.TryGetProperty("error", out JsonElement jsonError);
705

706
                                                // Check for the presence of property called 'errors'
707
                                                if (foundErrors && jsonErrors.ValueKind == JsonValueKind.Array)
708
                                                {
709
                                                        var errors = jsonErrors.EnumerateArray()
710
                                                                .Select(jsonElement => jsonElement.GetProperty("message").GetString())
711
                                                                .ToArray();
712

713
                                                        errorMessage = string.Join(Environment.NewLine, errors);
714
                                                        return (true, errorMessage);
715
                                                }
716

717
                                                // Check for the presence of property called 'error'
718
                                                else if (foundError)
719
                                                {
720
                                                        errorMessage = jsonError.GetString();
721
                                                        return (true, errorMessage);
722
                                                }
723
                                        }
724
                                }
725
                                catch
726
                                {
727
                                        // Intentionally ignore parsing errors
728
                                }
729
                        }
730

731
                        return (!message.IsSuccessStatusCode, errorMessage);
732
                }
733

734
                internal static async Task<Stream> CompressAsync(this Stream source)
735
                {
736
                        var compressedStream = new MemoryStream();
737
                        using (var gzip = new GZipStream(compressedStream, CompressionMode.Compress, true))
738
                        {
739
                                await source.CopyToAsync(gzip).ConfigureAwait(false);
740
                        }
741

742
                        compressedStream.Position = 0;
743
                        return compressedStream;
744
                }
745

746
                internal static async Task<Stream> DecompressAsync(this Stream source)
747
                {
748
                        var decompressedStream = new MemoryStream();
749
                        using (var gzip = new GZipStream(source, CompressionMode.Decompress, true))
750
                        {
751
                                await gzip.CopyToAsync(decompressedStream).ConfigureAwait(false);
752
                        }
753

754
                        decompressedStream.Position = 0;
755
                        return decompressedStream;
756
                }
757

758
                /// <summary>Convert an enum to its string representation.</summary>
759
                /// <typeparam name="T">The enum type.</typeparam>
760
                /// <param name="enumValue">The value.</param>
761
                /// <returns>The string representation of the enum value.</returns>
762
                /// <remarks>Inspired by: https://stackoverflow.com/questions/10418651/using-enummemberattribute-and-doing-automatic-string-conversions .</remarks>
763
                internal static string ToEnumString<T>(this T enumValue)
764
                        where T : Enum
765
                {
766
                        if (TryToEnumString(enumValue, out string stringValue)) return stringValue;
260✔
767
                        return enumValue.ToString();
×
768
                }
769

770
                internal static bool TryToEnumString<T>(this T enumValue, out string stringValue)
771
                        where T : Enum
772
                {
773
                        var enumMemberAttribute = enumValue.GetAttributeOfType<EnumMemberAttribute>();
130✔
774
                        if (enumMemberAttribute != null)
130✔
775
                        {
776
                                stringValue = enumMemberAttribute.Value;
130✔
777
                                return true;
130✔
778
                        }
779

780
                        var jsonPropertyNameAttribute = enumValue.GetAttributeOfType<JsonPropertyNameAttribute>();
×
781
                        if (jsonPropertyNameAttribute != null)
×
782
                        {
783
                                stringValue = jsonPropertyNameAttribute.Name;
×
784
                                return true;
×
785
                        }
786

787
                        var descriptionAttribute = enumValue.GetAttributeOfType<DescriptionAttribute>();
×
788
                        if (descriptionAttribute != null)
×
789
                        {
790
                                stringValue = descriptionAttribute.Description;
×
791
                                return true;
×
792
                        }
793

794
                        stringValue = null;
×
795
                        return false;
×
796
                }
797

798
                /// <summary>Parses a string into its corresponding enum value.</summary>
799
                /// <typeparam name="T">The enum type.</typeparam>
800
                /// <param name="str">The string value.</param>
801
                /// <returns>The enum representation of the string value.</returns>
802
                /// <remarks>Inspired by: https://stackoverflow.com/questions/10418651/using-enummemberattribute-and-doing-automatic-string-conversions .</remarks>
803
                internal static T ToEnum<T>(this string str)
804
                        where T : Enum
805
                {
806
                        if (TryToEnum(str, out T enumValue)) return enumValue;
345✔
807

808
                        throw new ArgumentException($"There is no value in the {typeof(T).Name} enum that corresponds to '{str}'.");
1✔
809
                }
810

811
                internal static bool TryToEnum<T>(this string str, out T enumValue)
812
                        where T : Enum
813
                {
814
                        var enumType = typeof(T);
173✔
815
                        foreach (var name in Enum.GetNames(enumType))
1,466✔
816
                        {
817
                                var customAttributes = enumType.GetField(name).GetCustomAttributes(true);
646✔
818

819
                                // See if there's a matching 'EnumMember' attribute
820
                                if (customAttributes.OfType<EnumMemberAttribute>().Any(attribute => string.Equals(attribute.Value, str, StringComparison.OrdinalIgnoreCase)))
646✔
821
                                {
822
                                        enumValue = (T)Enum.Parse(enumType, name);
170✔
823
                                        return true;
170✔
824
                                }
825

826
                                // See if there's a matching 'JsonPropertyName' attribute
827
                                if (customAttributes.OfType<JsonPropertyNameAttribute>().Any(attribute => string.Equals(attribute.Name, str, StringComparison.OrdinalIgnoreCase)))
476✔
828
                                {
829
                                        enumValue = (T)Enum.Parse(enumType, name);
×
830
                                        return true;
×
831
                                }
832

833
                                // See if there's a matching 'Description' attribute
834
                                if (customAttributes.OfType<DescriptionAttribute>().Any(attribute => string.Equals(attribute.Description, str, StringComparison.OrdinalIgnoreCase)))
476✔
835
                                {
836
                                        enumValue = (T)Enum.Parse(enumType, name);
×
837
                                        return true;
×
838
                                }
839

840
                                // See if the value matches the name
841
                                if (string.Equals(name, str, StringComparison.OrdinalIgnoreCase))
476✔
842
                                {
843
                                        enumValue = (T)Enum.Parse(enumType, name);
2✔
844
                                        return true;
2✔
845
                                }
846
                        }
847

848
                        enumValue = default;
1✔
849
                        return false;
1✔
850
                }
851

852
                internal static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
853
                {
854
                        return JsonSerializer.Deserialize<T>(element.GetRawText(), options ?? JsonFormatter.DeserializerOptions);
57✔
855
                }
856

857
                internal static string ToHexString(this byte[] bytes)
858
                {
859
                        var result = new StringBuilder(bytes.Length * 2);
×
860
                        for (int i = 0; i < bytes.Length; i++)
×
861
                                result.Append(bytes[i].ToString("x2"));
×
862
                        return result.ToString();
×
863
                }
864

865
                internal static string ToExactLength(this string source, int totalWidth, string postfix = "...", char paddingChar = ' ')
866
                {
867
                        if (string.IsNullOrEmpty(source)) return new string(paddingChar, totalWidth);
×
868
                        if (source.Length <= totalWidth) return source.PadRight(totalWidth, paddingChar);
×
869
                        var result = $"{source.Substring(0, totalWidth - (postfix?.Length ?? 0))}{postfix ?? string.Empty}";
×
870
                        return result;
×
871
                }
872

873
                internal static StrongGridJsonObject ToStrongGridJsonObject<T>(this T source, bool ignoreDefault = true)
874
                {
875
                        var jsonObject = new StrongGridJsonObject();
1✔
876
                        foreach (var property in source.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
42✔
877
                        {
878
                                var propertyName = ((JsonPropertyNameAttribute)property.GetCustomAttribute(typeof(JsonPropertyNameAttribute))).Name;
20✔
879
                                var propertyValue = property.GetValue(source);
20✔
880

881
                                jsonObject.AddProperty(propertyName, propertyValue, ignoreDefault);
20✔
882
                        }
883

884
                        return jsonObject;
1✔
885
                }
886

887
                /// <summary>Asynchronously converts the JSON encoded content and convert it to an object of the desired type.</summary>
888
                /// <typeparam name="T">The response model to deserialize into.</typeparam>
889
                /// <param name="httpContent">The content.</param>
890
                /// <param name="propertyName">The name of the JSON property (or null if not applicable) where the desired data is stored.</param>
891
                /// <param name="throwIfPropertyIsMissing">Indicates if an exception should be thrown when the specified JSON property is missing from the response.</param>
892
                /// <param name="options">Options to control behavior Converter during parsing.</param>
893
                /// <param name="cancellationToken">The cancellation token.</param>
894
                /// <returns>Returns the strongly typed object.</returns>
895
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
896
                private static async Task<T> AsObject<T>(this HttpContent httpContent, string propertyName = null, bool throwIfPropertyIsMissing = true, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
897
                {
898
                        var responseContent = await httpContent.ReadAsStringAsync(null, cancellationToken).ConfigureAwait(false);
899

900
                        if (string.IsNullOrEmpty(propertyName))
901
                        {
902
                                return JsonSerializer.Deserialize<T>(responseContent, options ?? JsonFormatter.DeserializerOptions);
903
                        }
904

905
                        var jsonDoc = JsonDocument.Parse(responseContent, (JsonDocumentOptions)default);
906
                        if (jsonDoc.RootElement.TryGetProperty(propertyName, out JsonElement property))
907
                        {
908
                                return property.ToObject<T>(options);
909
                        }
910
                        else if (throwIfPropertyIsMissing)
911
                        {
912
                                throw new ArgumentException($"The response does not contain a field called '{propertyName}'", nameof(propertyName));
913
                        }
914
                        else
915
                        {
916
                                return default;
917
                        }
918
                }
919

920
                /// <summary>Get a raw JSON object representation of the response.</summary>
921
                /// <param name="httpContent">The content.</param>
922
                /// <param name="propertyName">The name of the JSON property (or null if not applicable) where the desired data is stored.</param>
923
                /// <param name="throwIfPropertyIsMissing">Indicates if an exception should be thrown when the specified JSON property is missing from the response.</param>
924
                /// <param name="cancellationToken">The cancellation token.</param>
925
                /// <returns>Returns the response body, or <c>null</c> if the response has no body.</returns>
926
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
927
                private static async Task<JsonDocument> AsRawJsonDocument(this HttpContent httpContent, string propertyName = null, bool throwIfPropertyIsMissing = true, CancellationToken cancellationToken = default)
928
                {
929
                        var responseContent = await httpContent.ReadAsStringAsync(null, cancellationToken).ConfigureAwait(false);
930

931
                        var jsonDoc = JsonDocument.Parse(responseContent, (JsonDocumentOptions)default);
932

933
                        if (string.IsNullOrEmpty(propertyName))
934
                        {
935
                                return jsonDoc;
936
                        }
937

938
                        if (jsonDoc.RootElement.TryGetProperty(propertyName, out JsonElement property))
939
                        {
940
                                var propertyContent = property.GetRawText();
941
                                return JsonDocument.Parse(propertyContent, (JsonDocumentOptions)default);
942
                        }
943
                        else if (throwIfPropertyIsMissing)
944
                        {
945
                                throw new ArgumentException($"The response does not contain a field called '{propertyName}'", nameof(propertyName));
946
                        }
947
                        else
948
                        {
949
                                return default;
950
                        }
951
                }
952

953
                /// <summary>Asynchronously retrieve the JSON encoded content and convert it to a 'PaginatedResponse' object.</summary>
954
                /// <typeparam name="T">The response model to deserialize into.</typeparam>
955
                /// <param name="httpContent">The content.</param>
956
                /// <param name="propertyName">The name of the JSON property (or null if not applicable) where the desired data is stored.</param>
957
                /// <param name="options">Options to control behavior Converter during parsing.</param>
958
                /// <param name="cancellationToken">The cancellation token.</param>
959
                /// <returns>Returns the response body, or <c>null</c> if the response has no body.</returns>
960
                /// <exception cref="SendGridException">An error occurred processing the response.</exception>
961
                private static async Task<PaginatedResponse<T>> AsPaginatedResponse<T>(this HttpContent httpContent, string propertyName, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
962
                {
963
                        var jsonDocument = await httpContent.AsRawJsonDocument(null, false, cancellationToken).ConfigureAwait(false);
964
                        var metadataProperty = jsonDocument.RootElement.GetProperty("_metadata");
965
                        var metadata = metadataProperty.ToObject<PaginationMetadata>(options);
966

967
                        if (!jsonDocument.RootElement.TryGetProperty(propertyName, out JsonElement jProperty))
968
                        {
969
                                throw new ArgumentException($"The response does not contain a field called '{propertyName}'", nameof(propertyName));
970
                        }
971

972
                        var result = new PaginatedResponse<T>()
973
                        {
974
                                PreviousPageToken = metadata.PrevToken,
975
                                CurrentPageToken = metadata.SelfToken,
976
                                NextPageToken = metadata.NextToken,
977
                                TotalRecords = metadata.Count,
978
                                Records = jProperty.ToObject<T[]>(options) ?? Array.Empty<T>()
979
                        };
980

981
                        return result;
982
                }
983

984
                private static T GetPropertyValue<T>(this JsonElement element, string[] names, T defaultValue, bool throwIfMissing)
985
                {
986
                        JsonElement? property = null;
×
987

988
                        foreach (var name in names)
×
989
                        {
990
                                property = element.GetProperty(name, false);
×
991
                                if (property.HasValue) break;
×
992
                        }
993

994
                        if (!property.HasValue) return defaultValue;
×
995

996
                        var typeOfT = typeof(T);
×
997

998
                        if (typeOfT.IsEnum)
×
999
                        {
1000
                                return property.Value.ValueKind switch
×
1001
                                {
×
1002
                                        JsonValueKind.String => (T)Enum.Parse(typeof(T), property.Value.GetString()),
×
1003
                                        JsonValueKind.Number => (T)Enum.ToObject(typeof(T), property.Value.GetInt16()),
×
1004
                                        _ => throw new ArgumentException($"Unable to convert a {property.Value.ValueKind} into a {typeof(T).FullName}", nameof(T)),
×
1005
                                };
×
1006
                        }
1007

1008
                        if (typeOfT.IsGenericType && typeOfT.GetGenericTypeDefinition() == typeof(Nullable<>))
×
1009
                        {
1010
                                var underlyingType = Nullable.GetUnderlyingType(typeOfT);
×
1011
                                var getElementValue = typeof(Internal)
×
1012
                                        .GetMethod(nameof(Internal.GetElementValue), BindingFlags.Static | BindingFlags.NonPublic)
×
1013
                                        .MakeGenericMethod(underlyingType);
×
1014

1015
                                return (T)getElementValue.Invoke(null, new object[] { property.Value });
×
1016
                        }
1017

1018
                        if (typeOfT.IsArray)
×
1019
                        {
1020
                                var elementType = typeOfT.GetElementType();
×
1021
                                var getElementValue = typeof(Internal)
×
1022
                                        .GetMethod(nameof(Internal.GetElementValue), BindingFlags.Static | BindingFlags.NonPublic)
×
1023
                                        .MakeGenericMethod(elementType);
×
1024

1025
                                var arrayList = new ArrayList(property.Value.GetArrayLength());
×
1026
                                foreach (var arrayElement in property.Value.EnumerateArray())
×
1027
                                {
1028
                                        var elementValue = getElementValue.Invoke(null, new object[] { arrayElement });
×
1029
                                        arrayList.Add(elementValue);
×
1030
                                }
1031

1032
                                return (T)Convert.ChangeType(arrayList.ToArray(elementType), typeof(T));
×
1033
                        }
1034

1035
                        return property.Value.GetElementValue<T>();
×
1036
                }
1037

1038
                private static T GetElementValue<T>(this JsonElement element)
1039
                {
1040
                        var typeOfT = typeof(T);
×
1041

1042
                        return typeOfT switch
×
1043
                        {
×
1044
                                Type boolType when boolType == typeof(bool) => (T)(object)element.GetBoolean(),
×
1045
                                Type strType when strType == typeof(string) => (T)(object)element.GetString(),
×
1046
                                Type bytesType when bytesType == typeof(byte[]) => (T)(object)element.GetBytesFromBase64(),
×
1047
                                Type sbyteType when sbyteType == typeof(sbyte) => (T)(object)element.GetSByte(),
×
1048
                                Type byteType when byteType == typeof(byte) => (T)(object)element.GetByte(),
×
1049
                                Type shortType when shortType == typeof(short) => (T)(object)element.GetInt16(),
×
1050
                                Type ushortType when ushortType == typeof(ushort) => (T)(object)element.GetUInt16(),
×
1051
                                Type intType when intType == typeof(int) => (T)(object)element.GetInt32(),
×
1052
                                Type uintType when uintType == typeof(uint) => (T)(object)element.GetUInt32(),
×
1053
                                Type longType when longType == typeof(long) => (T)(object)element.GetInt64(),
×
1054
                                Type ulongType when ulongType == typeof(ulong) => (T)(object)element.GetUInt64(),
×
1055
                                Type doubleType when doubleType == typeof(double) => (T)(object)element.GetDouble(),
×
1056
                                Type floatType when floatType == typeof(float) => (T)(object)element.GetSingle(),
×
1057
                                Type decimalType when decimalType == typeof(decimal) => (T)(object)element.GetDecimal(),
×
1058
                                Type datetimeType when datetimeType == typeof(DateTime) => (T)(object)element.GetDateTime(),
×
1059
                                Type offsetType when offsetType == typeof(DateTimeOffset) => (T)(object)element.GetDateTimeOffset(),
×
1060
                                Type guidType when guidType == typeof(Guid) => (T)(object)element.GetGuid(),
×
1061
                                _ => throw new ArgumentException($"Unsable to map {typeof(T).FullName} to a corresponding JSON type", nameof(T)),
×
1062
                        };
×
1063
                }
1064
        }
1065
}
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