diff --git a/src/lib/Microsoft.Health.Common/Telemetry/Metrics/Dimensions/ErrorType.cs b/src/lib/Microsoft.Health.Common/Telemetry/Metrics/Dimensions/ErrorType.cs
index 53a79936..aee69628 100644
--- a/src/lib/Microsoft.Health.Common/Telemetry/Metrics/Dimensions/ErrorType.cs
+++ b/src/lib/Microsoft.Health.Common/Telemetry/Metrics/Dimensions/ErrorType.cs
@@ -45,7 +45,7 @@ public static class ErrorType
///
/// A metric type for errors that occur when interacting with the FHIR server.
///
- public static string FHIRServerError => nameof(FHIRServerError);
+ public static string FHIRServiceError => nameof(FHIRServiceError);
///
/// A metric type for errors of unknown type (e.g. unhandled exceptions)
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientFactory.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientFactory.cs
index 97486fa9..f5c4772a 100644
--- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientFactory.cs
+++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirClientFactory.cs
@@ -16,6 +16,7 @@
using Microsoft.Health.Common.Auth;
using Microsoft.Health.Common.Telemetry;
using Microsoft.Health.Extensions.Fhir.Config;
+using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions;
using Microsoft.Health.Extensions.Fhir.Telemetry.Metrics;
using Microsoft.Health.Extensions.Host.Auth;
using Microsoft.Health.Logging.Telemetry;
@@ -64,18 +65,27 @@ public FhirClient Create()
private static FhirClient CreateClient(TokenCredential tokenCredential, ITelemetryLogger logger)
{
+ EnsureArg.IsNotNull(tokenCredential, nameof(tokenCredential));
+
var url = Environment.GetEnvironmentVariable("FhirService:Url");
EnsureArg.IsNotNullOrEmpty(url, nameof(url));
var uri = new Uri(url);
- EnsureArg.IsNotNull(tokenCredential, nameof(tokenCredential));
-
var fhirClientSettings = new FhirClientSettings
{
PreferredFormat = ResourceFormat.Json,
};
- var client = new FhirClient(url, fhirClientSettings, new BearerTokenAuthorizationMessageHandler(uri, tokenCredential, logger));
+ FhirClient client = null;
+ try
+ {
+ client = new FhirClient(url, fhirClientSettings, new BearerTokenAuthorizationMessageHandler(uri, tokenCredential, logger));
+ FhirServiceValidator.ValidateFhirService(client, logger);
+ }
+ catch (Exception ex)
+ {
+ FhirServiceExceptionProcessor.ProcessException(ex, logger);
+ }
return client;
}
@@ -118,15 +128,8 @@ protected override async Task SendAsync(HttpRequestMessage
if (Logger != null && !response.IsSuccessStatusCode)
{
var statusDescription = response.ReasonPhrase.Replace(" ", string.Empty);
-
- if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
- {
- Logger.LogMetric(FhirClientMetrics.HandledException($"FhirServerError{statusDescription}", ErrorSeverity.Informational, ConnectorOperation.FHIRConversion), 1);
- }
- else
- {
- Logger.LogMetric(FhirClientMetrics.HandledException($"FhirServerError{statusDescription}", ErrorSeverity.Critical, ConnectorOperation.FHIRConversion), 1);
- }
+ var severity = response.StatusCode == System.Net.HttpStatusCode.TooManyRequests ? ErrorSeverity.Informational : ErrorSeverity.Critical;
+ Logger.LogMetric(FhirClientMetrics.HandledException($"{ErrorType.FHIRServiceError}{statusDescription}", severity), 1);
}
return response;
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirServiceValidator.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirServiceValidator.cs
new file mode 100644
index 00000000..09acfa7b
--- /dev/null
+++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/FhirServiceValidator.cs
@@ -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;
+ }
+ }
+ }
+}
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceErrorCode.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceErrorCode.cs
new file mode 100644
index 00000000..8fb54207
--- /dev/null
+++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceErrorCode.cs
@@ -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
+ {
+ ///
+ /// Error code that categorizes invalid configurations (e.g. invalid FHIR service URL)
+ ///
+ ConfigurationError,
+
+ ///
+ /// Error code that categorizes authorization errors (e.g. missing role with permission to write FHIR data)
+ ///
+ AuthorizationError,
+
+ ///
+ /// Error code that categorizes invalid arguments (i.e. exceptions encountered of type ArgumentException), which may occur when FhirClient's endpoint is validated
+ ///
+ ArgumentError,
+
+ ///
+ /// Error code that categorizes HTTP request exceptions (i.e. exceptions encountered of type HttpRequestException)
+ ///
+ HttpRequestError,
+
+ ///
+ /// Error code that categorizes MSAL.NET exceptions (i.e. exceptions encountered of type MsalServiceException)
+ ///
+ MsalServiceError,
+
+ ///
+ /// Error code that categorizes all other generic exceptions
+ ///
+ GeneralError,
+ }
+}
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs
new file mode 100644
index 00000000..020b577a
--- /dev/null
+++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/FhirServiceExceptionProcessor.cs
@@ -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));
+ }
+ }
+ }
+}
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/InvalidFhirServiceException.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/InvalidFhirServiceException.cs
new file mode 100644
index 00000000..724c4cd8
--- /dev/null
+++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/InvalidFhirServiceException.cs
@@ -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);
+ }
+}
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/UnauthorizedAccessFhirServiceException.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/UnauthorizedAccessFhirServiceException.cs
new file mode 100644
index 00000000..350ea550
--- /dev/null
+++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Exceptions/UnauthorizedAccessFhirServiceException.cs
@@ -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);
+ }
+}
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Metrics/FhirClientMetrics.cs b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Metrics/FhirClientMetrics.cs
index 8a1ac0f5..bc8a00ae 100644
--- a/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Metrics/FhirClientMetrics.cs
+++ b/src/lib/Microsoft.Health.Extensions.Fhir.R4/Telemetry/Metrics/FhirClientMetrics.cs
@@ -14,10 +14,9 @@ public class FhirClientMetrics
///
/// The name of the exception
/// The severity of the error
- /// The stage of the connector
- public static Metric HandledException(string exceptionName, string severity, string connectorStage)
+ public static Metric HandledException(string exceptionName, string severity)
{
- return exceptionName.ToErrorMetric(connectorStage, ErrorType.GeneralError, severity);
+ return exceptionName.ToErrorMetric(ConnectorOperation.FHIRConversion, ErrorType.FHIRServiceError, severity);
}
}
}
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj b/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj
index 9bca20a4..dd4e0ee9 100644
--- a/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj
+++ b/src/lib/Microsoft.Health.Extensions.Fhir/Microsoft.Health.Extensions.Fhir.csproj
@@ -30,4 +30,17 @@
+
+
+ True
+ True
+ FhirResources.resx
+
+
+
+
+ PublicResXFileCodeGenerator
+ FhirResources.Designer.cs
+
+
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir/Resources/FhirResources.Designer.cs b/src/lib/Microsoft.Health.Extensions.Fhir/Resources/FhirResources.Designer.cs
new file mode 100644
index 00000000..5dad8103
--- /dev/null
+++ b/src/lib/Microsoft.Health.Extensions.Fhir/Resources/FhirResources.Designer.cs
@@ -0,0 +1,126 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Health.Extensions.Fhir.Resources {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ public class FhirResources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal FhirResources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Health.Extensions.Fhir.Resources.FhirResources", typeof(FhirResources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ public static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify that the provided FHIR service's 'FHIR Data Writer' role has been assigned to the applicable Azure Active Directory security principal or managed identity..
+ ///
+ public static string FhirServiceAccessForbidden {
+ get {
+ return ResourceManager.GetString("FhirServiceAccessForbidden", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify that the FHIR service URL is provided and is an absolute URL..
+ ///
+ public static string FhirServiceEndpointInvalid {
+ get {
+ return ResourceManager.GetString("FhirServiceEndpointInvalid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify that the provided FHIR service exists..
+ ///
+ public static string FhirServiceHttpRequestError {
+ get {
+ return ResourceManager.GetString("FhirServiceHttpRequestError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify that the provided FHIR service URL is a base URL and exists in the applicable Azure Active Directory tenant..
+ ///
+ public static string FhirServiceMsalServiceError {
+ get {
+ return ResourceManager.GetString("FhirServiceMsalServiceError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify that the provided URL is for a FHIR service..
+ ///
+ public static string FhirServiceNotFound {
+ get {
+ return ResourceManager.GetString("FhirServiceNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Verify that the provided FHIR service URL is an absolute URL..
+ ///
+ public static string FhirServiceUriFormatInvalid {
+ get {
+ return ResourceManager.GetString("FhirServiceUriFormatInvalid", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Name or service not known.
+ ///
+ public static string HttpRequestErrorNotKnown {
+ get {
+ return ResourceManager.GetString("HttpRequestErrorNotKnown", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/lib/Microsoft.Health.Extensions.Fhir/Resources/FhirResources.resx b/src/lib/Microsoft.Health.Extensions.Fhir/Resources/FhirResources.resx
new file mode 100644
index 00000000..9db085fa
--- /dev/null
+++ b/src/lib/Microsoft.Health.Extensions.Fhir/Resources/FhirResources.resx
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Verify that the provided FHIR service's 'FHIR Data Writer' role has been assigned to the applicable Azure Active Directory security principal or managed identity.
+
+
+ Verify that the FHIR service URL is provided and is an absolute URL.
+
+
+ Verify that the provided FHIR service exists.
+
+
+ Verify that the provided FHIR service URL is a base URL and exists in the applicable Azure Active Directory tenant.
+
+
+ Verify that the provided URL is for a FHIR service.
+
+
+ Verify that the provided FHIR service URL is an absolute URL.
+
+
+ Name or service not known
+
+
\ No newline at end of file
diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs
index be58aa34..4c505838 100644
--- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs
+++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Service/R4FhirImportService.cs
@@ -11,6 +11,7 @@
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Health.Extensions.Fhir;
using Microsoft.Health.Extensions.Fhir.Search;
+using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions;
using Microsoft.Health.Fhir.Ingest.Data;
using Microsoft.Health.Fhir.Ingest.Telemetry;
using Microsoft.Health.Fhir.Ingest.Template;
@@ -42,14 +43,22 @@ public R4FhirImportService(IResourceIdentityService resourceIdentityService, Fhi
public override async Task ProcessAsync(ILookupTemplate config, IMeasurementGroup data, Func> errorConsumer = null)
{
- // Get required ids
- var ids = await ResourceIdentityService.ResolveResourceIdentitiesAsync(data).ConfigureAwait(false);
+ try
+ {
+ // Get required ids
+ var ids = await ResourceIdentityService.ResolveResourceIdentitiesAsync(data).ConfigureAwait(false);
- var grps = _fhirTemplateProcessor.CreateObservationGroups(config, data);
+ var grps = _fhirTemplateProcessor.CreateObservationGroups(config, data);
- foreach (var grp in grps)
+ foreach (var grp in grps)
+ {
+ _ = await SaveObservationAsync(config, grp, ids).ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
{
- _ = await SaveObservationAsync(config, grp, ids).ConfigureAwait(false);
+ FhirServiceExceptionProcessor.ProcessException(ex, _logger);
+ throw;
}
}
@@ -73,16 +82,16 @@ public virtual async Task SaveObservationAsync(ILookupTemplate
- .Handle(ex => ex.Status == System.Net.HttpStatusCode.Conflict || ex.Status == System.Net.HttpStatusCode.PreconditionFailed)
- .RetryAsync(2, async (polyRes, attempt) =>
- {
- existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);
- })
- .ExecuteAndCaptureAsync(async () =>
- {
- var mergedObservation = MergeObservation(config, existingObservation, observationGroup);
- return await _client.UpdateAsync(mergedObservation, versionAware: true).ConfigureAwait(false);
- }).ConfigureAwait(false);
+ .Handle(ex => ex.Status == System.Net.HttpStatusCode.Conflict || ex.Status == System.Net.HttpStatusCode.PreconditionFailed)
+ .RetryAsync(2, async (polyRes, attempt) =>
+ {
+ existingObservation = await GetObservationFromServerAsync(identifier).ConfigureAwait(false);
+ })
+ .ExecuteAndCaptureAsync(async () =>
+ {
+ var mergedObservation = MergeObservation(config, existingObservation, observationGroup);
+ return await _client.UpdateAsync(mergedObservation, versionAware: true).ConfigureAwait(false);
+ }).ConfigureAwait(false);
var exception = policyResult.FinalException;
diff --git a/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceExceptionProcessorTests.cs b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceExceptionProcessorTests.cs
new file mode 100644
index 00000000..4906604f
--- /dev/null
+++ b/test/Microsoft.Health.Extensions.Fhir.R4.UnitTests/FhirServiceExceptionProcessorTests.cs
@@ -0,0 +1,106 @@
+// -------------------------------------------------------------------------------------------------
+// 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.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using Hl7.Fhir.Rest;
+using Microsoft.Health.Common.Telemetry;
+using Microsoft.Health.Extensions.Fhir.Telemetry.Exceptions;
+using Microsoft.Health.Logging.Telemetry;
+using Microsoft.Identity.Client;
+using NSubstitute;
+using Xunit;
+
+namespace Microsoft.Health.Extensions.Fhir.R4.UnitTests
+{
+ public class FhirServiceExceptionProcessorTests
+ {
+ private static readonly Exception _fhirForbiddenEx = new FhirOperationException("test", HttpStatusCode.Forbidden);
+ private static readonly Exception _fhirNotFoundEx = new FhirOperationException("test", HttpStatusCode.NotFound);
+ private static readonly Exception _fhirBadRequestEx = new FhirOperationException("test", HttpStatusCode.BadRequest);
+ private static readonly Exception _argEndpointNullEx = new ArgumentNullException("endpoint");
+ private static readonly Exception _argEndpointEx = new ArgumentException("endpoint", "Endpoint must be absolute");
+ private static readonly Exception _argEx = new ArgumentException("test_message", "test_param");
+ private static readonly Exception _uriEx = new UriFormatException();
+ private static readonly Exception _httpNotKnownEx = new HttpRequestException("Name or service not known");
+ private static readonly Exception _httpEx = new HttpRequestException();
+ private static readonly Exception _msalInvalidResourceEx = new MsalServiceException("invalid_resource", "test_message");
+ private static readonly Exception _msalInvalidScopeEx = new MsalServiceException("invalid_scope", "test_message");
+ private static readonly Exception _msalEx = new MsalServiceException("test_code", "test_message");
+ private static readonly Exception _ex = new Exception();
+
+ public static IEnumerable