Skip to content

Commit

Permalink
Last of error codes documentation (#2110) (#2074)
Browse files Browse the repository at this point in the history
refactor(iot-service): Add API example for DeviceAlreadyExists error (#2101)

fix(iot-service): Hide unreferenced error code (#2094)

a few misc errors (#2092)

* a few misc errors

* fixup

Document ArgumentNull (#2091)

Document InvalidOperation and ArgumentInvalid (#2089)

Updating exception details for IotHubThrottledException (#2088)

refactor(iot-service): Add comments for service returned error codes (#2083)

Fix build error (#2087)

Updating exception details for IotHubSuspendedException (#2086)

Updating exception details for IotHubCommunicationException (#2085)

Fix InvalidProtocolVersion documentation. (#2084)

More codes doc'd and code cleanup (#2082)

* More codes doc'd, code clean up

* Update license

* Random is static

* License header

* More codes doc'd, code clean up

More updates to exceptions thrown (#2081)

More error code doc comments

Remove ref to ExponentialBackoff

[Error Codes] Update ThrottleBacklogLimitExceeded error code (#2078)

[Error Codes] Update PreconditionFailed error description (#2077)

* [Error Codes] Update PreconditionFailed error description

* Remove space

[Error Codes] Updated MessageTooLarge (#2073)

* [Error Codes] Updated MessageTooLarge

* Removed en-us where seen

* Remove en-us

* REmove en-us

[Error Codes] Updating UnauthorizedException (#2072)

* [Error Codes] Updating UnauthorizedException

* Remove en-us

* Remove en-us

* Update UnauthorizedException.cs

* Update UnauthorizedException.cs

[Error Codes] Updated ServerErrorException (#2071)

* [Error Codes] Updated ServerErrorException

* Update ServerErrorException.cs

* Update ServerErrorException.cs

* Update ServerErrorException.cs

* Update ServerErrorException.cs

* Update ServerErrorException.cs

[Error Codes] Updated QuotaExceededException (#2070)

* Updated QuotaExceededException classes

* Update QuotaExceededException.cs

* Update QuotaExceededException.cs

Updated ServerBusyException (#2069)

[Error Codes] Update PartitionNotFound error code (#2075)

* [Error Codes] Update PartitionNotFound error code

* remove double lines

* remove double lines

Obsolete error codes that are not throw by the service (#2079)

Fix deprecation messages.

Notes for and deprecation of BlobContainerValidationError, BulkRegistryOperationFailure, and JobQuotaExceeded

Document errors and remove unreferenced file
  • Loading branch information
drwill-ms committed Jul 19, 2021
1 parent c1b707c commit 2d2f1eb
Show file tree
Hide file tree
Showing 39 changed files with 557 additions and 483 deletions.
221 changes: 129 additions & 92 deletions common/src/service/ExceptionHandlingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
Expand All @@ -17,97 +18,88 @@ namespace Microsoft.Azure.Devices
{
internal class ExceptionHandlingHelper
{
public static IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> GetDefaultErrorMapping()
private static readonly IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> s_mappings =
new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>
{
var mappings = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>
{
{
HttpStatusCode.NoContent,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.NotFound,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.Conflict,
async (response) => new DeviceAlreadyExistsException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{ HttpStatusCode.BadRequest, async (response) => new ArgumentException(
message: await GetExceptionMessageAsync(response).ConfigureAwait(false)) },

{
HttpStatusCode.Unauthorized,
async (response) => new UnauthorizedException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.Forbidden,
async (response) => new QuotaExceededException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.PreconditionFailed,
async (response) => new DeviceMessageLockLostException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.RequestEntityTooLarge,
async (response) => new MessageTooLargeException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.InternalServerError,
async (response) => new ServerErrorException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
HttpStatusCode.ServiceUnavailable,
async (response) => new ServerBusyException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},

{
(HttpStatusCode)429,
async (response) => new ThrottlingException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
}
};
HttpStatusCode.NoContent,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.NotFound,
async (response) => new DeviceNotFoundException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.Conflict,
async (response) => new DeviceAlreadyExistsException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.BadRequest, async (response) => new ArgumentException(
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.Unauthorized,
async (response) => new UnauthorizedException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.Forbidden,
async (response) => new QuotaExceededException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.PreconditionFailed,
async (response) => new DeviceMessageLockLostException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.RequestEntityTooLarge,
async (response) => new MessageTooLargeException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.InternalServerError,
async (response) => new ServerErrorException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
HttpStatusCode.ServiceUnavailable,
async (response) => new ServerBusyException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
},
{
(HttpStatusCode)429,
async (response) => new ThrottlingException(
code: await GetExceptionCodeAsync(response).ConfigureAwait(false),
message: await GetExceptionMessageAsync(response).ConfigureAwait(false))
}
};

return mappings;
}
public static IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> GetDefaultErrorMapping() =>
s_mappings;

public static Task<string> GetExceptionMessageAsync(HttpResponseMessage response)
{
return response.Content.ReadAsStringAsync();
}

/// <summary>
/// Get the fully qualified error code from the http response message, if exists
/// Get the fully-qualified error code from the HTTP response message, if exists.
/// </summary>
/// <param name="response">The http response message</param>
/// <returns>The fully qualified error code, or the response status code if no error code was provided.</returns>
/// <param name="response">The HTTP response message</param>
/// <returns>The fully-qualified error code, or the response status code, if no error code was provided.</returns>
public static async Task<ErrorCode> GetExceptionCodeAsync(HttpResponseMessage response)
{
// First we will attempt to retrieve the error code from the response content.
Expand All @@ -121,22 +113,67 @@ public static async Task<ErrorCode> GetExceptionCodeAsync(HttpResponseMessage re
// to 'error code' enum mapping, the SDK will check if both values are a match. If so, the SDK will populate the exception with the proper Code. In the case where
// there is a mismatch between the error code and the description, the SDK returns ErrorCode.InvalidErrorCode and log a warning.

int errorCode;
int errorCodeValue = (int)ErrorCode.InvalidErrorCode;
try
{
IoTHubExceptionResult responseContent = JsonConvert
.DeserializeObject<IoTHubExceptionResult>(responseContentStr);
Dictionary<string, string> messageFields = JsonConvert
.DeserializeObject<Dictionary<string, string>>(responseContent.Message);
IoTHubExceptionResult responseContent = JsonConvert.DeserializeObject<IoTHubExceptionResult>(responseContentStr);

if (messageFields != null
&& messageFields.TryGetValue(CommonConstants.ErrorCode, out string errorCodeObj))
try
{
errorCode = Convert.ToInt32(errorCodeObj, CultureInfo.InvariantCulture);
Dictionary<string, string> messageFields = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseContent.Message);

if (messageFields != null
&& messageFields.TryGetValue(CommonConstants.ErrorCode, out string errorCodeObj))
{
// The result of TryParse is not being tracked since errorCodeValue has already been initialized to a default value of InvalidErrorCode.
_ = int.TryParse(errorCodeObj, NumberStyles.Any, CultureInfo.InvariantCulture, out errorCodeValue);
}
}
else
catch (JsonReaderException ex)
{
return ErrorCode.InvalidErrorCode;
if (Logging.IsEnabled)
Logging.Error(null, $"Failed to deserialize error message into a dictionary: {ex}. Message body: '{responseContentStr}.'");

// In some scenarios, the error response string is a ';' delimited string with the service-returned error code.
const char errorFieldsDelimiter = ';';
string[] messageFields = responseContent.Message?.Split(errorFieldsDelimiter);

if (messageFields != null)
{
foreach (string messageField in messageFields)
{
#if NET451 || NET472 || NETSTANDARD2_0
if (messageField.IndexOf(CommonConstants.ErrorCode, StringComparison.OrdinalIgnoreCase) >= 0)
#else
if (messageField.Contains(CommonConstants.ErrorCode, StringComparison.OrdinalIgnoreCase))
#endif
{
const char errorCodeDelimiter = ':';

#if NET451 || NET472 || NETSTANDARD2_0
if (messageField.IndexOf(errorCodeDelimiter) >= 0)
#else
if (messageField.Contains(errorCodeDelimiter))
#endif
{
string[] errorCodeFields = messageField.Split(errorCodeDelimiter);
if (Enum.TryParse(errorCodeFields[1], out ErrorCode errorCode))
{
errorCodeValue = (int)errorCode;
}
}
}
break;
}
}
else
{
if (Logging.IsEnabled)
Logging.Error(null, $"Failed to deserialize error message into a dictionary and could not parse ';' delimited string either: {ex}." +
$" Message body: '{responseContentStr}.'");

return ErrorCode.InvalidErrorCode;
}
}
}
catch (JsonReaderException ex)
Expand All @@ -152,15 +189,15 @@ public static async Task<ErrorCode> GetExceptionCodeAsync(HttpResponseMessage re
if (headerErrorCodeString != null
&& Enum.TryParse(headerErrorCodeString, out ErrorCode headerErrorCode))
{
if ((int)headerErrorCode == errorCode)
if ((int)headerErrorCode == errorCodeValue)
{
// We have a match. Therefore, return the proper error code.
return headerErrorCode;
}

if (Logging.IsEnabled)
Logging.Error(null, $"There is a mismatch between the error code retrieved from the response content and the response header." +
$"Content error code: {errorCode}. Header error code description: {(int)headerErrorCode}.");
$"Content error code: {errorCodeValue}. Header error code description: {(int)headerErrorCode}.");
}

return ErrorCode.InvalidErrorCode;
Expand Down
5 changes: 2 additions & 3 deletions common/src/service/HttpClientHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ internal sealed class HttpClientHelper : IHttpClientHelper
public HttpClientHelper(
Uri baseAddress,
IAuthorizationHeaderProvider authenticationHeaderProvider,
IDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> defaultErrorMapping,
IReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> defaultErrorMapping,
TimeSpan timeout,
IWebProxy customHttpProxy,
int connectionLeaseTimeoutMilliseconds)
{
_baseAddress = baseAddress;
_authenticationHeaderProvider = authenticationHeaderProvider;
_defaultErrorMapping = new ReadOnlyDictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>(defaultErrorMapping);
_defaultErrorMapping = defaultErrorMapping;
_defaultOperationTimeout = timeout;

// We need two types of HttpClients, one with our default operation timeout, and one without. The one without will rely on
Expand Down Expand Up @@ -924,7 +924,6 @@ internal static HttpMessageHandler CreateDefaultHttpMessageHandler(IWebProxy web
#endif
#pragma warning restore CA2000 // Dispose objects before losing scope


if (webProxy != DefaultWebProxySettings.Instance)
{
httpMessageHandler.UseProxy = webProxy != null;
Expand Down
2 changes: 1 addition & 1 deletion e2e/test/prerequisites/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ docker run -d --restart unless-stopped --name azure-iot-tpmsim -p 127.0.0.1:2321

Alternatives:

- Stand-alone executable for Windows: https://www.microsoft.com/en-us/download/details.aspx?id=52507
- Stand-alone executable for Windows: https://www.microsoft.com/download/details.aspx?id=52507

### Proxy Server

Expand Down
65 changes: 0 additions & 65 deletions iothub/device/src/Common/ErrorCode.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
namespace Microsoft.Azure.Devices.Client.Exceptions
{
/// <summary>
/// The exception that is thrown when an attempt to enqueue a message fails because the message queue for the device is already full.
/// This exception actually corresponds to IoTHubQuotaExceeded. For more information on what causes this error
/// and steps to resolve, see <see href="https://docs.microsoft.com/azure/iot-hub/iot-hub-troubleshoot-error-403002-iothubquotaexceeded"/>.
/// The exception type has not been changed to avoid breaking changes but the inner exception has the correct exception type.
/// </summary>
[Serializable]
public sealed class DeviceMaximumQueueDepthExceededException : IotHubException
Expand Down

0 comments on commit 2d2f1eb

Please sign in to comment.