diff --git a/src/Microsoft.Graph.Core/Exceptions/ErrorConstants.cs b/src/Microsoft.Graph.Core/Exceptions/ErrorConstants.cs
index 86e6af8d5..e0b833a3a 100644
--- a/src/Microsoft.Graph.Core/Exceptions/ErrorConstants.cs
+++ b/src/Microsoft.Graph.Core/Exceptions/ErrorConstants.cs
@@ -8,82 +8,30 @@ internal static class ErrorConstants
{
internal static class Codes
{
- internal static string GeneralException = "generalException";
-
- internal static string InvalidRequest = "invalidRequest";
-
- internal static string ItemNotFound = "itemNotFound";
-
- internal static string NotAllowed = "notAllowed";
-
- internal static string Timeout = "timeout";
-
- internal static string TooManyRedirects = "tooManyRedirects";
-
- internal static string TooManyRetries = "tooManyRetries";
-
- internal static string MaximumValueExceeded = "MaximumValueExceeded";
-
- internal static string InvalidArgument = "invalidArgument";
-
- internal const string TemporarilyUnavailable = "temporarily_unavailable";
+ internal const string GeneralException = "generalException";
}
internal static class Messages
{
- internal static string AuthenticationProviderMissing = "Authentication provider is required before sending a request.";
-
- internal static string BaseUrlMissing = "Base URL cannot be null or empty.";
-
- internal static string InvalidTypeForDateConverter = "DateConverter can only serialize objects of type Date.";
-
- internal static string InvalidTypeForDateTimeOffsetConverter = "DateTimeOffsetConverter can only serialize objects of type DateTimeOffset.";
-
- internal static string LocationHeaderNotSetOnRedirect = "Location header not present in redirection response.";
-
- internal static string OverallTimeoutCannotBeSet = "Overall timeout cannot be set after the first request is sent.";
-
- internal static string RequestTimedOut = "The request timed out.";
-
- internal static string RequestUrlMissing = "Request URL is required to send a request.";
-
- internal static string TooManyRedirectsFormatString = "More than {0} redirects encountered while sending the request.";
-
- internal static string TooManyRetriesFormatString = "More than {0} retries encountered while sending the request.";
-
- internal static string UnableToCreateInstanceOfTypeFormatString = "Unable to create an instance of type {0}.";
-
- internal static string UnableToDeserializeDate = "Unable to deserialize the returned Date.";
-
- internal static string UnableToDeserializeDateTimeOffset = "Unable to deserialize the returned DateDateTimeOffset.";
-
- internal static string UnexpectedExceptionOnSend = "An error occurred sending the request.";
-
- internal static string UnexpectedExceptionResponse = "Unexpected exception returned from the service.";
-
- internal static string MaximumValueExceeded = "{0} exceeds the maximum value of {1}.";
-
- internal static string NullParameter = "{0} parameter cannot be null.";
-
- internal static string UnableToDeserializeContent = "Unable to deserialize content.";
+ internal const string MaximumValueExceeded = "{0} exceeds the maximum value of {1}.";
- internal static string InvalidDependsOnRequestId = "Corresponding batch request id not found for the specified dependsOn relation.";
+ internal const string NullParameter = "{0} parameter cannot be null.";
- internal static string ExpiredUploadSession = "Upload session expired. Upload cannot resume";
+ internal const string UnableToDeserializeContent = "Unable to deserialize content.";
- internal static string NoResponseForUpload = "No Response Received for upload.";
+ internal const string InvalidDependsOnRequestId = "Corresponding batch request id not found for the specified dependsOn relation.";
- internal static string NullValue = "{0} cannot be null.";
+ internal const string ExpiredUploadSession = "Upload session expired. Upload cannot resume";
- internal static string UnexpectedMsalException = "Unexpected exception returned from MSAL.";
+ internal const string NoResponseForUpload = "No Response Received for upload.";
- internal static string UnexpectedException = "Unexpected exception occured while authenticating the request.";
+ internal const string MissingRetryAfterHeader = "Missing retry after header.";
- internal static string MissingRetryAfterHeader = "Missing retry after header.";
+ internal const string PageIteratorRequestError = "Error occured when making a request with the page iterator. See inner exception for more details.";
- internal static string PageIteratorRequestError = "Error occured when making a request with the page iterator. See inner exception for more details.";
+ internal const string BatchRequestError = "Error occured when making the batch request. See inner exception for more details.";
- public static string InvalidProxyArgument = "Proxy cannot be set more once. Proxy can only be set on the proxy or defaultHttpHandler argument and not both.";
+ internal const string InvalidProxyArgument = "Proxy cannot be set more once. Proxy can only be set on the proxy or defaultHttpHandler argument and not both.";
}
}
}
diff --git a/src/Microsoft.Graph.Core/Extensions/IParseNodeExtensions.cs b/src/Microsoft.Graph.Core/Extensions/IParseNodeExtensions.cs
new file mode 100644
index 000000000..cc7615dc3
--- /dev/null
+++ b/src/Microsoft.Graph.Core/Extensions/IParseNodeExtensions.cs
@@ -0,0 +1,16 @@
+using Microsoft.Kiota.Abstractions.Serialization;
+
+namespace Microsoft.Graph;
+
+///
+/// Extension helpers for the
+///
+public static class ParseNodeExtensions
+{
+ internal static string GetErrorMessage(this IParseNode responseParseNode)
+ {
+ var errorParseNode = responseParseNode.GetChildNode("error");
+ // concatenate the error code and message
+ return $"{errorParseNode?.GetChildNode("code")?.GetStringValue()} : {errorParseNode?.GetChildNode("message")?.GetStringValue()}";
+ }
+}
diff --git a/src/Microsoft.Graph.Core/Requests/BatchRequestBuilder.cs b/src/Microsoft.Graph.Core/Requests/BatchRequestBuilder.cs
index ae342fb51..de4b8d7c1 100644
--- a/src/Microsoft.Graph.Core/Requests/BatchRequestBuilder.cs
+++ b/src/Microsoft.Graph.Core/Requests/BatchRequestBuilder.cs
@@ -7,10 +7,12 @@ namespace Microsoft.Graph.Core.Requests
using System;
using System.Collections.Generic;
using System.Net.Http;
+ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Serialization;
+ using Microsoft.Kiota.Serialization.Json;
///
/// The type BatchRequestBuilder
@@ -58,7 +60,9 @@ public async Task PostAsync(BatchRequestContent batchReque
var nativeResponseHandler = new NativeResponseHandler();
requestInfo.SetResponseHandler(nativeResponseHandler);
await this.RequestAdapter.SendNoContentAsync(requestInfo, cancellationToken: cancellationToken);
- return new BatchResponseContent(nativeResponseHandler.Value as HttpResponseMessage, errorMappings);
+ var httpResponseMessage = nativeResponseHandler.Value as HttpResponseMessage;
+ await ThrowIfFailedResponseAsync(httpResponseMessage, cancellationToken);
+ return new BatchResponseContent(httpResponseMessage, errorMappings);
}
///
@@ -99,5 +103,34 @@ public async Task ToPostRequestInformationAsync(BatchRequest
requestInfo.Headers.Add("Content-Type", CoreConstants.MimeTypeNames.Application.Json);
return requestInfo;
}
+
+ private static async Task ThrowIfFailedResponseAsync(HttpResponseMessage httpResponseMessage, CancellationToken cancellationToken)
+ {
+ if (httpResponseMessage.IsSuccessStatusCode) return;
+
+ if (httpResponseMessage is { Content.Headers.ContentType.MediaType: string contentTypeMediaType } && contentTypeMediaType.StartsWith(CoreConstants.MimeTypeNames.Application.Json, StringComparison.OrdinalIgnoreCase))
+ {
+#if NET5_0_OR_GREATER
+ using var responseContent = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+#else
+ using var responseContent = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false);
+#endif
+ using var document = await JsonDocument.ParseAsync(responseContent, cancellationToken: cancellationToken).ConfigureAwait(false);
+ var parsable = new JsonParseNode(document.RootElement);
+ throw new ServiceException(ErrorConstants.Messages.BatchRequestError, httpResponseMessage.Headers, (int)httpResponseMessage.StatusCode, new Exception(parsable.GetErrorMessage()));
+ }
+
+ var responseStringContent = string.Empty;
+ if (httpResponseMessage.Content != null)
+ {
+#if NET5_0_OR_GREATER
+ responseStringContent = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+#else
+ responseStringContent = await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
+#endif
+ }
+
+ throw new ServiceException(ErrorConstants.Messages.BatchRequestError, httpResponseMessage.Headers, (int)httpResponseMessage.StatusCode, responseStringContent);
+ }
}
}
diff --git a/src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs b/src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs
index bcb3bb9c1..b5f38ab8a 100644
--- a/src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs
+++ b/src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs
@@ -32,7 +32,9 @@ public class BatchResponseContent
public BatchResponseContent(HttpResponseMessage httpResponseMessage, Dictionary> errorMappings = null)
{
this.batchResponseMessage = httpResponseMessage ?? throw new ArgumentNullException(nameof(httpResponseMessage));
- this.apiErrorMappings = errorMappings ?? new();
+ this.apiErrorMappings = errorMappings ?? new Dictionary>(StringComparer.OrdinalIgnoreCase) {
+ {"XXX", (parsable) => new ServiceException(ErrorConstants.Messages.BatchRequestError, new Exception(parsable.GetErrorMessage())) }
+ };
}
///
diff --git a/src/Microsoft.Graph.Core/Tasks/PageIterator.cs b/src/Microsoft.Graph.Core/Tasks/PageIterator.cs
index f13bb116b..f0792368e 100644
--- a/src/Microsoft.Graph.Core/Tasks/PageIterator.cs
+++ b/src/Microsoft.Graph.Core/Tasks/PageIterator.cs
@@ -115,8 +115,7 @@ public static PageIterator CreatePageIterator(IRequest
_processPageItemCallback = callback,
_requestConfigurator = requestConfigurator,
_errorMapping = errorMapping ?? new Dictionary>(StringComparer.OrdinalIgnoreCase) {
- {"4XX", (parsable) => new ServiceException(ErrorConstants.Messages.PageIteratorRequestError,new Exception(GetErrorMessageFromParsable(parsable))) },
- {"5XX", (parsable) => new ServiceException(ErrorConstants.Messages.PageIteratorRequestError,new Exception(GetErrorMessageFromParsable(parsable))) }
+ {"XXX", (parsable) => new ServiceException(ErrorConstants.Messages.PageIteratorRequestError,new Exception(parsable.GetErrorMessage())) }
},
State = PagingState.NotStarted
};
@@ -172,15 +171,14 @@ public static PageIterator CreatePageIterator(IRequest
_asyncProcessPageItemCallback = asyncCallback,
_requestConfigurator = requestConfigurator,
_errorMapping = errorMapping ?? new Dictionary>(StringComparer.OrdinalIgnoreCase) {
- {"4XX", (parsable) => new ServiceException(ErrorConstants.Messages.PageIteratorRequestError,new Exception(GetErrorMessageFromParsable(parsable))) },
- {"5XX", (parsable) =>new ServiceException(ErrorConstants.Messages.PageIteratorRequestError,new Exception(GetErrorMessageFromParsable(parsable))) },
+ {"XXX", (parsable) => new ServiceException(ErrorConstants.Messages.PageIteratorRequestError,new Exception(parsable.GetErrorMessage())) }
},
State = PagingState.NotStarted
};
}
///
- /// Iterate across the content of a a single results page with the callback.
+ /// Iterate across the content of a single results page with the callback.
///
/// A boolean value that indicates whether the callback cancelled
/// iterating across the page results or whether there are more pages to page.
@@ -393,13 +391,6 @@ private static string ExtractNextLinkFromParsable(TCollectionPage parsableCollec
// the next link property may not be defined in the response schema so we also check its presence in the additional data bag
return parsableCollection.AdditionalData.TryGetValue(CoreConstants.OdataInstanceAnnotations.NextLink, out var nextLink) ? nextLink.ToString() : string.Empty;
}
-
- private static string GetErrorMessageFromParsable(IParseNode responseParseNode)
- {
- var errorParseNode = responseParseNode.GetChildNode("error");
- // concatenate the error code and message
- return $"{errorParseNode?.GetChildNode("code")?.GetStringValue()} : {errorParseNode?.GetChildNode("message")?.GetStringValue()}";
- }
}
///
diff --git a/tests/Microsoft.Graph.DotnetCore.Core.Test/Requests/BatchRequestBuilderTests.cs b/tests/Microsoft.Graph.DotnetCore.Core.Test/Requests/BatchRequestBuilderTests.cs
index 2786b5f0f..e074512c1 100644
--- a/tests/Microsoft.Graph.DotnetCore.Core.Test/Requests/BatchRequestBuilderTests.cs
+++ b/tests/Microsoft.Graph.DotnetCore.Core.Test/Requests/BatchRequestBuilderTests.cs
@@ -6,10 +6,15 @@ namespace Microsoft.Graph.DotnetCore.Core.Test.Requests
{
using System.Collections.Generic;
using System.Net.Http;
+ using System.Text;
+ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph.Core.Requests;
using Microsoft.Graph.DotnetCore.Core.Test.Mocks;
+ using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
+ using Microsoft.Kiota.Abstractions.Serialization;
+ using Moq;
using Xunit;
public class BatchRequestBuilderTests
@@ -42,5 +47,119 @@ public async Task BatchRequestBuilderAsync()
Assert.Equal("{+baseurl}/$batch", requestInformation.UrlTemplate);
Assert.Equal(baseClient.RequestAdapter, batchRequestBuilder.RequestAdapter);
}
+
+
+ [Fact]
+ public async Task BatchRequestBuilderPostAsyncHandlesDoesNotThrowExceptionAsync()
+ {
+ // Arrange
+ var requestAdapter = new Mock();
+ IBaseClient baseClient = new BaseClient(requestAdapter.Object);
+
+ var errorResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.OK)
+ {
+ Content = new StringContent("{}", Encoding.UTF8, "application/json"),//dummy content
+ };
+ requestAdapter
+ .Setup(requestAdapter => requestAdapter.SendNoContentAsync(It.IsAny(), It.IsAny>>(), It.IsAny()))
+ .Callback((RequestInformation requestInfo, Dictionary> errorMapping, CancellationToken cancellationToken) => ((NativeResponseHandler)requestInfo.GetRequestOption().ResponseHandler).Value = errorResponseMessage)
+ .Returns(Task.FromResult(0));
+
+ // Act
+ var batchRequestBuilder = new BatchRequestBuilder(baseClient.RequestAdapter);
+
+ // 4. Create batch request content to be sent out
+ // 4.1 Create HttpRequestMessages for the content
+ HttpRequestMessage httpRequestMessage1 = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/");
+
+ // 4.2 Create batch request steps with request ids.
+ BatchRequestStep requestStep1 = new BatchRequestStep("1", httpRequestMessage1);
+
+ // 4.3 Add batch request steps to BatchRequestContent.
+#pragma warning disable CS0618 // Type or member is obsolete use the BatchRequestContentCollection for making batch requests
+ BatchRequestContent batchRequestContent = new BatchRequestContent(baseClient, requestStep1);
+#pragma warning restore CS0618 // Type or member is obsolete use the BatchRequestContentCollection for making batch requests
+ var responseContent = await batchRequestBuilder.PostAsync(batchRequestContent);
+
+ // Assert
+ Assert.NotNull(responseContent);
+ }
+
+ [Fact]
+ public async Task BatchRequestBuilderPostAsyncHandlesNonSuccessStatusWithJsonResponseAsync()
+ {
+ // Arrange
+ var requestAdapter = new Mock();
+ IBaseClient baseClient = new BaseClient(requestAdapter.Object);
+
+ var errorResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized)
+ {
+ Content = new StringContent("{\"error\": {\"code\": \"20117\",\"message\": \"An item with this name already exists in this location.\",\"innerError\":{\"request-id\": \"nothing1b13-45cd-new-92be873c5781\",\"date\": \"2019-03-22T23:17:50\"}}}", Encoding.UTF8, "application/json"),
+ };
+ requestAdapter
+ .Setup(requestAdapter => requestAdapter.SendNoContentAsync(It.IsAny(), It.IsAny>>(), It.IsAny()))
+ .Callback((RequestInformation requestInfo, Dictionary> errorMapping, CancellationToken cancellationToken) => ((NativeResponseHandler)requestInfo.GetRequestOption().ResponseHandler).Value = errorResponseMessage)
+ .Returns(Task.FromResult(0));
+
+ // Act
+ var batchRequestBuilder = new BatchRequestBuilder(baseClient.RequestAdapter);
+
+ // 4. Create batch request content to be sent out
+ // 4.1 Create HttpRequestMessages for the content
+ HttpRequestMessage httpRequestMessage1 = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/");
+
+ // 4.2 Create batch request steps with request ids.
+ BatchRequestStep requestStep1 = new BatchRequestStep("1", httpRequestMessage1);
+
+ // 4.3 Add batch request steps to BatchRequestContent.
+#pragma warning disable CS0618 // Type or member is obsolete use the BatchRequestContentCollection for making batch requests
+ BatchRequestContent batchRequestContent = new BatchRequestContent(baseClient, requestStep1);
+#pragma warning restore CS0618 // Type or member is obsolete use the BatchRequestContentCollection for making batch requests
+ var serviceException = await Assert.ThrowsAsync(async () => await batchRequestBuilder.PostAsync(batchRequestContent));
+
+ // Assert
+ Assert.Equal(ErrorConstants.Messages.BatchRequestError, serviceException.Message);
+ Assert.Equal(401, serviceException.ResponseStatusCode);
+ Assert.NotNull(serviceException.InnerException);
+ Assert.Equal("20117 : An item with this name already exists in this location.", serviceException.InnerException.Message);
+ }
+
+ [Fact]
+ public async Task BatchRequestBuilderPostAsyncHandlesNonSuccessStatusWithNonJsonResponseAsync()
+ {
+ // Arrange
+ var requestAdapter = new Mock();
+ IBaseClient baseClient = new BaseClient(requestAdapter.Object);
+
+ var errorResponseMessage = new HttpResponseMessage(System.Net.HttpStatusCode.Conflict)
+ {
+ Content = new StringContent("This is random html", Encoding.UTF8, "text/plain"),
+ };
+ requestAdapter
+ .Setup(requestAdapter => requestAdapter.SendNoContentAsync(It.IsAny(), It.IsAny>>(), It.IsAny()))
+ .Callback((RequestInformation requestInfo, Dictionary> errorMapping, CancellationToken cancellationToken) => ((NativeResponseHandler)requestInfo.GetRequestOption().ResponseHandler).Value = errorResponseMessage)
+ .Returns(Task.FromResult(0));
+
+ // Act
+ var batchRequestBuilder = new BatchRequestBuilder(baseClient.RequestAdapter);
+
+ // 4. Create batch request content to be sent out
+ // 4.1 Create HttpRequestMessages for the content
+ HttpRequestMessage httpRequestMessage1 = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/");
+
+ // 4.2 Create batch request steps with request ids.
+ BatchRequestStep requestStep1 = new BatchRequestStep("1", httpRequestMessage1);
+
+ // 4.3 Add batch request steps to BatchRequestContent.
+#pragma warning disable CS0618 // Type or member is obsolete use the BatchRequestContentCollection for making batch requests
+ BatchRequestContent batchRequestContent = new BatchRequestContent(baseClient, requestStep1);
+#pragma warning restore CS0618 // Type or member is obsolete use the BatchRequestContentCollection for making batch requests
+ var serviceException = await Assert.ThrowsAsync(async () => await batchRequestBuilder.PostAsync(batchRequestContent));
+
+ // Assert
+ Assert.Equal(ErrorConstants.Messages.BatchRequestError, serviceException.Message);
+ Assert.Equal(409, serviceException.ResponseStatusCode);
+ Assert.Equal("This is random html", serviceException.RawResponseBody);
+ }
}
}