diff --git a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs index ed092fb444..251825a6ba 100644 --- a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs +++ b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs @@ -11,6 +11,7 @@ using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; +using Hl7.Fhir.Specification; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; @@ -860,9 +861,9 @@ private static ResourceIdentity verifyResourceIdentity(Uri location, bool needId if (msg is null && entryComponent is null) throw new ArgumentException("Either msg or entryComponent should be set"); // Validate the response and throw the appropriate exceptions. Also, if we have *not* verified the FHIR version // of the server, add a suggestion about this in the (legacy) parsing exception. - var suggestedVersionOnParseError = !Settings.VerifyFhirVersion ? fhirVersion : null; + (LastResult, LastBody, LastBodyAsText, LastBodyAsResource, var issue) = - await ValidateResponse(responseMessage, expect, getSerializationEngine(), suggestedVersionOnParseError, useBinaryProtocol) + await ValidateResponse(responseMessage, expect, getSerializationEngine(), !Settings.VerifyFhirVersion, fhirVersion, useBinaryProtocol) .ConfigureAwait(false); // If an error occurred while trying to interpret and validate the response, we will bail out now. @@ -921,17 +922,20 @@ static string unexpectedBodyTypeForMessage(HttpRequestMessage msg, Resource resu /// The body content type could not be handled or the response status indicated failure, or we received an unexpected success status. /// Thrown when the original ITypedElement-based parsers are used and a parse exception occurred. /// Thrown when a newer parsers is used and a parse exception occurred. - /// + /// internal static async Task ValidateResponse( HttpResponseMessage responseMessage, IEnumerable expect, IFhirSerializationEngine engine, - string? suggestedVersionOnParseError, + bool suggestVersionOnParsingError, + string fhirVersion, bool useBinaryProtocol) { - var responseData = (await responseMessage.ExtractResponseData(engine, useBinaryProtocol).ConfigureAwait(false)) + var fhirRelease = FhirReleaseParser.Parse(fhirVersion); + + var responseData = (await responseMessage.ExtractResponseData(engine, useBinaryProtocol, fhirRelease).ConfigureAwait(false)) .TranslateUnsupportedBodyTypeException(responseMessage.StatusCode) - .TranslateLegacyParserException(suggestedVersionOnParseError); + .TranslateLegacyParserException(suggestVersionOnParsingError ? fhirVersion : null); // If extracting the data caused an issue, return it immediately if (responseData.Issue is not null) diff --git a/src/Hl7.Fhir.Base/Rest/HttpContentParsers.cs b/src/Hl7.Fhir.Base/Rest/HttpContentParsers.cs index 38caf8be35..b237858f1b 100644 --- a/src/Hl7.Fhir.Base/Rest/HttpContentParsers.cs +++ b/src/Hl7.Fhir.Base/Rest/HttpContentParsers.cs @@ -11,6 +11,7 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; +using Hl7.Fhir.Specification; using Hl7.Fhir.Utility; using System; using System.Linq; @@ -147,7 +148,7 @@ internal record ResponseData(Bundle.ResponseComponent Response, byte[]? BodyData /// If the status of the response indicates failure, this function will be lenient and return the body data /// instead of throwing an when the content type or content itself is not recognizable as FHIR. This improves /// the chances of capturing diagnostic (non-FHIR) bodies returned by the server when an operation fails. - internal static async Task ExtractResponseData(this HttpResponseMessage message, IFhirSerializationEngine ser, bool expectBinaryProtocol) + internal static async Task ExtractResponseData(this HttpResponseMessage message, IFhirSerializationEngine ser, bool expectBinaryProtocol, FhirRelease fhirRelease) { var component = message.ExtractResponseComponent(); var data = await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false); @@ -159,14 +160,14 @@ internal static async Task ExtractResponseData(this HttpResponseMe // If this is not binary data, try to capture the body as text if (!expectBinaryProtocol) result = result with { BodyText = await message.Content.GetBodyAsString().ConfigureAwait(false) }; - + var usesFhirFormat = ContentType.GetResourceFormatFromContentType(message.Content.GetContentType()) != ResourceFormat.Unknown; try { var resource = message switch { - { IsSuccessStatusCode: true } when expectBinaryProtocol && !usesFhirFormat => await ReadBinaryDataFromMessage(message).ConfigureAwait(false), + { IsSuccessStatusCode: true } when expectBinaryProtocol && !usesFhirFormat => await ReadBinaryDataFromMessage(message, fhirRelease).ConfigureAwait(false), { IsSuccessStatusCode: true } => await ReadResourceFromMessage(message.Content, ser).ConfigureAwait(false), { IsSuccessStatusCode: false } => await ReadOutcomeFromMessage(message.Content, ser).ConfigureAwait(false), }; @@ -179,7 +180,7 @@ internal static async Task ExtractResponseData(this HttpResponseMe } // Sets the Resource.ResourceBase to the location given in the RequestUri of the response message. - if (result.BodyResource is not null && (message.Headers.Location?.OriginalString ?? message.GetRequestUri()?.OriginalString) is {} location) + if (result.BodyResource is not null && (message.Headers.Location?.OriginalString ?? message.GetRequestUri()?.OriginalString) is { } location) { var ri = new ResourceIdentity(location); result.BodyResource.ResourceBase = ri.HasBaseUri && ri.Form == ResourceIdentityForm.AbsoluteRestUrl @@ -196,11 +197,15 @@ internal static async Task ExtractResponseData(this HttpResponseMe /// /// A resource containing the streamed binary data. The resource's /// metadata will be retrieved from the appropriate HTTP headers. - public static async Task ReadBinaryDataFromMessage(this HttpResponseMessage message) + public static async Task ReadBinaryDataFromMessage(this HttpResponseMessage message, FhirRelease fhirRelease) { var result = new Binary(); - result.Data = result.Content = await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + if (fhirRelease == FhirRelease.STU3) + result.Content = await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + else + result.Data = await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + result.ContentType = message.Content.GetContentType(); result.SecurityContext = message.GetSecurityContext() is { } reference ? new ResourceReference(reference) : null; result.Meta ??= new(); diff --git a/src/Hl7.Fhir.Support.Poco.Tests/Rest/ResponseMessageTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/Rest/ResponseMessageTests.cs index 80ca9abdfd..88253ebbaf 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/Rest/ResponseMessageTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/Rest/ResponseMessageTests.cs @@ -101,7 +101,7 @@ public async Task SetAndExtractRelevantHeaders() response.Headers.Location = new Uri("http://nu.nl"); response.Headers.TryAddWithoutValidation("Test-key", "Test-value"); - var extracted = await response.ExtractResponseData(engine, expectBinaryProtocol: false); + var extracted = await response.ExtractResponseData(engine, expectBinaryProtocol: false, TESTINSPECTOR.FhirRelease); extracted.BodyText.Should().Be(DEFAULT_XML); engine.SerializeToXml(extracted.BodyResource!).Should().Be(DEFAULT_XML); @@ -117,7 +117,7 @@ public async Task SetAndExtractRelevantHeaders() public async Task GetEmptyResponse() { var response = new HttpResponseMessage(HttpStatusCode.Conflict); - var components = await response.ExtractResponseData(POCOENGINE, expectBinaryProtocol: false); + var components = await response.ExtractResponseData(POCOENGINE, expectBinaryProtocol: false, fhirRelease: Specification.FhirRelease.R4); components.Response.Status.Should().Be("409"); components.BodyData.Should().BeNull(); @@ -128,7 +128,7 @@ public async Task GetEmptyResponse() private static async Task check(HttpResponseMessage response, IFhirSerializationEngine engine, bool hasResource = false, Type? expectedIssue = null, string? messagePattern = null, string? notMessagePattern = null) { - var components = await response.ExtractResponseData(engine, expectBinaryProtocol: false).ConfigureAwait(false); + var components = await response.ExtractResponseData(engine, expectBinaryProtocol: false, fhirRelease: Specification.FhirRelease.R4).ConfigureAwait(false); await checkResult(response, components, engine, hasResource, expectedIssue, messagePattern, notMessagePattern); } @@ -299,7 +299,7 @@ await assertIssue(response, "Operation was unsuccessful, public async Task TurnsNewParsingFailureIntoDFE() { var response = makeXmlMessage(xml: """"""); - await assertIssue(response, "*Unknown type 'Unknown' found in root property*", engine: POCOENGINE, version: "1.0.0"); + await assertIssue(response, "*Unknown type 'Unknown' found in root property*", engine: POCOENGINE, suggestVersionOnParsingError: true, version: "1.0.0"); } [TestMethod] @@ -308,17 +308,17 @@ public async Task TurnsLegacyParsingFailureIntoFE() var response = makeXmlMessage(xml: """"""); await assertIssue(response, "*Cannot locate type information for type 'Unknown'*", engine: ELEMENTENGINE, notmatch: "*with FHIR version 1.0.0*"); await assertIssue(response, "*Cannot locate type information for type 'Unknown'*" + - "Are you connected to a FHIR server with FHIR version 1.0.0*", version: "1.0.0", engine: ELEMENTENGINE); + "Are you connected to a FHIR server with FHIR version 1.0.0*", true, version: "1.0.0", engine: ELEMENTENGINE); response = makeJsonMessage(json: """{ "resourceType": "Patient", "activex": 4 }"""); await assertIssue(response, "*Encountered unknown element 'activex' at location*", engine: ELEMENTENGINE); } - private static async Task assertIssue(HttpResponseMessage response, string match, string? version = null, + private static async Task assertIssue(HttpResponseMessage response, string match, bool suggestVersionOnParsingError = false, string? version = null, IFhirSerializationEngine? engine = null, string? notmatch = null) where T : Exception { - var result = await BaseFhirClient.ValidateResponse(response, new[] { HttpStatusCode.OK }, engine ?? POCOENGINE, version, useBinaryProtocol: false); + var result = await BaseFhirClient.ValidateResponse(response, new[] { HttpStatusCode.OK }, engine ?? POCOENGINE, suggestVersionOnParsingError, (suggestVersionOnParsingError) ? version! : "1.0.0", useBinaryProtocol: false); await checkResult(response, result, engine ?? POCOENGINE, hasResource: false, expectedIssue: typeof(T), messagePattern: match, notMessagePattern: notmatch); } @@ -336,15 +336,20 @@ public async Task GetBinaryBody() msg.SetSecurityContext("http://nu.nl"); msg.SetVersionFromETag("123"); - var b = await msg.ReadBinaryDataFromMessage(); + var b = await msg.ReadBinaryDataFromMessage(Specification.FhirRelease.R4); - b.Content.Should().BeEquivalentTo(data); + b.Content.Should().BeNull(); b.Data.Should().BeEquivalentTo(data); b.SecurityContext.Reference.Should().Be("http://nu.nl"); b.ContentType.Should().Be("application/crap"); b.Meta.VersionId.Should().Be("123"); b.Meta.LastUpdated.Should().Be(when); b.Id.Should().Be("4"); + + b = await msg.ReadBinaryDataFromMessage(Specification.FhirRelease.STU3); + b.Content.Should().BeEquivalentTo(data); + b.Data.Should().BeNull(); + } } }