From 193c18dacc00e61b814967e590ecaba463cd97c1 Mon Sep 17 00:00:00 2001 From: Caleb Kiage <747955+calebkiage@users.noreply.github.com> Date: Thu, 2 Nov 2023 01:13:27 +0300 Subject: [PATCH 1/7] Add uri replacement handler --- .../Middleware/UriReplacementHandlerTests.cs | 64 +++++++++ src/Middleware/UriReplacementHandler.cs | 126 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs create mode 100644 src/Middleware/UriReplacementHandler.cs diff --git a/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs new file mode 100644 index 0000000..ced259d --- /dev/null +++ b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Kiota.Http.HttpClientLibrary.Middleware; +using Moq; +using Xunit; + +namespace Microsoft.Kiota.Http.HttpClientLibrary.Tests.Middleware; + +public class UriReplacementOptionTests { + [Fact] + public void Does_Nothing_When_Url_Replacement_Is_Disabled() + { + var uri = new Uri("http://localhost/test"); + var disabled = new UriReplacementHandlerOption(false, new Dictionary()); + + Assert.Equal(uri, disabled.Replace(uri)); + + disabled = new UriReplacementHandlerOption(false, new Dictionary{ + {"test", ""} + }); + + Assert.Equal(uri, disabled.Replace(uri)); + } + + [Fact] + public void Returns_Null_When_Url_Provided_Is_Null() + { + var disabled = new UriReplacementHandlerOption(false, new Dictionary()); + + Assert.Null(disabled.Replace(null)); + } + + [Fact] + public void Replaces_Key_In_Path_With_Value() + { + var uri = new Uri("http://localhost/test"); + var disabled = new UriReplacementHandlerOption(true, new Dictionary{{"test", ""}}); + + Assert.Equal("http://localhost/", disabled.Replace(uri)!.ToString()); + } +} + +public class UriReplacementHandlerTests +{ + [Fact] + public async Task Calls_Uri_ReplacementAsync() + { + var mockReplacement = new Mock(); + mockReplacement.Setup(static x => x.IsEnabled()).Returns(true); + mockReplacement.Setup(static x => x.Replace(It.IsAny())).Returns(new Uri("http://changed")); + + var handler = new UriReplacementHandler(mockReplacement.Object) + { + InnerHandler = new FakeSuccessHandler() + }; + var msg = new HttpRequestMessage(HttpMethod.Get, "http://localhost"); + var client = new HttpClient(handler); + await client.SendAsync(msg); + + mockReplacement.Verify(static x=> x.Replace(It.IsAny()), Times.Once()); + } +} diff --git a/src/Middleware/UriReplacementHandler.cs b/src/Middleware/UriReplacementHandler.cs new file mode 100644 index 0000000..1b5786d --- /dev/null +++ b/src/Middleware/UriReplacementHandler.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Http.HttpClientLibrary.Extensions; + +namespace Microsoft.Kiota.Http.HttpClientLibrary.Middleware; + +/// +/// Interface for making URI replacements. +/// +public interface IUriReplacementHandlerOption : IRequestOption +{ + /// + /// Check if URI replacement is enabled for the option. + /// + /// true if replacement is enabled or false otherwise. + bool IsEnabled(); + + /// + /// Accepts a URI and returns a new URI with all replacements applied. + /// + /// The URI to apply replacements to + /// A new URI with all replacements applied. + Uri? Replace(Uri? original); +} + +/// +/// Url replacement options. +/// +public class UriReplacementHandlerOption : IUriReplacementHandlerOption +{ + private readonly bool isEnabled = false; + + private readonly IEnumerable> replacementPairs = new Dictionary(); + + /// + /// Creates a new instance of UriReplacementOption. + /// + /// Whether replacement is enabled. + /// Replacements with the key being a string to match against and the value being the replacement. + public UriReplacementHandlerOption(bool isEnabled, IEnumerable> replacementPairs) + { + this.isEnabled = isEnabled; + this.replacementPairs = replacementPairs; + + } + + /// + public bool IsEnabled() + { + return isEnabled; + } + + /// + public Uri? Replace(Uri? original) + { + if(original is null) return null; + + if(!isEnabled) + { + return original; + } + + var newUrl = new UriBuilder(original); + foreach(var pair in replacementPairs) + { + newUrl.Path = newUrl.Path.Replace(pair.Key, pair.Value); + } + + return newUrl.Uri; + } +} + +/// +/// Replaces a portion of the URL. +/// +/// A type with the rules used to perform a URI replacement. +public class UriReplacementHandler : DelegatingHandler where TUriReplacementHandlerOption : IUriReplacementHandlerOption, IRequestOption +{ + private readonly TUriReplacementHandlerOption uriReplacement; + + /// + /// Creates a new UriReplacementHandler. + /// + /// An object with the URI replacement rules. + public UriReplacementHandler(TUriReplacementHandlerOption uriReplacement) + { + this.uriReplacement = uriReplacement; + } + + /// + protected override async Task SendAsync( + HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) + { + ActivitySource? activitySource; + Activity? activity; + if(request.GetRequestOption() is ObservabilityOptions obsOptions) + { + activitySource = new ActivitySource(obsOptions.TracerInstrumentationName); + activity = activitySource.StartActivity($"{nameof(UriReplacementHandler)}_{nameof(SendAsync)}"); + activity?.SetTag("com.microsoft.kiota.handler.uri_replacement.enable", uriReplacement.IsEnabled()); + } + else + { + activity = null; + activitySource = null; + } + + try + { + var uriReplacementHandlerOption = request.GetRequestOption() ?? uriReplacement; + if (uriReplacement.IsEnabled()) { + request.RequestUri = uriReplacementHandlerOption.Replace(request.RequestUri); + } + return await base.SendAsync(request, cancellationToken); + } + finally + { + activity?.Dispose(); + activitySource?.Dispose(); + } + } +} From 3fd6084630486d0602cc00720195611db62f957a Mon Sep 17 00:00:00 2001 From: Caleb Kiage <747955+calebkiage@users.noreply.github.com> Date: Thu, 2 Nov 2023 01:14:56 +0300 Subject: [PATCH 2/7] Bump minor version --- src/Microsoft.Kiota.Http.HttpClientLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj b/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj index 4358a0a..919ce11 100644 --- a/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj +++ b/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj @@ -14,7 +14,7 @@ https://aka.ms/kiota/docs true true - 1.2.0 + 1.3.0 true From 1fb14b9a3c412677bbc18e0eae4725c6e894ca60 Mon Sep 17 00:00:00 2001 From: Caleb Kiage <747955+calebkiage@users.noreply.github.com> Date: Thu, 2 Nov 2023 01:21:52 +0300 Subject: [PATCH 3/7] Fix code smell --- src/Middleware/UriReplacementHandler.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Middleware/UriReplacementHandler.cs b/src/Middleware/UriReplacementHandler.cs index 1b5786d..05a40ab 100644 --- a/src/Middleware/UriReplacementHandler.cs +++ b/src/Middleware/UriReplacementHandler.cs @@ -32,9 +32,9 @@ public interface IUriReplacementHandlerOption : IRequestOption /// public class UriReplacementHandlerOption : IUriReplacementHandlerOption { - private readonly bool isEnabled = false; + private readonly bool isEnabled; - private readonly IEnumerable> replacementPairs = new Dictionary(); + private readonly IEnumerable> replacementPairs; /// /// Creates a new instance of UriReplacementOption. @@ -45,7 +45,6 @@ public UriReplacementHandlerOption(bool isEnabled, IEnumerable @@ -59,7 +58,7 @@ public bool IsEnabled() { if(original is null) return null; - if(!isEnabled) + if(!IsEnabled()) { return original; } From cfe85d0775161777c61edb0dd3243441fd46f8bf Mon Sep 17 00:00:00 2001 From: Caleb Kiage <747955+calebkiage@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:23:09 +0300 Subject: [PATCH 4/7] Fix request option lookup. --- .../Middleware/UriReplacementHandlerTests.cs | 8 ++- .../Options/UriReplacementHandlerOption.cs | 70 +++++++++++++++++++ src/Middleware/UriReplacementHandler.cs | 70 +------------------ 3 files changed, 78 insertions(+), 70 deletions(-) create mode 100644 src/Middleware/Options/UriReplacementHandlerOption.cs diff --git a/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs index ced259d..02df8ce 100644 --- a/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs +++ b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Middleware/UriReplacementHandlerTests.cs @@ -3,6 +3,7 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.Kiota.Http.HttpClientLibrary.Middleware; +using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options; using Moq; using Xunit; @@ -15,6 +16,7 @@ public void Does_Nothing_When_Url_Replacement_Is_Disabled() var uri = new Uri("http://localhost/test"); var disabled = new UriReplacementHandlerOption(false, new Dictionary()); + Assert.False(disabled.IsEnabled()); Assert.Equal(uri, disabled.Replace(uri)); disabled = new UriReplacementHandlerOption(false, new Dictionary{ @@ -29,6 +31,7 @@ public void Returns_Null_When_Url_Provided_Is_Null() { var disabled = new UriReplacementHandlerOption(false, new Dictionary()); + Assert.False(disabled.IsEnabled()); Assert.Null(disabled.Replace(null)); } @@ -36,9 +39,10 @@ public void Returns_Null_When_Url_Provided_Is_Null() public void Replaces_Key_In_Path_With_Value() { var uri = new Uri("http://localhost/test"); - var disabled = new UriReplacementHandlerOption(true, new Dictionary{{"test", ""}}); + var option = new UriReplacementHandlerOption(true, new Dictionary{{"test", ""}}); - Assert.Equal("http://localhost/", disabled.Replace(uri)!.ToString()); + Assert.True(option.IsEnabled()); + Assert.Equal("http://localhost/", option.Replace(uri)!.ToString()); } } diff --git a/src/Middleware/Options/UriReplacementHandlerOption.cs b/src/Middleware/Options/UriReplacementHandlerOption.cs new file mode 100644 index 0000000..b0a14e7 --- /dev/null +++ b/src/Middleware/Options/UriReplacementHandlerOption.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Microsoft.Kiota.Abstractions; + +namespace Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options; + +/// +/// Interface for making URI replacements. +/// +public interface IUriReplacementHandlerOption : IRequestOption +{ + /// + /// Check if URI replacement is enabled for the option. + /// + /// true if replacement is enabled or false otherwise. + bool IsEnabled(); + + /// + /// Accepts a URI and returns a new URI with all replacements applied. + /// + /// The URI to apply replacements to + /// A new URI with all replacements applied. + Uri? Replace(Uri? original); +} + +/// +/// Url replacement options. +/// +public class UriReplacementHandlerOption : IUriReplacementHandlerOption +{ + private readonly bool isEnabled; + + private readonly IEnumerable> replacementPairs; + + /// + /// Creates a new instance of UriReplacementOption. + /// + /// Whether replacement is enabled. + /// Replacements with the key being a string to match against and the value being the replacement. + public UriReplacementHandlerOption(bool isEnabled, IEnumerable> replacementPairs) + { + this.isEnabled = isEnabled; + this.replacementPairs = replacementPairs; + } + + /// + public bool IsEnabled() + { + return isEnabled; + } + + /// + public Uri? Replace(Uri? original) + { + if(original is null) return null; + + if(!isEnabled) + { + return original; + } + + var newUrl = new UriBuilder(original); + foreach(var pair in replacementPairs) + { + newUrl.Path = newUrl.Path.Replace(pair.Key, pair.Value); + } + + return newUrl.Uri; + } +} diff --git a/src/Middleware/UriReplacementHandler.cs b/src/Middleware/UriReplacementHandler.cs index 05a40ab..33e6a60 100644 --- a/src/Middleware/UriReplacementHandler.cs +++ b/src/Middleware/UriReplacementHandler.cs @@ -1,78 +1,12 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Kiota.Abstractions; using Microsoft.Kiota.Http.HttpClientLibrary.Extensions; +using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options; namespace Microsoft.Kiota.Http.HttpClientLibrary.Middleware; -/// -/// Interface for making URI replacements. -/// -public interface IUriReplacementHandlerOption : IRequestOption -{ - /// - /// Check if URI replacement is enabled for the option. - /// - /// true if replacement is enabled or false otherwise. - bool IsEnabled(); - - /// - /// Accepts a URI and returns a new URI with all replacements applied. - /// - /// The URI to apply replacements to - /// A new URI with all replacements applied. - Uri? Replace(Uri? original); -} - -/// -/// Url replacement options. -/// -public class UriReplacementHandlerOption : IUriReplacementHandlerOption -{ - private readonly bool isEnabled; - - private readonly IEnumerable> replacementPairs; - - /// - /// Creates a new instance of UriReplacementOption. - /// - /// Whether replacement is enabled. - /// Replacements with the key being a string to match against and the value being the replacement. - public UriReplacementHandlerOption(bool isEnabled, IEnumerable> replacementPairs) - { - this.isEnabled = isEnabled; - this.replacementPairs = replacementPairs; - } - - /// - public bool IsEnabled() - { - return isEnabled; - } - - /// - public Uri? Replace(Uri? original) - { - if(original is null) return null; - - if(!IsEnabled()) - { - return original; - } - - var newUrl = new UriBuilder(original); - foreach(var pair in replacementPairs) - { - newUrl.Path = newUrl.Path.Replace(pair.Key, pair.Value); - } - - return newUrl.Uri; - } -} - /// /// Replaces a portion of the URL. /// @@ -110,7 +44,7 @@ protected override async Task SendAsync( try { - var uriReplacementHandlerOption = request.GetRequestOption() ?? uriReplacement; + var uriReplacementHandlerOption = request.GetRequestOption() ?? uriReplacement; if (uriReplacement.IsEnabled()) { request.RequestUri = uriReplacementHandlerOption.Replace(request.RequestUri); } From 7c174e2a25bf4af25046cb5afa71e457bb9dd4df Mon Sep 17 00:00:00 2001 From: Caleb Kiage <747955+calebkiage@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:29:45 +0300 Subject: [PATCH 5/7] Add changelog entry. --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d31ccc..94b5bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.3.0] - 2023-11-02 + +### Added + +- Added uri replacement handler. + ## [1.2.0] - 2023-10-23 ### Added From c62b5b32b8d32a0676861f64ae25cadda65a0d9b Mon Sep 17 00:00:00 2001 From: Caleb Kiage <747955+calebkiage@users.noreply.github.com> Date: Thu, 2 Nov 2023 16:17:22 +0300 Subject: [PATCH 6/7] Remove redundant generic constraint --- src/Middleware/UriReplacementHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/UriReplacementHandler.cs b/src/Middleware/UriReplacementHandler.cs index 33e6a60..05e43ad 100644 --- a/src/Middleware/UriReplacementHandler.cs +++ b/src/Middleware/UriReplacementHandler.cs @@ -11,7 +11,7 @@ namespace Microsoft.Kiota.Http.HttpClientLibrary.Middleware; /// Replaces a portion of the URL. /// /// A type with the rules used to perform a URI replacement. -public class UriReplacementHandler : DelegatingHandler where TUriReplacementHandlerOption : IUriReplacementHandlerOption, IRequestOption +public class UriReplacementHandler : DelegatingHandler where TUriReplacementHandlerOption : IUriReplacementHandlerOption { private readonly TUriReplacementHandlerOption uriReplacement; From 881a1be20b8170708404a8ed863f277cce37a503 Mon Sep 17 00:00:00 2001 From: Caleb Kiage <747955+calebkiage@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:12:43 +0300 Subject: [PATCH 7/7] Remove unnecessary per-request replacement option lookup --- src/Middleware/UriReplacementHandler.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Middleware/UriReplacementHandler.cs b/src/Middleware/UriReplacementHandler.cs index 05e43ad..9c86409 100644 --- a/src/Middleware/UriReplacementHandler.cs +++ b/src/Middleware/UriReplacementHandler.cs @@ -44,10 +44,7 @@ protected override async Task SendAsync( try { - var uriReplacementHandlerOption = request.GetRequestOption() ?? uriReplacement; - if (uriReplacement.IsEnabled()) { - request.RequestUri = uriReplacementHandlerOption.Replace(request.RequestUri); - } + request.RequestUri = uriReplacement.Replace(request.RequestUri); return await base.SendAsync(request, cancellationToken); } finally