Skip to content
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

Throw IotHubClientException for file uploading with invalid correlation id #3159

Merged
merged 13 commits into from
Mar 20, 2023
Merged
6 changes: 4 additions & 2 deletions e2e/test/iothub/device/FileUploadE2ETests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,12 @@ private async Task UploadFileGranularAsync(Stream source, string filename, IotHu
{
// Gateway V1 flow
}
catch (IotHubClientException ex) when (ex.ErrorCode is IotHubClientErrorCode.BadRequest)
catch (IotHubClientException ex) when (ex.ErrorCode is IotHubClientErrorCode.IotHubFormatError)
{
// Gateway V2 flow
ex.Message.Should().Contain("400006");
ex.Message.Should().Contain("Cannot decode correlation_id");
ex.TrackingId.Should().NotBe(string.Empty);
ex.IsTransient.Should().BeFalse();
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions iothub/device/src/Exceptions/ErrorPayload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Newtonsoft.Json;

namespace Microsoft.Azure.Devices.Client
{
/// <summary>
/// A class used as a model to deserialize one schema type of errors received from IoT hub.
/// </summary>
internal sealed class ErrorPayload
{
[JsonProperty("errorCode")]
internal string ErrorCode { get; set; }

[JsonProperty("trackingId")]
internal string TrackingId { get; set; }

[JsonProperty("message")]
internal string Message { get; set; }

[JsonProperty("timestampUtc")]
internal string OccurredOnUtc { get; set; }
}
}
65 changes: 59 additions & 6 deletions iothub/device/src/Exceptions/ExceptionHandlingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Microsoft.Azure.Devices.Client
{
Expand All @@ -22,19 +24,16 @@ internal static IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Excep
CreateMessageWhenDeviceNotFound(await GetExceptionMessageAsync(response).ConfigureAwait(false)),
IotHubClientErrorCode.DeviceNotFound)
},
{
{
HttpStatusCode.NotFound,
async (response) =>
new IotHubClientException(
CreateMessageWhenDeviceNotFound(await GetExceptionMessageAsync(response).ConfigureAwait(false)),
IotHubClientErrorCode.DeviceNotFound)
},
{
{
HttpStatusCode.BadRequest,
async (response) =>
new IotHubClientException(
await GetExceptionMessageAsync(response).ConfigureAwait(false),
IotHubClientErrorCode.BadRequest)
async (response) => await GenerateIotHubClientExceptionAsync(response)
},
{
HttpStatusCode.Unauthorized,
Expand Down Expand Up @@ -94,9 +93,63 @@ internal static Task<string> GetExceptionMessageAsync(HttpResponseMessage respon
return response.Content.ReadAsStringAsync();
}

internal static async Task<Tuple<string, IotHubClientErrorCode>> GetErrorCodeAndTrackingIdAsync(HttpResponseMessage response)
{
string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
ErrorPayload responseMessage = null;

try
{
IotHubExceptionResult result = JsonConvert.DeserializeObject<IotHubExceptionResult>(responseBody);
responseMessage = JsonConvert.DeserializeObject<ErrorPayload>(result.Message);
}
catch (JsonException ex)
{
if (Logging.IsEnabled)
Logging.Error(
nameof(GetErrorCodeAndTrackingIdAsync),
$"Failed to parse response content JSON: {ex.Message}. Message body: '{responseBody}.'");
}

if (responseMessage != null)
{
string trackingId = string.Empty;
if (responseMessage.TrackingId != null)
{
trackingId = responseMessage.TrackingId;
}

if (responseMessage.ErrorCode != null)
{
if (int.TryParse(responseMessage.ErrorCode, NumberStyles.Any, CultureInfo.InvariantCulture, out int errorCodeInt))
{
return Tuple.Create(trackingId, (IotHubClientErrorCode)errorCodeInt);
}
}
}

if (Logging.IsEnabled)
Logging.Error(
nameof(GetErrorCodeAndTrackingIdAsync),
$"Failed to derive any error code from the response message: {responseBody}");

return Tuple.Create(string.Empty, IotHubClientErrorCode.Unknown);
}

private static string CreateMessageWhenDeviceNotFound(string deviceId)
{
return "Device {0} not registered".FormatInvariant(deviceId);
}

private static async Task<IotHubClientException> GenerateIotHubClientExceptionAsync(HttpResponseMessage response)
{
string message = await GetExceptionMessageAsync(response).ConfigureAwait(false);
Tuple<string, IotHubClientErrorCode> pair = await GetErrorCodeAndTrackingIdAsync(response);
brycewang-microsoft marked this conversation as resolved.
Show resolved Hide resolved

return new IotHubClientException(message, pair.Item2)
{
TrackingId = pair.Item1,
};
}
}
}
6 changes: 0 additions & 6 deletions iothub/device/src/Exceptions/IotHubClientErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,6 @@ public enum IotHubClientErrorCode
/// </remarks>
TlsAuthenticationError,

/// <summary>
/// The request failed because the server cannot process it due to a client-side error. Check the error
/// message for more details.
/// </summary>
BadRequest = 400,

/// <summary>
/// The request failed because the operation timed out. This can be caused by underlying network issues
/// or by the server being too busy to handle the request.
Expand Down
19 changes: 19 additions & 0 deletions iothub/device/src/IotHubExceptionResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Newtonsoft.Json;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Azure.Devices.Client
{
/// <summary>
/// A class used as a model to deserialize error response object received from IoT hub.
/// </summary>
internal sealed class IotHubExceptionResult
{
[SuppressMessage("Usage", "CA1507: Use nameof in place of string literal 'Message'",
Justification = "This JsonProperty annotation depends on service-defined contract (name) and is independent of the property name selected by the SDK.")]
[JsonProperty("Message")]
internal string Message { get; set; }
}
}