Skip to content
This repository was archived by the owner on Jul 9, 2024. It is now read-only.

Commit f93a5f8

Browse files
authored
Merge pull request #260 from kasperk81/main
Remove all LINQ usage from product code
2 parents d714e6b + dea20d9 commit f93a5f8

10 files changed

+243
-113
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [Unreleased]
11+
12+
## [1.4.3] - 2024-05-24
13+
14+
### Changed
15+
16+
- Remove all LINQ usage from product code
17+
1018
## [1.4.2] - 2024-05-21
1119

1220
### Added

src/HttpClientRequestAdapter.cs

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System;
66
using System.Collections.Generic;
77
using System.IO;
8-
using System.Linq;
98
using System.Net.Http;
109
using System.Threading.Tasks;
1110
using Microsoft.Kiota.Abstractions;
@@ -388,7 +387,9 @@ private async Task ThrowIfFailedResponse(HttpResponseMessage response, Dictionar
388387

389388
var statusCodeAsInt = (int)response.StatusCode;
390389
var statusCodeAsString = statusCodeAsInt.ToString();
391-
var responseHeadersDictionary = response.Headers.ToDictionary(x => x.Key, y => y.Value, StringComparer.OrdinalIgnoreCase);
390+
var responseHeadersDictionary = new Dictionary<string, IEnumerable<string>>(StringComparer.OrdinalIgnoreCase);
391+
foreach (var header in response.Headers)
392+
responseHeadersDictionary[header.Key] = header.Value;
392393
ParsableFactory<IParsable>? errorFactory;
393394
if(errorMapping == null ||
394395
!errorMapping.TryGetValue(statusCodeAsString, out errorFactory) &&
@@ -470,51 +471,86 @@ private async Task<HttpResponseMessage> GetHttpResponseMessage(RequestInformatio
470471
var ex = new InvalidOperationException("Could not get a response after calling the service");
471472
throw ex;
472473
}
473-
if(response.Headers.TryGetValues("Content-Length", out var contentLengthValues) &&
474-
contentLengthValues.Any() &&
475-
contentLengthValues.First() is string firstContentLengthValue &&
476-
int.TryParse(firstContentLengthValue, out var contentLength))
474+
if(response.Headers.TryGetValues("Content-Length", out var contentLengthValues))
477475
{
478-
activityForAttributes?.SetTag("http.response_content_length", contentLength);
476+
using var contentLengthEnumerator = contentLengthValues.GetEnumerator();
477+
if(contentLengthEnumerator.MoveNext() && int.TryParse(contentLengthEnumerator.Current, out var contentLength))
478+
{
479+
activityForAttributes?.SetTag("http.response_content_length", contentLength);
480+
}
479481
}
480-
if(response.Headers.TryGetValues("Content-Type", out var contentTypeValues) &&
481-
contentTypeValues.Any() &&
482-
contentTypeValues.First() is string firstContentTypeValue)
482+
if(response.Headers.TryGetValues("Content-Type", out var contentTypeValues))
483483
{
484-
activityForAttributes?.SetTag("http.response_content_type", firstContentTypeValue);
484+
using var contentTypeEnumerator = contentTypeValues.GetEnumerator();
485+
if(contentTypeEnumerator.MoveNext())
486+
{
487+
activityForAttributes?.SetTag("http.response_content_type", contentTypeEnumerator.Current);
488+
}
485489
}
486490
activityForAttributes?.SetTag("http.status_code", (int)response.StatusCode);
487491
activityForAttributes?.SetTag("http.flavor", $"{response.Version.Major}.{response.Version.Minor}");
488492

489493
return await RetryCAEResponseIfRequired(response, requestInfo, cancellationToken, claims, activityForAttributes).ConfigureAwait(false);
490494
}
495+
491496
private static readonly Regex caeValueRegex = new("\"([^\"]*)\"", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
497+
492498
/// <summary>
493499
/// The key for the event raised by tracing when an authentication challenge is received
494500
/// </summary>
495501
public const string AuthenticateChallengedEventKey = "com.microsoft.kiota.authenticate_challenge_received";
502+
496503
private async Task<HttpResponseMessage> RetryCAEResponseIfRequired(HttpResponseMessage response, RequestInformation requestInfo, CancellationToken cancellationToken, string? claims, Activity? activityForAttributes)
497504
{
498505
using var span = activitySource?.StartActivity(nameof(RetryCAEResponseIfRequired));
499506
if(response.StatusCode == HttpStatusCode.Unauthorized &&
500507
string.IsNullOrEmpty(claims) && // avoid infinite loop, we only retry once
501-
(requestInfo.Content?.CanSeek ?? true) &&
502-
response.Headers.WwwAuthenticate?.FirstOrDefault(filterAuthHeader) is AuthenticationHeaderValue authHeader &&
503-
authHeader.Parameter?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
504-
.Select(static x => x.Trim())
505-
.FirstOrDefault(static x => x.StartsWith(ClaimsKey, StringComparison.OrdinalIgnoreCase)) is string rawResponseClaims &&
506-
caeValueRegex.Match(rawResponseClaims) is Match claimsMatch &&
507-
claimsMatch.Groups.Count > 1 &&
508-
claimsMatch.Groups[1].Value is string responseClaims)
508+
(requestInfo.Content?.CanSeek ?? true))
509509
{
510-
span?.AddEvent(new ActivityEvent(AuthenticateChallengedEventKey));
511-
activityForAttributes?.SetTag("http.retry_count", 1);
512-
requestInfo.Content?.Seek(0, SeekOrigin.Begin);
513-
await DrainAsync(response, cancellationToken).ConfigureAwait(false);
514-
return await GetHttpResponseMessage(requestInfo, cancellationToken, activityForAttributes, responseClaims).ConfigureAwait(false);
510+
AuthenticationHeaderValue? authHeader = null;
511+
foreach(var header in response.Headers.WwwAuthenticate)
512+
{
513+
if(filterAuthHeader(header))
514+
{
515+
authHeader = header;
516+
break;
517+
}
518+
}
519+
520+
if(authHeader is not null)
521+
{
522+
var authHeaderParameters = authHeader.Parameter?.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries);
523+
524+
string? rawResponseClaims = null;
525+
if(authHeaderParameters != null)
526+
{
527+
foreach(var parameter in authHeaderParameters)
528+
{
529+
var trimmedParameter = parameter.Trim();
530+
if(trimmedParameter.StartsWith(ClaimsKey, StringComparison.OrdinalIgnoreCase))
531+
{
532+
rawResponseClaims = trimmedParameter;
533+
break;
534+
}
535+
}
536+
}
537+
538+
if(rawResponseClaims != null &&
539+
caeValueRegex.Match(rawResponseClaims) is Match claimsMatch &&
540+
claimsMatch.Groups.Count > 1 &&
541+
claimsMatch.Groups[1].Value is string responseClaims)
542+
{
543+
span?.AddEvent(new ActivityEvent(AuthenticateChallengedEventKey));
544+
activityForAttributes?.SetTag("http.retry_count", 1);
545+
requestInfo.Content?.Seek(0, SeekOrigin.Begin);
546+
await DrainAsync(response, cancellationToken).ConfigureAwait(false);
547+
return await GetHttpResponseMessage(requestInfo, cancellationToken, activityForAttributes, responseClaims).ConfigureAwait(false);
548+
}
549+
}
515550
}
516551
return response;
517552
}
553+
518554
private void SetBaseUrlForRequestInformation(RequestInformation requestInfo)
519555
{
520556
IDictionaryExtensions.AddOrReplace(requestInfo.PathParameters, "baseurl", BaseUrl!);
@@ -527,6 +563,7 @@ private void SetBaseUrlForRequestInformation(RequestInformation requestInfo)
527563
return result;
528564
else throw new InvalidOperationException($"Could not convert the request information to a {typeof(T).Name}");
529565
}
566+
530567
private HttpRequestMessage GetRequestMessageFromRequestInformation(RequestInformation requestInfo, Activity? activityForAttributes)
531568
{
532569
using var span = activitySource?.StartActivity(nameof(GetRequestMessageFromRequestInformation));
@@ -544,37 +581,42 @@ private HttpRequestMessage GetRequestMessageFromRequestInformation(RequestInform
544581
Version = new Version(2, 0)
545582
};
546583

547-
if(requestInfo.RequestOptions.Any())
584+
if(requestInfo.RequestOptions != null)
548585
#if NET5_0_OR_GREATER
549586
{
550-
requestInfo.RequestOptions.ToList().ForEach(x => message.Options.Set(new HttpRequestOptionsKey<IRequestOption>(x.GetType().FullName!), x));
587+
foreach (var option in requestInfo.RequestOptions)
588+
message.Options.Set(new HttpRequestOptionsKey<IRequestOption>(option.GetType().FullName!), option);
551589
}
552590
message.Options.Set(new HttpRequestOptionsKey<IRequestOption>(typeof(ObservabilityOptions).FullName!), obsOptions);
553591
#else
554592
{
555-
requestInfo.RequestOptions.ToList().ForEach(x => IDictionaryExtensions.TryAdd(message.Properties, x.GetType().FullName!, x));
593+
foreach(var option in requestInfo.RequestOptions)
594+
IDictionaryExtensions.TryAdd(message.Properties, option.GetType().FullName!, option);
556595
}
557596
IDictionaryExtensions.TryAdd(message.Properties!, typeof(ObservabilityOptions).FullName, obsOptions);
558597
#endif
559598

560599
if(requestInfo.Content != null && requestInfo.Content != Stream.Null)
561600
message.Content = new StreamContent(requestInfo.Content);
562-
if(requestInfo.Headers?.Any() ?? false)
601+
if(requestInfo.Headers != null)
563602
foreach(var header in requestInfo.Headers)
564603
if(!message.Headers.TryAddWithoutValidation(header.Key, header.Value) && message.Content != null)
565604
message.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);// Try to add the headers we couldn't add to the HttpRequestMessage before to the HttpContent
566605

