-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Customized FHIR service configuration exceptions (#150)
* Added FHIR service configuration exceptions. * Renamed FhirServerError to FhirServiceError for consistency with product name. * Updated comment. * Localized exception messages. * Added unit tests. * Updated where to catch exception for FHIR service authorization failure. * Addressed review comment. * Addressed review comments. * Updated validation of 'ErrorSource' metric dimension. * Removed underscores in error names for consistency with other error names and updated redundant string interpolations. * Nit change to error message when FHIR service doesn't exist. * Updated unit test as per PR comment. * Re-throw exception in case it's being retried on.
- Loading branch information
Showing
14 changed files
with
743 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirServiceValidator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
using System; | ||
using EnsureThat; | ||
using Hl7.Fhir.Rest; | ||
using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions; | ||
using Microsoft.Health.Logging.Telemetry; | ||
|
||
namespace Microsoft.Health.Extensions.Fhir | ||
{ | ||
public static class FhirServiceValidator | ||
{ | ||
public static bool ValidateFhirService(FhirClient client, ITelemetryLogger logger) | ||
{ | ||
EnsureArg.IsNotNull(client, nameof(client)); | ||
|
||
try | ||
{ | ||
client.CapabilityStatement(SummaryType.True); | ||
return true; | ||
} | ||
catch (Exception exception) | ||
{ | ||
FhirServiceExceptionProcessor.ProcessException(exception, logger); | ||
return false; | ||
} | ||
} | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceErrorCode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
namespace Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions | ||
{ | ||
public enum FhirServiceErrorCode | ||
{ | ||
/// <summary> | ||
/// Error code that categorizes invalid configurations (e.g. invalid FHIR service URL) | ||
/// </summary> | ||
ConfigurationError, | ||
|
||
/// <summary> | ||
/// Error code that categorizes authorization errors (e.g. missing role with permission to write FHIR data) | ||
/// </summary> | ||
AuthorizationError, | ||
|
||
/// <summary> | ||
/// Error code that categorizes invalid arguments (i.e. exceptions encountered of type ArgumentException), which may occur when FhirClient's endpoint is validated | ||
/// </summary> | ||
ArgumentError, | ||
|
||
/// <summary> | ||
/// Error code that categorizes HTTP request exceptions (i.e. exceptions encountered of type HttpRequestException) | ||
/// </summary> | ||
HttpRequestError, | ||
|
||
/// <summary> | ||
/// Error code that categorizes MSAL.NET exceptions (i.e. exceptions encountered of type MsalServiceException) | ||
/// </summary> | ||
MsalServiceError, | ||
|
||
/// <summary> | ||
/// Error code that categorizes all other generic exceptions | ||
/// </summary> | ||
GeneralError, | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
...Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Net; | ||
using System.Net.Http; | ||
using EnsureThat; | ||
using Hl7.Fhir.Rest; | ||
using Microsoft.Health.Common.Telemetry; | ||
using Microsoft.Health.Extensions.Fhir.Resources; | ||
using Microsoft.Health.Extensions.Fhir.Telemetry.Metrics; | ||
using Microsoft.Health.Logging.Telemetry; | ||
using Microsoft.Identity.Client; | ||
|
||
namespace Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions | ||
{ | ||
public static class FhirServiceExceptionProcessor | ||
{ | ||
private static readonly IExceptionTelemetryProcessor _exceptionTelemetryProcessor = new ExceptionTelemetryProcessor(); | ||
|
||
public static void ProcessException(Exception exception, ITelemetryLogger logger) | ||
{ | ||
EnsureArg.IsNotNull(logger, nameof(logger)); | ||
|
||
var (customException, errorName) = CustomizeException(exception); | ||
|
||
logger.LogError(customException); | ||
|
||
string exceptionName = customException.Equals(exception) ? $"{ErrorType.FHIRServiceError}{errorName}" : customException.GetType().Name; | ||
_exceptionTelemetryProcessor.LogExceptionMetric(customException, logger, FhirClientMetrics.HandledException(exceptionName, ErrorSeverity.Critical)); | ||
} | ||
|
||
public static (Exception customException, string errorName) CustomizeException(Exception exception) | ||
{ | ||
EnsureArg.IsNotNull(exception, nameof(exception)); | ||
|
||
string message; | ||
string errorName; | ||
|
||
switch (exception) | ||
{ | ||
case FhirOperationException _: | ||
var status = ((FhirOperationException)exception).Status; | ||
switch (status) | ||
{ | ||
case HttpStatusCode.Forbidden: | ||
message = FhirResources.FhirServiceAccessForbidden; | ||
string helpLink = "https://docs.microsoft.com/azure/healthcare-apis/iot/deploy-iot-connector-in-azure#accessing-the-iot-connector-from-the-fhir-service"; | ||
errorName = nameof(FhirServiceErrorCode.AuthorizationError); | ||
return (new UnauthorizedAccessFhirServiceException(message, exception, helpLink, errorName), errorName); | ||
case HttpStatusCode.NotFound: | ||
message = FhirResources.FhirServiceNotFound; | ||
errorName = nameof(FhirServiceErrorCode.ConfigurationError); | ||
return (new InvalidFhirServiceException(message, exception, errorName), errorName); | ||
default: | ||
return (exception, status.ToString()); | ||
} | ||
|
||
case ArgumentException _: | ||
var paramName = ((ArgumentException)exception).ParamName; | ||
if (paramName.Contains("endpoint", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
message = FhirResources.FhirServiceEndpointInvalid; | ||
errorName = nameof(FhirServiceErrorCode.ConfigurationError); | ||
return (new InvalidFhirServiceException(message, exception, errorName), errorName); | ||
} | ||
|
||
return (exception, $"{FhirServiceErrorCode.ArgumentError}{paramName}"); | ||
|
||
case UriFormatException _: | ||
message = FhirResources.FhirServiceUriFormatInvalid; | ||
errorName = nameof(FhirServiceErrorCode.ConfigurationError); | ||
return (new InvalidFhirServiceException(message, exception, errorName), errorName); | ||
|
||
case HttpRequestException _: | ||
// TODO: In .NET 5 and later, check HttpRequestException's StatusCode property instead of the Message property | ||
if (exception.Message.Contains(FhirResources.HttpRequestErrorNotKnown, StringComparison.CurrentCultureIgnoreCase)) | ||
{ | ||
message = FhirResources.FhirServiceHttpRequestError; | ||
errorName = nameof(FhirServiceErrorCode.ConfigurationError); | ||
return (new InvalidFhirServiceException(message, exception, errorName), errorName); | ||
} | ||
|
||
return (exception, nameof(FhirServiceErrorCode.HttpRequestError)); | ||
|
||
case MsalServiceException _: | ||
var errorCode = ((MsalServiceException)exception).ErrorCode; | ||
if (string.Equals(errorCode, "invalid_resource", StringComparison.OrdinalIgnoreCase) | ||
|| string.Equals(errorCode, "invalid_scope", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
message = FhirResources.FhirServiceMsalServiceError; | ||
errorName = nameof(FhirServiceErrorCode.ConfigurationError); | ||
return (new InvalidFhirServiceException(message, exception, errorName), errorName); | ||
} | ||
|
||
return (exception, $"{FhirServiceErrorCode.MsalServiceError}{errorCode}"); | ||
|
||
default: | ||
return (exception, nameof(FhirServiceErrorCode.GeneralError)); | ||
} | ||
} | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
...b/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/InvalidFhirServiceException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
using System; | ||
using Microsoft.Health.Common.Telemetry; | ||
using Microsoft.Health.Common.Telemetry.Exceptions; | ||
|
||
namespace Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions | ||
{ | ||
public sealed class InvalidFhirServiceException : IomtTelemetryFormattableException | ||
{ | ||
private static readonly string _errorType = ErrorType.FHIRServiceError; | ||
|
||
public InvalidFhirServiceException() | ||
{ | ||
} | ||
|
||
public InvalidFhirServiceException(string message) | ||
: base(message) | ||
{ | ||
} | ||
|
||
public InvalidFhirServiceException(string message, Exception innerException) | ||
: base(message, innerException) | ||
{ | ||
} | ||
|
||
public InvalidFhirServiceException( | ||
string message, | ||
Exception innerException, | ||
string errorName) | ||
: base( | ||
message, | ||
innerException, | ||
name: $"{_errorType}{errorName}", | ||
operation: ConnectorOperation.FHIRConversion) | ||
{ | ||
} | ||
|
||
public override string ErrType => _errorType; | ||
|
||
public override string ErrSeverity => ErrorSeverity.Critical; | ||
|
||
public override string ErrSource => nameof(ErrorSource.User); | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
....Health.Extensions.Fhir.R4/Telemetry/Exceptions/UnauthorizedAccessFhirServiceException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// ------------------------------------------------------------------------------------------------- | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. | ||
// ------------------------------------------------------------------------------------------------- | ||
|
||
using System; | ||
using Microsoft.Health.Common.Telemetry; | ||
using Microsoft.Health.Common.Telemetry.Exceptions; | ||
|
||
namespace Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions | ||
{ | ||
public sealed class UnauthorizedAccessFhirServiceException : IomtTelemetryFormattableException | ||
{ | ||
private static readonly string _errorType = ErrorType.FHIRServiceError; | ||
|
||
public UnauthorizedAccessFhirServiceException() | ||
{ | ||
} | ||
|
||
public UnauthorizedAccessFhirServiceException(string message) | ||
: base(message) | ||
{ | ||
} | ||
|
||
public UnauthorizedAccessFhirServiceException(string message, Exception innerException) | ||
: base(message, innerException) | ||
{ | ||
} | ||
|
||
public UnauthorizedAccessFhirServiceException( | ||
string message, | ||
Exception innerException, | ||
string helpLink, | ||
string errorName) | ||
: base( | ||
message, | ||
innerException, | ||
name: $"{_errorType}{errorName}", | ||
operation: ConnectorOperation.FHIRConversion) | ||
{ | ||
HelpLink = helpLink; | ||
} | ||
|
||
public override string ErrType => _errorType; | ||
|
||
public override string ErrSeverity => ErrorSeverity.Critical; | ||
|
||
public override string ErrSource => nameof(ErrorSource.User); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.