Skip to content

fix: resolved error handling of larger batch request message #916

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 10, 2024
74 changes: 11 additions & 63 deletions src/Microsoft.Graph.Core/Exceptions/ErrorConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
}
}
}
16 changes: 16 additions & 0 deletions src/Microsoft.Graph.Core/Extensions/IParseNodeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.Kiota.Abstractions.Serialization;

namespace Microsoft.Graph;

/// <summary>
/// Extension helpers for the <see cref="IParseNode"/>
/// </summary>
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()}";
}
}
35 changes: 34 additions & 1 deletion src/Microsoft.Graph.Core/Requests/BatchRequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// The type BatchRequestBuilder
Expand Down Expand Up @@ -58,7 +60,9 @@ public async Task<BatchResponseContent> 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);
}

/// <summary>
Expand Down Expand Up @@ -99,5 +103,34 @@ public async Task<RequestInformation> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
public BatchResponseContent(HttpResponseMessage httpResponseMessage, Dictionary<string, ParsableFactory<IParsable>> errorMappings = null)
{
this.batchResponseMessage = httpResponseMessage ?? throw new ArgumentNullException(nameof(httpResponseMessage));
this.apiErrorMappings = errorMappings ?? new();
this.apiErrorMappings = errorMappings ?? new Dictionary<string, ParsableFactory<IParsable>>(StringComparer.OrdinalIgnoreCase) {
{"XXX", (parsable) => new ServiceException(ErrorConstants.Messages.BatchRequestError, new Exception(parsable.GetErrorMessage())) }
};
}

/// <summary>
Expand Down Expand Up @@ -88,7 +90,7 @@

if (jBatchResponseObject.RootElement.TryGetProperty(CoreConstants.BatchRequest.Responses, out JsonElement jResponses) && jResponses.ValueKind == JsonValueKind.Array)
{
foreach (var element in jResponses.EnumerateArray())

Check warning on line 93 in src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs

View workflow job for this annotation

GitHub Actions / Build

Loops should be simplified using the "Where" LINQ method

Check warning on line 93 in src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs

View workflow job for this annotation

GitHub Actions / Build

Loops should be simplified using the "Where" LINQ method
{
if (element.GetProperty(CoreConstants.BatchRequest.Id).GetString().Equals(requestId))
{
Expand Down Expand Up @@ -139,7 +141,7 @@
/// Gets the @NextLink of a batch response.
/// </summary>
/// <returns></returns>
[Obsolete("This method is deprecated as a batch response does not contain a next link", true)]

Check warning on line 144 in src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs

View workflow job for this annotation

GitHub Actions / Build

Do not forget to remove this deprecated code someday.

Check warning on line 144 in src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs

View workflow job for this annotation

GitHub Actions / Build

Do not forget to remove this deprecated code someday.
public async Task<string> GetNextLinkAsync()
{
jBatchResponseObject = jBatchResponseObject ?? await GetBatchResponseContentAsync().ConfigureAwait(false);
Expand Down Expand Up @@ -169,7 +171,7 @@
/// </summary>
/// <param name="jResponseItem">A single batch response item of type <see cref="JsonElement"/>.</param>
/// <returns>A single batch response as a <see cref="HttpResponseMessage"/>.</returns>
private HttpResponseMessage GetResponseMessageFromJObject(JsonElement jResponseItem)

Check warning on line 174 in src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'GetResponseMessageFromJObject' a static method.
{
HttpResponseMessage responseMessage = new HttpResponseMessage();

Expand Down Expand Up @@ -201,7 +203,7 @@
}


private HttpStatusCode GetStatusCodeFromJObject(JsonElement jResponseItem)

Check warning on line 206 in src/Microsoft.Graph.Core/Requests/Content/BatchResponseContent.cs

View workflow job for this annotation

GitHub Actions / Build

Make 'GetStatusCodeFromJObject' a static method.
{
if (jResponseItem.TryGetProperty(CoreConstants.BatchRequest.Status, out JsonElement status))
{
Expand Down
15 changes: 3 additions & 12 deletions src/Microsoft.Graph.Core/Tasks/PageIterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
if (requestAdapter == null)
throw new ArgumentNullException(nameof(requestAdapter));

if (page == null)

Check warning on line 99 in src/Microsoft.Graph.Core/Tasks/PageIterator.cs

View workflow job for this annotation

GitHub Actions / Build

Use a comparison to 'default(TCollectionPage)' instead or add a constraint to 'TCollectionPage' so that it can't be a value type.

Check warning on line 99 in src/Microsoft.Graph.Core/Tasks/PageIterator.cs

View workflow job for this annotation

GitHub Actions / Build

Use a comparison to 'default(TCollectionPage)' instead or add a constraint to 'TCollectionPage' so that it can't be a value type.
throw new ArgumentNullException(nameof(page));

if (callback == null)
Expand All @@ -115,8 +115,7 @@
_processPageItemCallback = callback,
_requestConfigurator = requestConfigurator,
_errorMapping = errorMapping ?? new Dictionary<string, ParsableFactory<IParsable>>(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
};
Expand Down Expand Up @@ -153,7 +152,7 @@
if (requestAdapter == null)
throw new ArgumentNullException(nameof(requestAdapter));

if (page == null)

Check warning on line 155 in src/Microsoft.Graph.Core/Tasks/PageIterator.cs

View workflow job for this annotation

GitHub Actions / Build

Use a comparison to 'default(TCollectionPage)' instead or add a constraint to 'TCollectionPage' so that it can't be a value type.

Check warning on line 155 in src/Microsoft.Graph.Core/Tasks/PageIterator.cs

View workflow job for this annotation

GitHub Actions / Build

Use a comparison to 'default(TCollectionPage)' instead or add a constraint to 'TCollectionPage' so that it can't be a value type.
throw new ArgumentNullException(nameof(page));

if (asyncCallback == null)
Expand All @@ -172,15 +171,14 @@
_asyncProcessPageItemCallback = asyncCallback,
_requestConfigurator = requestConfigurator,
_errorMapping = errorMapping ?? new Dictionary<string, ParsableFactory<IParsable>>(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
};
}

/// <summary>
/// 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.
/// </summary>
/// <returns>A boolean value that indicates whether the callback cancelled
/// iterating across the page results or whether there are more pages to page.
Expand Down Expand Up @@ -393,13 +391,6 @@
// 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()}";
}
}

/// <summary>
Expand Down
Loading