567606
if(message.Content != null)
568607
{
569-
if(message.Content.Headers.TryGetValues("Content-Length", out var contentLenValues) &&
570-
contentLenValues.Any() &&
571-
contentLenValues.First() is string contentLenValue &&
572-
int.TryParse(contentLenValue, out var contentLenValueInt))
573-
activityForAttributes?.SetTag("http.request_content_length", contentLenValueInt);
574-
if(message.Content.Headers.TryGetValues("Content-Type", out var contentTypeValues) &&
575-
contentTypeValues.Any() &&
576-
contentTypeValues.First() is string contentTypeValue)
577-
activityForAttributes?.SetTag("http.request_content_type", contentTypeValue);
608+
if(message.Content.Headers.TryGetValues("Content-Length", out var contentLenValues))
609+
{
610+
var contentLenEnumerator = contentLenValues.GetEnumerator();
611+
if(contentLenEnumerator.MoveNext() && int.TryParse(contentLenEnumerator.Current, out var contentLenValueInt))
612+
activityForAttributes?.SetTag("http.request_content_length", contentLenValueInt);
613+
}
614+
if(message.Content.Headers.TryGetValues("Content-Type", out var contentTypeValues))
615+
{
616+
var contentTypeEnumerator = contentTypeValues.GetEnumerator();
617+
if(contentTypeEnumerator.MoveNext())
618+
activityForAttributes?.SetTag("http.request_content_type", contentTypeEnumerator.Current);
619+
}
578620
}
579621
return message;
580622
}

