From 393984ff9ee02d584f22fe714b2eb843da2c483a Mon Sep 17 00:00:00 2001 From: Andrew Omondi Date: Tue, 23 Jan 2024 11:48:28 +0300 Subject: [PATCH] Adds XXX mapping code to adapter --- CHANGELOG.md | 6 ++ .../Mocks/MockError.cs | 19 ++++ .../RequestAdapterTests.cs | 89 +++++++++++++++++++ src/HttpClientRequestAdapter.cs | 5 +- ...rosoft.Kiota.Http.HttpClientLibrary.csproj | 2 +- 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 Microsoft.Kiota.Http.HttpClientLibrary.Tests/Mocks/MockError.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 48da269..6cf6407 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.5] - 2023-01-23 + +### Added + +- Adds support for `XXX` status code error mapping to HttpClientRequestAdapter. + ## [1.3.4] - 2023-12-29 ### Added diff --git a/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Mocks/MockError.cs b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Mocks/MockError.cs new file mode 100644 index 0000000..52b57b1 --- /dev/null +++ b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/Mocks/MockError.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Abstractions.Serialization; + +namespace Microsoft.Kiota.Http.HttpClientLibrary.Tests.Mocks; + +public class MockError(string message) : ApiException(message), IParsable +{ + public IDictionary> GetFieldDeserializers() + { + return new Dictionary>(); + } + + public void Serialize(ISerializationWriter writer) + { + } +} + diff --git a/Microsoft.Kiota.Http.HttpClientLibrary.Tests/RequestAdapterTests.cs b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/RequestAdapterTests.cs index 37e9610..8ea1fbe 100644 --- a/Microsoft.Kiota.Http.HttpClientLibrary.Tests/RequestAdapterTests.cs +++ b/Microsoft.Kiota.Http.HttpClientLibrary.Tests/RequestAdapterTests.cs @@ -446,5 +446,94 @@ public async void SetsTheApiExceptionStatusCode(HttpStatusCode statusCode) Assert.True(e.ResponseHeaders.ContainsKey("request-id")); } } + [InlineData(HttpStatusCode.NotFound)]// 4XX + [InlineData(HttpStatusCode.BadGateway)]// 5XX + [Theory] + public async void SelectsTheXXXErrorMappingClassCorrectly(HttpStatusCode statusCode) + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(() => + { + var responseMessage = new HttpResponseMessage + { + StatusCode = statusCode, + Content = new StringContent("{}",Encoding.UTF8,"application/json") + }; + return responseMessage; + }); + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetObjectValue(It.IsAny>())) + .Returns(new MockError("A general error occured")); + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNode(It.IsAny(), It.IsAny())) + .Returns(mockParseNode.Object); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + UrlTemplate = "https://example.com" + }; + try + { + var errorMapping = new Dictionary>() + { + { "XXX", (parseNode) => new MockError("A general error occured")}, + }; + var response = await adapter.SendPrimitiveAsync(requestInfo, errorMapping); + Assert.Fail("Expected an ApiException to be thrown"); + } + catch(MockError mockError) + { + Assert.Equal((int)statusCode, mockError.ResponseStatusCode); + Assert.Equal("A general error occured", mockError.Message); + } + } + [InlineData(HttpStatusCode.BadGateway)]// 5XX + [Theory] + public async void ThrowsApiExceptionOnMissingMapping(HttpStatusCode statusCode) + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(() => + { + var responseMessage = new HttpResponseMessage + { + StatusCode = statusCode, + Content = new StringContent("{}", Encoding.UTF8, "application/json") + }; + return responseMessage; + }); + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetObjectValue(It.IsAny>())) + .Returns(new MockError("A general error occured: "+ statusCode.ToString())); + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNode(It.IsAny(), It.IsAny())) + .Returns(mockParseNode.Object); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + UrlTemplate = "https://example.com" + }; + try + { + var errorMapping = new Dictionary>() + { + { "4XX", (parseNode) => new MockError("A 4XX error occured") }//Only 4XX + }; + var response = await adapter.SendPrimitiveAsync(requestInfo, errorMapping); + Assert.Fail("Expected an ApiException to be thrown"); + } + catch(ApiException apiException) + { + Assert.Equal((int)statusCode, apiException.ResponseStatusCode); + Assert.Contains("The server returned an unexpected status code and no error factory is registered for this code", apiException.Message); + } + } } } diff --git a/src/HttpClientRequestAdapter.cs b/src/HttpClientRequestAdapter.cs index 74ed178..118ce46 100644 --- a/src/HttpClientRequestAdapter.cs +++ b/src/HttpClientRequestAdapter.cs @@ -70,7 +70,7 @@ public string? BaseUrl get => baseUrl; set => this.baseUrl = value?.TrimEnd('/'); } - private static readonly char[] charactersToDecodeForUriTemplate = new char[] { '$', '.', '-', '~' }; + private static readonly char[] charactersToDecodeForUriTemplate = ['$', '.', '-', '~']; private static readonly Regex queryParametersCleanupRegex = new(@"\{\?[^\}]+}", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline, TimeSpan.FromMilliseconds(100)); private Activity? startTracingSpan(RequestInformation requestInfo, string methodName) { @@ -393,7 +393,8 @@ private async Task ThrowIfFailedResponse(HttpResponseMessage response, Dictionar if(errorMapping == null || !errorMapping.TryGetValue(statusCodeAsString, out errorFactory) && !(statusCodeAsInt >= 400 && statusCodeAsInt < 500 && errorMapping.TryGetValue("4XX", out errorFactory)) && - !(statusCodeAsInt >= 500 && statusCodeAsInt < 600 && errorMapping.TryGetValue("5XX", out errorFactory))) + !(statusCodeAsInt >= 500 && statusCodeAsInt < 600 && errorMapping.TryGetValue("5XX", out errorFactory)) && + !errorMapping.TryGetValue("XXX", out errorFactory)) { activityForAttributes?.SetTag(ErrorMappingFoundAttributeName, false); throw new ApiException($"The server returned an unexpected status code and no error factory is registered for this code: {statusCodeAsString}") diff --git a/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj b/src/Microsoft.Kiota.Http.HttpClientLibrary.csproj index ce56b68..b473543 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.3.4 + 1.3.5 true