src/KiotaClientFactory.cs

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
33
// ------------------------------------------------------------------------------
44

5-
using System.Linq;
5+
using System;
66
using System.Collections.Generic;
77
using System.Net;
88
using System.Net.Http;
@@ -26,8 +26,17 @@ public static class KiotaClientFactory
2626
/// <returns>The <see cref="HttpClient"/> with the default middlewares.</returns>
2727
public static HttpClient Create(HttpMessageHandler? finalHandler = null, IRequestOption[]? optionsForHandlers = null)
2828
{
29-
var defaultHandlers = CreateDefaultHandlers(optionsForHandlers);
30-
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), defaultHandlers.ToArray());
29+
var defaultHandlersEnumerable = CreateDefaultHandlers(optionsForHandlers);
30+
int count = 0;
31+
foreach(var _ in defaultHandlersEnumerable) count++;
32+
33+
var defaultHandlersArray = new DelegatingHandler[count];
34+
int index = 0;
35+
foreach(var handler2 in defaultHandlersEnumerable)
36+
{
37+
defaultHandlersArray[index++] = handler2;
38+
}
39+
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), defaultHandlersArray);
3140
return handler != null ? new HttpClient(handler) : new HttpClient();
3241
}
3342

@@ -39,9 +48,16 @@ public static HttpClient Create(HttpMessageHandler? finalHandler = null, IReques
3948
/// <returns>The <see cref="HttpClient"/> with the custom handlers.</returns>
4049
public static HttpClient Create(IList<DelegatingHandler> handlers, HttpMessageHandler? finalHandler = null)
4150
{
42-
if(handlers == null || !handlers.Any())
51+
if(handlers == null || handlers.Count == 0)
4352
return Create(finalHandler);
44-
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), handlers.ToArray());
53+
54+
DelegatingHandler[] handlersArray = new DelegatingHandler[handlers.Count];
55+
for(int i = 0; i < handlers.Count; i++)
56+
{
57+
handlersArray[i] = handlers[i];
58+
}
59+
60+
var handler = ChainHandlersCollectionAndGetFirstLink(finalHandler ?? GetDefaultHttpMessageHandler(), handlersArray);
4561
return handler != null ? new HttpClient(handler) : new HttpClient();
4662
}
4763

@@ -51,35 +67,39 @@ public static HttpClient Create(IList<DelegatingHandler> handlers, HttpMessageHa
5167
/// <returns>A list of the default handlers used by the client.</returns>
5268
public static IList<DelegatingHandler> CreateDefaultHandlers(IRequestOption[]? optionsForHandlers = null)
5369
{
54-
optionsForHandlers ??= [];
55-
56-
return new List<DelegatingHandler>
57-
{
58-
//add the default middlewares as they are ready, and add them to the list below as well
59-
60-
optionsForHandlers.OfType<UriReplacementHandlerOption>().FirstOrDefault() is UriReplacementHandlerOption uriReplacementOption
61-
? new UriReplacementHandler<UriReplacementHandlerOption>(uriReplacementOption)
62-
: new UriReplacementHandler<UriReplacementHandlerOption>(),
63-
64-
optionsForHandlers.OfType<RetryHandlerOption>().FirstOrDefault() is RetryHandlerOption retryHandlerOption
65-
? new RetryHandler(retryHandlerOption)
66-
: new RetryHandler(),
67-
68-
optionsForHandlers.OfType<RedirectHandlerOption>().FirstOrDefault() is RedirectHandlerOption redirectHandlerOption
69-
? new RedirectHandler(redirectHandlerOption)
70-
: new RedirectHandler(),
70+
optionsForHandlers ??= Array.Empty<IRequestOption>();
7171

72-
optionsForHandlers.OfType<ParametersNameDecodingOption>().FirstOrDefault() is ParametersNameDecodingOption parametersNameDecodingOption
73-
? new ParametersNameDecodingHandler(parametersNameDecodingOption)
74-
: new ParametersNameDecodingHandler(),
72+
UriReplacementHandlerOption? uriReplacementOption = null;
73+
RetryHandlerOption? retryHandlerOption = null;
74+
RedirectHandlerOption? redirectHandlerOption = null;
75+
ParametersNameDecodingOption? parametersNameDecodingOption = null;
76+
UserAgentHandlerOption? userAgentHandlerOption = null;
77+
HeadersInspectionHandlerOption? headersInspectionHandlerOption = null;
7578

76-
optionsForHandlers.OfType<UserAgentHandlerOption>().FirstOrDefault() is UserAgentHandlerOption userAgentHandlerOption
77-
? new UserAgentHandler(userAgentHandlerOption)
78-
: new UserAgentHandler(),
79+
foreach(var option in optionsForHandlers)
80+
{
81+
if(uriReplacementOption == null && option is UriReplacementHandlerOption uriOption)
82+
uriReplacementOption = uriOption;
83+
else if(retryHandlerOption == null && option is RetryHandlerOption retryOption)
84+
retryHandlerOption = retryOption;
85+
else if(redirectHandlerOption == null && option is RedirectHandlerOption redirectOption)
86+
redirectHandlerOption = redirectOption;
87+
else if(parametersNameDecodingOption == null && option is ParametersNameDecodingOption parametersOption)
88+
parametersNameDecodingOption = parametersOption;
89+
else if(userAgentHandlerOption == null && option is UserAgentHandlerOption userAgentOption)
90+
userAgentHandlerOption = userAgentOption;
91+
else if(headersInspectionHandlerOption == null && option is HeadersInspectionHandlerOption headersOption)
92+
headersInspectionHandlerOption = headersOption;
93+
}
7994

80-
optionsForHandlers.OfType<HeadersInspectionHandlerOption>().FirstOrDefault() is HeadersInspectionHandlerOption headersInspectionHandlerOption
81-
? new HeadersInspectionHandler(headersInspectionHandlerOption)
82-
: new HeadersInspectionHandler(),
95+
return new List<DelegatingHandler>
96+
{
97+
uriReplacementOption != null ? new UriReplacementHandler<UriReplacementHandlerOption>(uriReplacementOption) : new UriReplacementHandler<UriReplacementHandlerOption>(),
98+
retryHandlerOption != null ? new RetryHandler(retryHandlerOption) : new RetryHandler(),
99+
redirectHandlerOption != null ? new RedirectHandler(redirectHandlerOption) : new RedirectHandler(),
100+
parametersNameDecodingOption != null ? new ParametersNameDecodingHandler(parametersNameDecodingOption) : new ParametersNameDecodingHandler(),
101+
userAgentHandlerOption != null ? new UserAgentHandler(userAgentHandlerOption) : new UserAgentHandler(),
102+
headersInspectionHandlerOption != null ? new HeadersInspectionHandler(headersInspectionHandlerOption) : new HeadersInspectionHandler(),
83103
};
84104
}
85105

@@ -109,7 +129,7 @@ public static IList<DelegatingHandler> CreateDefaultHandlers(IRequestOption[]? o
109129
/// <returns>The created <see cref="DelegatingHandler"/>.</returns>
110130
public static DelegatingHandler? ChainHandlersCollectionAndGetFirstLink(HttpMessageHandler? finalHandler, params DelegatingHandler[] handlers)
111131
{
112-
if(handlers == null || !handlers.Any()) return default;
132+
if(handlers == null || handlers.Length == 0) return default;
113133
var handlersCount = handlers.Length;
114134
for(var i = 0; i < handlersCount; i++)
115135
{

0 commit comments

Comments
 (0)