From 8e4ae362d1e0f753368ac23bfe9e73ad48188b60 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Thu, 6 Mar 2025 17:36:16 +0100 Subject: [PATCH 1/8] WIP --- .../ElementModel/PocoBuilderSettings.cs | 11 + src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs | 16 +- ...FhirClientSerializationEngineExtensions.cs | 6 +- src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs | 5 +- .../BaseFhirJsonPocoDeserializer.cs | 4 +- .../Serialization/BaseFhirParser.cs | 10 +- .../BaseFhirXmlPocoDeserializer.cs | 12 +- .../FhirXmlPocoDeserializerSettings.cs | 97 ------- .../Serialization/ParserSettings.cs | 242 +++++++++++------- .../engine/FhirSerializationEngineFactory.cs | 14 +- .../Specification/Source/MultiResolver.cs | 14 +- .../Specification/Source/ResolverResult.cs | 6 +- .../Source/CommonDirectorySource.cs | 3 +- .../Specification/Source/CommonWebResolver.cs | 11 +- .../Source/DirectorySourceSettings.cs | 10 +- ...ValidateAllExamplesSearchExtractionTest.cs | 2 +- .../Rest/FhirClientTests.cs | 10 +- .../Serialization/ResourceParsingTests.cs | 62 ++--- .../Serialization/SerializationTests.cs | 15 +- .../ValueQuantityParsingTests.cs | 4 +- .../Specification/Source/DirectorySource.cs | 3 +- .../Source/DirectorySourceSettings.cs | 10 +- .../Specification/Source/WebResolver.cs | 163 ++++++------ .../RoundTripAttachments.cs | 2 +- .../SerializationExcexptionHandlersJson.cs | 86 +------ .../SerializationExcexptionHandlersXml.cs | 74 +----- .../SerializationExcexptionHandlersXmlPoco.cs | 16 +- .../SerializeDemoPatientJson.cs | 2 +- .../SerializeDemoPatientXml.cs | 2 +- .../Rest/FhirClientTests.cs | 8 +- .../Serialization/ResourceParsingTests.cs | 59 +++-- .../Serialization/SerializationTests.cs | 15 +- .../ValueQuantityParsingTests.cs | 4 +- .../Serialization/FhirXmlPocoDeserializer.cs | 4 +- .../Source/ResourceResolverTests.cs | 15 +- .../SnapshotGeneratorManifestTests.cs | 6 +- .../Source/ResourceResolverTests.cs | 16 +- .../FhirXmlDeserializationTests.cs | 8 +- src/firely-net-sdk-tests.props | 2 +- src/firely-net-sdk.props | 2 +- 40 files changed, 433 insertions(+), 618 deletions(-) delete mode 100644 src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs diff --git a/src/Hl7.Fhir.Base/ElementModel/PocoBuilderSettings.cs b/src/Hl7.Fhir.Base/ElementModel/PocoBuilderSettings.cs index 6d814406dd..9a6485ebc6 100644 --- a/src/Hl7.Fhir.Base/ElementModel/PocoBuilderSettings.cs +++ b/src/Hl7.Fhir.Base/ElementModel/PocoBuilderSettings.cs @@ -67,6 +67,17 @@ public void CopyTo(PocoBuilderSettings other) other.ExceptionHandler = ExceptionHandler; } + /// + /// Initializes the current instance from the specified instance. + /// + public void CopyFrom(ParserSettings settings) + { + if (settings == null) throw Error.ArgumentNull(nameof(settings)); + + AllowUnrecognizedEnums = settings.AllowUnrecognizedEnums; + IgnoreUnknownMembers = settings.AcceptUnknownMembers; + } + /// Creates a new object that is a copy of the current instance. public PocoBuilderSettings Clone() => new(this); diff --git a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs index 6a9884e083..a5e335740e 100644 --- a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs +++ b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs @@ -787,7 +787,7 @@ public async Task DeleteHistoryVersionAsync(string location, CancellationToken? public Task executeAsync(Model.Bundle tx, HttpStatusCode expect, CancellationToken? ct) where TResource : Model.Resource { - return executeAsync(tx, new[] { expect }, ct); + return executeAsync(tx, [expect], ct); } private async Task executeAsync(Bundle tx, IEnumerable expect, CancellationToken? ct) where TResource : Resource @@ -974,7 +974,9 @@ private static bool isPostOrPutOrPatch(HttpMethod method) => private IFhirSerializationEngine getSerializationEngine() { - return Settings.SerializationEngine ?? FhirSerializationEngineFactory.Legacy.FromParserSettings(Inspector, Settings.ParserSettings ?? new()); +#pragma warning disable CS0618 // Type or member is obsolete + return Settings.SerializationEngine ?? FhirSerializationEngineFactory.Legacy.FromParserSettings(Inspector, Settings.ParserSettings ?? new ParserSettings()); +#pragma warning restore CS0618 // Type or member is obsolete } private async Task verifyServerVersion(CancellationToken ct) @@ -985,12 +987,12 @@ private async Task verifyServerVersion(CancellationToken ct) _versionChecked = true; // So we can now start calling Conformance() without getting into a loop string? serverVersion; - var settings = Settings; + var originalEngine = Settings.SerializationEngine; try { - Settings = Settings.Clone(); - Settings.ParserSettings = new() { AllowUnrecognizedEnums = true }; + // Temporarily set the engine to ignore most errors. + Settings.SerializationEngine = FhirSerializationEngineFactory.Ostrich(Inspector); serverVersion = await getFhirVersionOfServer(ct).ConfigureAwait(false); } catch (FormatException fe) @@ -1000,8 +1002,8 @@ private async Task verifyServerVersion(CancellationToken ct) } finally { - // put back the original settings - Settings = settings; + // put back the original engine + Settings.SerializationEngine = originalEngine; } if (serverVersion == null) diff --git a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs index 3bef34dc37..4387612b0b 100644 --- a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs +++ b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs @@ -20,10 +20,10 @@ public static class FhirClientSerializationEngineExtensions /// /// Configures the FhirClient to use the legacy serialization behaviour, using the . /// - public static BaseFhirClient WithLegacySerializer(this BaseFhirClient client) + public static BaseFhirClient WithLegacySerializer(this BaseFhirClient client, ParserSettings? parserSettings = null) { client.Settings.SerializationEngine = - FhirSerializationEngineFactory.Legacy.FromParserSettings(client.Inspector, client.Settings.ParserSettings ?? new()); + FhirSerializationEngineFactory.Legacy.FromParserSettings(client.Inspector, parserSettings ?? new ParserSettings()); return client; } @@ -96,7 +96,7 @@ public static BaseFhirClient WithOstrichModeSerializer(this BaseFhirClient clien public static BaseFhirClient WithCustomIgnoreListSerializer(this BaseFhirClient client, string[] ignoreList) { - var xmlSettings = new FhirXmlPocoDeserializerSettings().Ignoring(ignoreList); + var xmlSettings = new ParserSettings().Ignoring(ignoreList); var jsonSettings = new FhirJsonConverterOptions().Ignoring(ignoreList); client.Settings.SerializationEngine = FhirSerializationEngineFactory.Custom(client.Inspector, jsonSettings, xmlSettings); diff --git a/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs b/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs index a7d2cddca9..b5cc51201f 100644 --- a/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs +++ b/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs @@ -93,7 +93,8 @@ public class FhirClientSettings /// /// ParserSettings for the pre-5.0 SDK parsers. Are only used when is not set. /// - public ParserSettings? ParserSettings = ParserSettings.CreateDefault(); + [Obsolete("Use the SerializationEngine setting instead, chosing one of the options on FhirSerializationEngineFactory.")] + public ParserSettings? ParserSettings = new(); /// /// How to transfer binary data when sending data to a Binary endpoint. @@ -123,7 +124,9 @@ public void CopyTo(FhirClientSettings other) { if (other == null) throw Error.ArgumentNull(nameof(other)); +#pragma warning disable CS0618 // Type or member is obsolete other.ParserSettings = ParserSettings; +#pragma warning restore CS0618 // Type or member is obsolete other.PreferCompressedResponses = PreferCompressedResponses; other.PreferredFormat = PreferredFormat; other.ReturnPreference = ReturnPreference; diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs index e044200dee..b600e441fd 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs @@ -76,7 +76,7 @@ public BaseFhirJsonPocoDeserializer(ModelInspector inspector, FhirJsonConverterO /// The result of deserialization. May be incomplete when there are issues. /// Issues encountered while deserializing. Will be empty when the function returns true. /// false if there are issues, true otherwise. - /// The influences which issues are returned. + /// The influences which issues are returned. public bool TryDeserializeResource(ref Utf8JsonReader reader, [NotNullWhen(true)] out Resource? instance, out IEnumerable issues) { if (reader.CurrentState.Options.CommentHandling is not JsonCommentHandling.Skip and not JsonCommentHandling.Disallow) @@ -103,7 +103,7 @@ public bool TryDeserializeResource(ref Utf8JsonReader reader, [NotNullWhen(true) /// The result of deserialization. May be incomplete when there are issues. /// Issues encountered while deserializing. Will be empty when the function returns true. /// false if there are issues, true otherwise. - /// The influences which issues are returned. + /// The influences which issues are returned. public bool TryDeserializeObject(Type targetType, ref Utf8JsonReader reader, [NotNullWhen(true)] out Base? instance, out IEnumerable issues) { if (reader.CurrentState.Options.CommentHandling is not JsonCommentHandling.Skip and not JsonCommentHandling.Disallow) diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs index 43e0357f88..38a93006c2 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs @@ -17,19 +17,19 @@ internal static PocoBuilderSettings BuildPocoBuilderSettings(ParserSettings ps) { AllowUnrecognizedEnums = ps.AllowUnrecognizedEnums, IgnoreUnknownMembers = ps.AcceptUnknownMembers, - ExceptionHandler = ps.ExceptionHandler, -#pragma warning disable CS0618 // Type or member is obsolete - TruncateDateTimeToDate = ps.TruncateDateTimeToDate -#pragma warning restore CS0618 // Type or member is obsolete }; internal static FhirXmlParsingSettings BuildXmlParsingSettings(ParserSettings settings) => new() { - DisallowSchemaLocation = settings.DisallowXsiAttributesOnRoot, + DisallowSchemaLocation = !settings.AllowXsiAttributesOnRoot, +#pragma warning disable CS0618 // Type or member is obsolete PermissiveParsing = settings.PermissiveParsing, +#pragma warning restore CS0618 // Type or member is obsolete }; internal static FhirJsonParsingSettings BuildJsonParserSettings(ParserSettings settings) => +#pragma warning disable CS0618 // Type or member is obsolete new() { AllowJsonComments = false, PermissiveParsing = settings.PermissiveParsing }; +#pragma warning restore CS0618 // Type or member is obsolete } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs index a52722383e..a5c579b70d 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs @@ -20,7 +20,7 @@ public class BaseFhirXmlPocoDeserializer /// Initializes an instance of the deserializer. /// /// Assembly containing the POCO classes to be used for deserialization. - public BaseFhirXmlPocoDeserializer(Assembly assembly) : this(assembly, new FhirXmlPocoDeserializerSettings()) + public BaseFhirXmlPocoDeserializer(Assembly assembly) : this(assembly, new ParserSettings()) { // nothing } @@ -39,7 +39,7 @@ public class BaseFhirXmlPocoDeserializer /// /// Assembly containing the POCO classes to be used for deserialization. /// A settings object to be used by this instance. - public BaseFhirXmlPocoDeserializer(Assembly assembly, FhirXmlPocoDeserializerSettings settings) + public BaseFhirXmlPocoDeserializer(Assembly assembly, ParserSettings settings) { Settings = settings; _inspector = ModelInspector.ForAssembly(assembly ?? throw new ArgumentNullException(nameof(assembly))); @@ -50,7 +50,7 @@ public BaseFhirXmlPocoDeserializer(Assembly assembly, FhirXmlPocoDeserializerSet /// /// The containing the POCO classes to be used for deserialization. /// A settings object to be used by this instance. - public BaseFhirXmlPocoDeserializer(ModelInspector inspector, FhirXmlPocoDeserializerSettings settings) + public BaseFhirXmlPocoDeserializer(ModelInspector inspector, ParserSettings settings) { Settings = settings; _inspector = inspector; @@ -59,7 +59,7 @@ public BaseFhirXmlPocoDeserializer(ModelInspector inspector, FhirXmlPocoDeserial /// /// The settings that were passed to the constructor. /// - public FhirXmlPocoDeserializerSettings Settings { get; } + public ParserSettings Settings { get; } private readonly ModelInspector _inspector; @@ -70,7 +70,7 @@ public BaseFhirXmlPocoDeserializer(ModelInspector inspector, FhirXmlPocoDeserial /// The result of deserialization. May be incomplete when there are issues. /// Issues encountered while deserializing. Will be empty when the function returns true. /// false if there are issues, true otherwise. - /// The influences which issues are returned. + /// The influences which issues are returned. public bool TryDeserializeResource(XmlReader reader, [NotNullWhen(true)] out Resource? instance, out IEnumerable issues) { FhirXmlPocoDeserializerState state = new(); @@ -100,7 +100,7 @@ public bool TryDeserializeResource(XmlReader reader, [NotNullWhen(true)] out Res /// The result of deserialization. May be incomplete when there are issues. /// Issues encountered while deserializing. Will be empty when the function returns true. /// false if there are issues, true otherwise. - /// The influences which issues are returned. + /// The influences which issues are returned. public bool TryDeserializeElement(Type targetType, XmlReader reader, [NotNullWhen(true)] out Base? instance, out IEnumerable issues) { FhirXmlPocoDeserializerState state = new(); diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs deleted file mode 100644 index 8b68de85c4..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlPocoDeserializerSettings.cs +++ /dev/null @@ -1,97 +0,0 @@ -#nullable enable - -using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; -using Hl7.FhirPath.Sprache; -using System; -using System.Collections.Generic; - -namespace Hl7.Fhir.Serialization; - -public record FhirXmlPocoDeserializerSettings -{ - /// - /// If set, this validator is invoked before the value is set in the object under construction to validate - /// and possibly alter the value. Setting this property to null will disable validation completely. - /// - public IDeserializationValidator? Validator { get; init; } = DataAnnotationDeserialzationValidator.Default; - - /// - /// Specifies a filter that can be used to filter out exceptions that are not considered fatal. The filter - /// returns true for exceptions that should be ignored, and false otherwise. - /// - public Predicate? ExceptionFilter { get; set; } = null; - - /// - /// Perform the parse time validation on the deserialized object even if parsing issues occurred. - /// - /// - /// This is useful for "strict mode" single-pass validators and may result in spurious error messages - /// from validating incomplete content. - /// - public bool ValidateOnFailedParse { get; init; } = false; - - /// - /// During parsing any contained resources (such as those in a bundle) that encounter some form of parse/validation exception - /// will have a List<CodedException> of these exceptions added as an annotation to the child resource. - /// - /// - /// This is primarily added to ease the processing of bundles during a batch submission. - /// (without requiring processing fhirpath expressions in the issues in the parsing operation outcome to determine if a - /// resource was clean and possibly ok to process). - /// - public bool AnnotateResourceParseExceptions { get; init; } = false; - - /// - /// For performance reasons, validation of Xhtml again the rules specified in the FHIR - /// specification for Narrative (http://hl7.org/fhir/narrative.html#2.4.0) is turned off by - /// default. Set this property to any other value than - /// to perform validation. - /// - public NarrativeValidationKind NarrativeValidation { get; init; } = NarrativeValidationKind.None; - - /// - /// Enables all validation rules that are available. - /// - /// The selected mode to use, see . - /// How strict to validate the XHtml in FHIR Narrative. Only relevant in mode - public FhirXmlPocoDeserializerSettings WithMode(DeserializationMode mode, - NarrativeValidationKind nvk = NarrativeValidationKind.FhirXhtml) => - mode switch - { - DeserializationMode.Strict => this with - { - ExceptionFilter = null, // No exceptions are ignored - NarrativeValidation = nvk - }, - DeserializationMode.BackwardsCompatible => this with - { - ExceptionFilter = CodedExceptionFilters.IsBackwardsCompatibilityIssue, - NarrativeValidation = NarrativeValidationKind.None - }, - DeserializationMode.Recoverable => this with - { - ExceptionFilter = CodedExceptionFilters.IsRecoverableIssue, - NarrativeValidation = NarrativeValidationKind.None - }, - DeserializationMode.Ostrich => this with - { - Validator = null, // Disable all validations, we don't care. - ExceptionFilter = _ => true, // If there are still errors, ignore. - NarrativeValidation = NarrativeValidationKind.None // We don't care about the narrative. - }, - _ => throw Error.NotSupported("Unknown deserialization mode.") - }; - - /// - /// Alters the options to enforce specific parsing exceptions. - /// - public FhirXmlPocoDeserializerSettings Enforcing(IEnumerable toEnforce) => - this with { ExceptionFilter = this.ExceptionFilter.Enforce(toEnforce) }; - - /// - /// Alters the options to ignore specific parsing exceptions. - /// - public FhirXmlPocoDeserializerSettings Ignoring(IEnumerable toIgnore) => - this with { ExceptionFilter = this.ExceptionFilter.Ignore(toIgnore) }; -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs index 6088137602..d0357469b8 100644 --- a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs +++ b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs @@ -1,103 +1,161 @@ -/* - * Copyright (c) 2016, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - +#nullable enable using Hl7.Fhir.Utility; +using Hl7.Fhir.Validation; +using Hl7.FhirPath.Sprache; using System; +using System.Collections.Generic; +using System.Linq; + +namespace Hl7.Fhir.Serialization; -namespace Hl7.Fhir.Serialization +public record ParserSettings { - /// Common parser configuration settings for BaseFhirParser and subclasses. - public class ParserSettings + /// + /// If set, this validator is invoked before the value is set in the object under construction to validate + /// and possibly alter the value. Setting this property to null will disable validation completely. + /// + public IDeserializationValidator? Validator { get; init; } = DataAnnotationDeserialzationValidator.Default; + + /// + /// Specifies a filter that can be used to filter out exceptions that are not considered fatal. The filter + /// returns true for exceptions that should be ignored, and false otherwise. + /// + /// Setting , , + /// or will augment + /// this filter to reflect these settings. + public Predicate? ExceptionFilter { - /// - /// Raise an error when an xsi:schemaLocation is encountered. - /// - public bool DisallowXsiAttributesOnRoot { get; set; } - - /// - /// Do not throw when encountering values not parseable as a member of an enumeration in a Poco. - /// - public bool AllowUnrecognizedEnums { get; set; } - - /// - /// Do not throw when the data has an element that does not map to a property in the Poco. - /// - public bool AcceptUnknownMembers { get; set; } - - /// - /// Do not raise exceptions for recoverable errors. - /// - public bool PermissiveParsing { get; set; } = true; - - /// - /// Allow to parse a FHIR dateTime values into an element of type date. - /// - /// - /// Needed for backward compatibility with old parser for resources which were saved and considered valid in the past. - /// > - [Obsolete("Needed for backward compatibility with old parser for resources which were saved and considered valid in the past. " + - "Should not be used in new code.")] - public bool TruncateDateTimeToDate { get; set; } = false; - - /// - /// A Handler to permit intercepting Exceptions during parsing - /// - public ExceptionNotificationHandler ExceptionHandler { get; set; } - - /// Default constructor. Creates a new instance with default property values. - public ParserSettings() { } - - /// Clone constructor. Generates a new instance initialized from the state of the specified instance. - /// The specified argument is null. - public ParserSettings(ParserSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - other.CopyTo(this); - } - - /// Copy all configuration settings to another instance. - /// Another instance. - /// The specified argument is null. - public void CopyTo(ParserSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); + get => augmentFilter(); + init => _exceptionFilter = value; + } - other.DisallowXsiAttributesOnRoot = DisallowXsiAttributesOnRoot; - other.AllowUnrecognizedEnums = AllowUnrecognizedEnums; - other.AcceptUnknownMembers = AcceptUnknownMembers; - other.PermissiveParsing = PermissiveParsing; -#pragma warning disable CS0618 // Type or member is obsolete - other.TruncateDateTimeToDate = TruncateDateTimeToDate; -#pragma warning restore CS0618 // Type or member is obsolete - other.ExceptionHandler = ExceptionHandler; - } - - /// - /// Copy the necessary settings to PocoBuilderSettings - /// - /// The instance where the settings are copied to. - public void CopyTo(PocoBuilderSettings settings) - { - if (settings == null) throw Error.ArgumentNull(nameof(settings)); + private readonly Predicate? _exceptionFilter; + + /// + /// Perform the parse time validation on the deserialized object even if parsing issues occurred. + /// + /// + /// This is useful for "strict mode" single-pass validators and may result in spurious error messages + /// from validating incomplete content. + /// + public bool ValidateOnFailedParse { get; init; } = false; + + /// + /// During parsing any contained resources (such as those in a bundle) that encounter some form of parse/validation exception + /// will have a List<CodedException> of these exceptions added as an annotation to the child resource. + /// + /// + /// This is primarily added to ease the processing of bundles during a batch submission. + /// (without requiring processing fhirpath expressions in the issues in the parsing operation outcome to determine if a + /// resource was clean and possibly ok to process). + /// + public bool AnnotateResourceParseExceptions { get; init; } = false; + + /// + /// For performance reasons, validation of Xhtml again the rules specified in the FHIR + /// specification for Narrative (http://hl7.org/fhir/narrative.html#2.4.0) is turned off by + /// default. Set this property to any other value than + /// to perform validation. + /// + public NarrativeValidationKind NarrativeValidation { get; init; } = NarrativeValidationKind.None; + + private Predicate? augmentFilter() + { + var ignored = new List(); - settings.AllowUnrecognizedEnums = AllowUnrecognizedEnums; - settings.IgnoreUnknownMembers = AcceptUnknownMembers; + // Simulate the old behaviour by selectively enforcing errors. + if(AllowXsiAttributesOnRoot) ignored.Add(FhirXmlException.SCHEMALOCATION_DISALLOWED_CODE); + if(AllowUnrecognizedEnums) ignored.Add(CodedValidationException.INVALID_CODED_VALUE_CODE); + if(AllowUnrecognizedEnums) ignored.AddRange( + [FhirXmlException.UNKNOWN_ELEMENT_CODE, FhirXmlException.UNKNOWN_ATTRIBUTE_CODE]); + + var augmentedFilter = _exceptionFilter; #pragma warning disable CS0618 // Type or member is obsolete - settings.TruncateDateTimeToDate = TruncateDateTimeToDate; + if (PermissiveParsing) augmentedFilter = augmentedFilter.Or(CodedExceptionFilters.IsRecoverableIssue); #pragma warning restore CS0618 // Type or member is obsolete - settings.ExceptionHandler = ExceptionHandler; - } - - /// Creates a new object that is a copy of the current instance. - public ParserSettings Clone() => new(this); - - /// Creates a new instance with default property values. - public static ParserSettings CreateDefault() => new ParserSettings(); + if(ignored.Any()) augmentedFilter = augmentedFilter.Ignore(ignored); + return augmentedFilter; } -} \ No newline at end of file + + /// + /// Suppress an error when an xsi:schemaLocation is encountered. + /// + /// This is the same as calling with + /// FhirXmlException.SCHEMALOCATION_DISALLOWED_CODE as the argument. + public bool AllowXsiAttributesOnRoot { get; init; } + + /// + /// Do not throw when encountering values not parseable as a member of an enumeration in a Poco. + /// + /// + /// This is the same as calling with + /// CodedValidationException.INVALID_CODED_VALUE_CODE as the argument. + /// + public bool AllowUnrecognizedEnums { get; init; } + + /// + /// Do not throw when the data has an element that does not map to a property in the Poco. + /// + /// + /// This is the same as calling with + /// FhirXmlException.UNKNOWN_ELEMENT_CODE and FhirXmlException.UNKNOWN_ATTRIBUTE_CODE + /// as the arguments. + /// + public bool AcceptUnknownMembers { get; init; } + + /// + /// Do not raise exceptions for recoverable errors. + /// + /// This is the same as adding to the exception filter. + [Obsolete("Use WithMode(DeserializationMode.Recoverable) instead.")] + public bool PermissiveParsing { get; init; } + + /// + /// Enables all validation rules that are available. + /// + /// The selected mode to use, see . + /// How strict to validate the XHtml in FHIR Narrative. Only relevant in mode + public ParserSettings UsingMode(DeserializationMode mode, + NarrativeValidationKind nvk = NarrativeValidationKind.FhirXhtml) => + mode switch + { + DeserializationMode.Strict => this with + { + ExceptionFilter = null, // No exceptions are ignored + NarrativeValidation = nvk + }, + DeserializationMode.BackwardsCompatible => this with + { + ExceptionFilter = CodedExceptionFilters.IsBackwardsCompatibilityIssue, + NarrativeValidation = NarrativeValidationKind.None + }, + DeserializationMode.Recoverable => this with + { + ExceptionFilter = CodedExceptionFilters.IsRecoverableIssue, + NarrativeValidation = NarrativeValidationKind.None + }, + DeserializationMode.Ostrich => this with + { + Validator = null, // Disable all validations, we don't care. + ExceptionFilter = _ => true, // If there are still errors, ignore. + NarrativeValidation = NarrativeValidationKind.None // We don't care about the narrative. + }, + _ => throw Error.NotSupported("Unknown deserialization mode.") + }; + + /// + /// Alters the options to enforce specific parsing exceptions. + /// + public ParserSettings Enforcing(IEnumerable toEnforce) => + this with { ExceptionFilter = this.ExceptionFilter.Enforce(toEnforce) }; + + /// + /// Alters the options to ignore specific parsing exceptions. + /// + public ParserSettings Ignoring(IEnumerable toIgnore) => + this with { ExceptionFilter = this.ExceptionFilter.Ignore(toIgnore) }; +} + +[Obsolete("All parsing settings are unified under ParserSettings. Use that class instead.")] +public record FhirXmlPocoDeserializerSettings : ParserSettings; \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs index 47b790daf3..174dc148c2 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs @@ -26,7 +26,7 @@ public static partial class FhirSerializationEngineFactory /// public static IFhirSerializationEngine Strict(ModelInspector inspector, FhirJsonConverterOptions? converterOptions = null, - FhirXmlPocoDeserializerSettings? xmlSettings = null) => + ParserSettings? xmlSettings = null) => createEngine(inspector, converterOptions, xmlSettings, DeserializationMode.Strict); /// @@ -35,7 +35,7 @@ public static IFhirSerializationEngine Strict(ModelInspector inspector, /// public static IFhirSerializationEngine Recoverable(ModelInspector inspector, FhirJsonConverterOptions? converterOptions = null, - FhirXmlPocoDeserializerSettings? xmlSettings = null) => + ParserSettings? xmlSettings = null) => createEngine(inspector, converterOptions, xmlSettings, DeserializationMode.Recoverable); /// @@ -46,7 +46,7 @@ public static IFhirSerializationEngine Recoverable(ModelInspector inspector, /// public static IFhirSerializationEngine BackwardsCompatible(ModelInspector inspector, FhirJsonConverterOptions? converterOptions = null, - FhirXmlPocoDeserializerSettings? xmlSettings = null) => + ParserSettings? xmlSettings = null) => createEngine(inspector, converterOptions, xmlSettings, DeserializationMode.BackwardsCompatible); /// @@ -55,7 +55,7 @@ public static IFhirSerializationEngine BackwardsCompatible(ModelInspector inspec /// public static IFhirSerializationEngine Ostrich(ModelInspector inspector, FhirJsonConverterOptions? converterOptions = null, - FhirXmlPocoDeserializerSettings? xmlSettings = null) => + ParserSettings? xmlSettings = null) => createEngine(inspector, converterOptions, xmlSettings, DeserializationMode.Ostrich); /// @@ -68,7 +68,7 @@ public static IFhirSerializationEngine Ostrich(ModelInspector inspector, /// public static IFhirSerializationEngine Custom(ModelInspector inspector, FhirJsonConverterOptions converterOptions, - FhirXmlPocoDeserializerSettings xmlSerializerSettings) + ParserSettings xmlSerializerSettings) { var jsonDeserializer = new BaseFhirJsonPocoDeserializer(inspector, converterOptions); var xmlDeserializer = new BaseFhirXmlPocoDeserializer(inspector, xmlSerializerSettings); @@ -80,10 +80,10 @@ public static IFhirSerializationEngine Custom(ModelInspector inspector, } private static IFhirSerializationEngine createEngine(ModelInspector inspector, - FhirJsonConverterOptions? converterOptions, FhirXmlPocoDeserializerSettings? xmlSettings, DeserializationMode mode) + FhirJsonConverterOptions? converterOptions, ParserSettings? xmlSettings, DeserializationMode mode) { var jsonOptions = (converterOptions ?? new FhirJsonConverterOptions()).WithMode(mode); - var xmlOptions = (xmlSettings ?? new FhirXmlPocoDeserializerSettings()).WithMode(mode); + var xmlOptions = (xmlSettings ?? new ParserSettings()).UsingMode(mode); return Custom(inspector, jsonOptions, xmlOptions); } diff --git a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs index b177333107..5ca5a50c80 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/MultiResolver.cs @@ -74,10 +74,10 @@ public void Pop() [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] - public Resource ResolveByUri(string uri) => TryResolveByUri(uri).Value; + public Resource? ResolveByUri(string uri) => TryResolveByUri(uri).Value; [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByCanonicalUriAsync() instead.")] - public Resource ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; + public Resource? ResolveByCanonicalUri(string uri) => TryResolveByCanonicalUri(uri).Value; /// [Obsolete("MultiResolver now works best with asynchronous resolvers. Use TryResolveByUriAsync() instead.")] @@ -113,8 +113,8 @@ public async Task TryResolveByUriAsync(string uri) if (result.Success) return result; - - innerErrors.Add(result.Error); + else + innerErrors.Add(result.Error!); } catch(NotImplementedException ex) { @@ -141,8 +141,8 @@ public async Task TryResolveByCanonicalUriAsync(string uri) if (result.Success) return result; - - innerErrors.Add(result.Error); + else + innerErrors.Add(result.Error!); } catch (NotImplementedException ex) { @@ -161,4 +161,4 @@ public async Task TryResolveByCanonicalUriAsync(string uri) internal protected virtual string DebuggerDisplay => $"{GetType().Name} for {_sources.Count} sources: {string.Join(" | ", _sources.Select(s => s.DebuggerDisplayString()))}"; } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs index aee5c67a6c..d9d871ad9f 100644 --- a/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs +++ b/src/Hl7.Fhir.Base/Specification/Source/ResolverResult.cs @@ -2,6 +2,8 @@ using System; using System.Diagnostics.CodeAnalysis; +#nullable enable + namespace Hl7.Fhir.Specification.Source; /// @@ -20,7 +22,7 @@ public readonly record struct ResolverResult #if NET8_0_OR_GREATER [MemberNotNullWhen(true, nameof(Success))] #endif - public Resource Value { get; private init; } + public Resource? Value { get; } /// /// Error encountered while attempting retrieval of resource @@ -28,7 +30,7 @@ public readonly record struct ResolverResult #if NET8_0_OR_GREATER [MemberNotNullWhen(false, nameof(Success))] #endif - public ResolverException Error { get; private init; } + public ResolverException? Error { get; private init; } /// /// Constructor for successfully resolved resource diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs index bf9a999208..00ed250849 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonDirectorySource.cs @@ -974,7 +974,8 @@ protected internal IEnumerable ListResourceUris(string? filter = null) // Also use the current PoCo parser settings var pocoSettings = PocoBuilderSettings.CreateDefault(); - _settings.ParserSettings?.CopyTo(pocoSettings); + if (_settings.ParserSettings is { } ps) + pocoSettings.CopyFrom(ps); T? result = null; diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs index 01d8b886fa..10bf36c885 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/CommonWebResolver.cs @@ -38,12 +38,6 @@ public CommonWebResolver(Func fhirClientFactory) _clientFactory = fhirClientFactory ?? throw Error.ArgumentNull(nameof(fhirClientFactory)); } - /// Gets or sets configuration settings that control parsing behavior. - public ParserSettings? ParserSettings { get; set; } - - /// Gets or sets the request timeout of the internal instance. - public int TimeOut { get; set; } = DefaultTimeOut; - /// /// Gets the runtime from the last call to the /// method, if any, or null otherwise. @@ -69,12 +63,11 @@ public ResolverResult TryResolveByUri(string uri) var id = new ResourceIdentity(uri); var client = _clientFactory(id.BaseUri); - client.Settings.Timeout = this.TimeOut; - client.Settings.ParserSettings = this.ParserSettings; try { - var resultResource = TaskHelper.Await(() => client.ReadAsync(id)); + var resultResource = TaskHelper.Await(() => client.ReadAsync(id)) + ?? throw new InvalidOperationException("FhirClient.ReadAsync returned null, which was unexpected."); resultResource.SetOrigin(uri); LastError = null; return resultResource; diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs b/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs index 5e059469f1..b4f177501f 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs @@ -29,9 +29,9 @@ public sealed class DirectorySourceSettings public static DirectorySourceSettings CreateDefault() => new DirectorySourceSettings(); // Instance fields - ParserSettings _parserSettings = ParserSettings.CreateDefault(); - FhirXmlParsingSettings _xmlParserSettings = FhirXmlParsingSettings.CreateDefault(); - FhirJsonParsingSettings _jsonParserSettings = FhirJsonParsingSettings.CreateDefault(); + private ParserSettings _parserSettings = new(); + private FhirXmlParsingSettings _xmlParserSettings = FhirXmlParsingSettings.CreateDefault(); + private FhirJsonParsingSettings _jsonParserSettings = FhirJsonParsingSettings.CreateDefault(); /// Default constructor. Creates a new instance with default property values. public DirectorySourceSettings() @@ -67,7 +67,7 @@ public void CopyTo(DirectorySourceSettings other) other.MultiThreaded = this.MultiThreaded; other.SummaryDetailsHarvesters = (ArtifactSummaryHarvester[])this.SummaryDetailsHarvesters?.Clone(); other.ExcludeSummariesForUnknownArtifacts = this.ExcludeSummariesForUnknownArtifacts; - other.ParserSettings = new ParserSettings(this.ParserSettings); + other.ParserSettings = this.ParserSettings with { }; other.XmlParserSettings = new FhirXmlParsingSettings(this.XmlParserSettings); other.JsonParserSettings = new FhirJsonParsingSettings(this.JsonParserSettings); } @@ -296,7 +296,7 @@ public string Mask public ParserSettings ParserSettings { get => _parserSettings; - set => _parserSettings = value ?? ParserSettings.CreateDefault(); + set => _parserSettings = value ?? new ParserSettings(); } /// diff --git a/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs b/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs index f08906d398..4e243e4798 100644 --- a/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs +++ b/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs @@ -44,7 +44,7 @@ public void SearchExtractionAllExamples() private void SearchExtractionAllExamplesInternal() { - FhirXmlParser parser = new FhirXmlParser(new ParserSettings { PermissiveParsing = true }); + var parser = new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)); int errorCount = 0; int parserErrorCount = 0; int testFileCount = 0; diff --git a/src/Hl7.Fhir.STU3.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.STU3.Tests/Rest/FhirClientTests.cs index 83d79bfc29..af86eee163 100644 --- a/src/Hl7.Fhir.STU3.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Rest/FhirClientTests.cs @@ -145,7 +145,7 @@ public async Tasks.Task FetchConformanceHttpClient() private async Tasks.Task TestConformance(BaseFhirClient client) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; var entry = await client.CapabilityStatementAsync(); Assert.IsNotNull(entry); @@ -1067,7 +1067,7 @@ public async Tasks.Task CallsCallbacksHttpClientHandler() { using (FhirClient client = new FhirClient(testEndpoint, messageHandler: handler)) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; bool calledBefore = false; HttpStatusCode? status = null; @@ -1107,7 +1107,7 @@ public async Tasks.Task CallsCallbacksHttpClientHandler() // And use another on the same handler to ensure that it wasn't disposed :O using (FhirClient client = new FhirClient(testEndpoint, messageHandler: handler)) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; bool calledBefore = false; HttpStatusCode? status = null; @@ -1161,7 +1161,7 @@ public async Tasks.Task CallsCallbacksHttpClient() { using (FhirClient client = new FhirClient(testEndpoint, httpClient: httpClient)) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; bool calledBefore = false; HttpStatusCode? status = null; @@ -1201,7 +1201,7 @@ public async Tasks.Task CallsCallbacksHttpClient() // And use another on the same handler to ensure that it wasn't disposed :O using (FhirClient client = new FhirClient(testEndpoint, httpClient: httpClient)) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; bool calledBefore = false; HttpStatusCode? status = null; diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs index 5f3674285d..046d5b45dc 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs @@ -27,31 +27,33 @@ public class ResourceParsingTests public void ConfigureFailOnUnknownMember() { var xml = ""; - var parser = new FhirXmlParser(); - parser.Settings.AllowUnrecognizedEnums = true; - parser.Settings.ExceptionHandler = (object source, Utility.ExceptionNotification args) => - { - Debug.WriteLine(args.Message); - if (args.Exception is StructuralTypeException && args.Severity == Utility.ExceptionSeverity.Error) - { - Assert.IsTrue(args.Exception.Message.Contains("Type checking the data: "), "Error message detected"); - throw new StructuralTypeException(args.Exception.Message.Replace("Type checking the data: ", ""), args.Exception.InnerException); - } - }; - - try - { - var r2 = parser.Parse(xml); - Assert.Fail("Should have failed on unknown member"); - } - catch (StructuralTypeException ste) - { - Debug.WriteLine(ste.Message); - Assert.IsFalse(ste.Message.Contains("Type checking the data: "), "Custom error message should have removed the prefix"); - } - - parser.Settings.AcceptUnknownMembers = true; + var parser = new FhirXmlParser(new ParserSettings { AllowXsiAttributesOnRoot = true }); + + // parser.Settings.ExceptionHandler = (object source, Utility.ExceptionNotification args) => + // { + // Debug.WriteLine(args.Message); + // if (args.Exception is StructuralTypeException && args.Severity == Utility.ExceptionSeverity.Error) + // { + // Assert.IsTrue(args.Exception.Message.Contains("Type checking the data: "), "Error message detected"); + // throw new StructuralTypeException(args.Exception.Message.Replace("Type checking the data: ", ""), args.Exception.InnerException); + // } + // }; + + // try + // { + // var r2 = parser.Parse(xml); + // Assert.Fail("Should have failed on unknown member"); + // } + // catch (StructuralTypeException ste) + // { + // Debug.WriteLine(ste.Message); + // Assert.IsFalse(ste.Message.Contains("Type checking the data: "), "Custom error message should have removed the prefix"); + // } + // + // parser.Settings.AcceptUnknownMembers = true; var resource = parser.Parse(xml); + + throw new NotImplementedException("Repair this unit test as soon as we have replaced the FhirXmlParser."); } @@ -94,7 +96,7 @@ public void ReturnsLineNumbersJson() public void RequiresHl7Namespace() { var xml = ""; - var parser = new FhirXmlParser(new ParserSettings() { PermissiveParsing = false }); + var parser = new FhirXmlParser(); try { @@ -130,7 +132,7 @@ public void AcceptXsiStuffOnRoot() parser.Parse(xml); // Now, enforce xsi: attributes are no longer accepted - parser.Settings.DisallowXsiAttributesOnRoot = true; + parser.Settings = parser.Settings with { DisallowXsiAttributesOnRoot = true }; try { @@ -220,7 +222,7 @@ public async Tasks.Task AcceptUnknownEnums() Assert.AreEqual(AdministrativeGender.Male, p.Gender.Value); // Verify that if we relax the restriction that everything still works - pser.Settings.AllowUnrecognizedEnums = true; + pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; p = await pser.ParseAsync(json); Assert.IsNotNull(p.Gender); @@ -234,7 +236,7 @@ public async Tasks.Task AcceptUnknownEnums() try { - pser.Settings.AllowUnrecognizedEnums = false; + pser.Settings = pser.Settings with { AllowUnrecognizedEnums = false }; await pser.ParseAsync(xml2); Assert.Fail(); } @@ -244,7 +246,7 @@ public async Tasks.Task AcceptUnknownEnums() } // Now, allow unknown enums and check support - pser.Settings.AllowUnrecognizedEnums = true; + pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; p = await pser.ParseAsync(xml2); Assert.ThrowsException(() => p.Gender); Assert.AreEqual("superman", p.GenderElement.ObjectValue); @@ -366,7 +368,7 @@ public async Tasks.Task NarrativeMustBeValidXml() { var json = "{\"resourceType\": \"Patient\", \"text\": {\"status\": \"generated\", \"div\": \"text without div\" } }"; - var patient = await new FhirJsonParser(new ParserSettings { PermissiveParsing = false }).ParseAsync(json); + var patient = await new FhirJsonParser().ParseAsync(json); Assert.Fail("Should have thrown on invalid Div format"); } diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs index cf42572890..61d6a3ebc6 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs @@ -308,7 +308,7 @@ public async Tasks.Task SerializeJsonWithPlainDiv() { string json = TestDataHelper.ReadTestData(@"TestPatient.json"); Assert.IsNotNull(json); - var parser = new FhirJsonParser { Settings = { PermissiveParsing = true } }; + var parser = new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)); var pat = await parser.ParseAsync(json); Assert.IsNotNull(pat); @@ -547,18 +547,5 @@ public void IncludeMandatoryInElementsSummaryTest() Assert.IsTrue(json.ContainsKey("issued")); Assert.IsTrue(json.ContainsKey("status")); } - - [TestMethod] - public void AllowParseDateWithDateTime() - { - const string patientJson = "{ \"resourceType\": \"Patient\", \"birthDate\": \"1991-02-03T11:22:33Z\" }"; - -#pragma warning disable CS0618 // Type or member is obsolete - var parser = new FhirJsonParser(new ParserSettings { TruncateDateTimeToDate = true }); -#pragma warning restore CS0618 // Type or member is obsolete - var patient = parser.Parse(patientJson); - Assert.AreEqual("1991-02-03", patient.BirthDate); - } - } } \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs index 3d0407467a..6ce78c6989 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs @@ -73,7 +73,7 @@ async static Tasks.Task XmlRoundTripAsync(T resource) where T : Resource await File.WriteAllTextAsync(xmlFile, xml); xml = await File.ReadAllTextAsync(xmlFile); - var parsed = await new FhirXmlParser(new ParserSettings { PermissiveParsing = true }).ParseAsync(xml); + var parsed = await new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(xml); return parsed; } @@ -87,7 +87,7 @@ static async Tasks.Task JsonRoundTrip(T resource) where T : Resource await File.WriteAllTextAsync(jsonFile, json); json = await File.ReadAllTextAsync(jsonFile); - var parsed = await new FhirJsonParser(new ParserSettings { PermissiveParsing = true }).ParseAsync(json); + var parsed = await new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(json); return parsed; } diff --git a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs index 07e22f67c0..fbafb2c95b 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs @@ -971,7 +971,8 @@ T loadResourceInternal(ArtifactSummary summary) where T : Resource // Also use the current PoCo parser settings var pocoSettings = PocoBuilderSettings.CreateDefault(); - _settings.ParserSettings?.CopyTo(pocoSettings); + if (_settings.ParserSettings is { } ps) + pocoSettings.CopyFrom(ps); T result = null; diff --git a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs index ae7ee08441..ff84bc79e2 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs @@ -29,9 +29,9 @@ public sealed class DirectorySourceSettings public static DirectorySourceSettings CreateDefault() => new DirectorySourceSettings(); // Instance fields - ParserSettings _parserSettings = ParserSettings.CreateDefault(); - FhirXmlParsingSettings _xmlParserSettings = FhirXmlParsingSettings.CreateDefault(); - FhirJsonParsingSettings _jsonParserSettings = FhirJsonParsingSettings.CreateDefault(); + private ParserSettings _parserSettings = new(); + private FhirXmlParsingSettings _xmlParserSettings = FhirXmlParsingSettings.CreateDefault(); + private FhirJsonParsingSettings _jsonParserSettings = FhirJsonParsingSettings.CreateDefault(); /// Default constructor. Creates a new instance with default property values. public DirectorySourceSettings() @@ -67,7 +67,7 @@ public void CopyTo(DirectorySourceSettings other) other.MultiThreaded = this.MultiThreaded; other.SummaryDetailsHarvesters = (ArtifactSummaryHarvester[])this.SummaryDetailsHarvesters?.Clone(); other.ExcludeSummariesForUnknownArtifacts = this.ExcludeSummariesForUnknownArtifacts; - other.ParserSettings = new ParserSettings(this.ParserSettings); + other.ParserSettings = this.ParserSettings with { }; other.XmlParserSettings = new FhirXmlParsingSettings(this.XmlParserSettings); other.JsonParserSettings = new FhirJsonParsingSettings(this.JsonParserSettings); } @@ -296,7 +296,7 @@ public string Mask public ParserSettings ParserSettings { get => _parserSettings; - set => _parserSettings = value ?? ParserSettings.CreateDefault(); + set => _parserSettings = value ?? new ParserSettings(); } /// diff --git a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs index 18b6b1e192..6421bcc1ea 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/WebResolver.cs @@ -8,112 +8,103 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Rest; -using Hl7.Fhir.Serialization; using Hl7.Fhir.Utility; using System; using System.Diagnostics; using System.Net; using Tasks = System.Threading.Tasks; -namespace Hl7.Fhir.Specification.Source +#nullable enable + +namespace Hl7.Fhir.Specification.Source; + +/// Fetches FHIR artifacts (Profiles, ValueSets, ...) from a FHIR server. +[DebuggerDisplay(@"\{{DebuggerDisplay,nq}}")] +public class WebResolver : IResourceResolver, IAsyncResourceResolver { - /// Fetches FHIR artifacts (Profiles, ValueSets, ...) from a FHIR server. - [DebuggerDisplay(@"\{{DebuggerDisplay,nq}}")] - public class WebResolver : IResourceResolver, IAsyncResourceResolver + /// Default request timeout in milliseconds. + public const int DefaultTimeOut = 5000; + private readonly Func _clientFactory; + + /// + /// Gets the runtime from the last call to the + /// method, if any, or null otherwise. + /// + public Exception? LastError { get; private set; } + + /// Default constructor. + public WebResolver() : this(ep => new FhirClient(ep)) { - /// Default request timeout in milliseconds. - public const int DefaultTimeOut = 5000; - private readonly Func _clientFactory; - - /// Default constructor. - public WebResolver() { } - - /// Create a new instance that supports a custom implementation. - /// - /// Factory function that should create a new instance for the specified . - /// If this parameter equals null, then the new instance creates a default instance. - /// - public WebResolver(Func fhirClientFactory) - { - _clientFactory = fhirClientFactory ?? throw Error.ArgumentNull(nameof(fhirClientFactory)); - } + // Nothing + } + + /// Create a new instance that supports a custom implementation. + /// + /// Factory function that should create a new instance for the specified . + /// If this parameter equals null, then the new instance creates a default instance. + /// + public WebResolver(Func fhirClientFactory) + { + _clientFactory = fhirClientFactory ?? throw Error.ArgumentNull(nameof(fhirClientFactory)); + } + - /// Gets or sets configuration settings that control parsing behavior. - public ParserSettings ParserSettings { get; set; } + public Resource? ResolveByUri(string uri) => TryResolveByUri(uri).Value; - /// Gets or sets the request timeout of the internal instance. - public int TimeOut { get; set; } = DefaultTimeOut; + public Resource? ResolveByCanonicalUri(string uri) => ResolveByUri(uri); - /// - /// Gets the runtime from the last call to the - /// method, if any, or null otherwise. - /// - public Exception LastError { get; private set; } + /// + /// Uses provided to construct and then tries to read the resource from that source. + /// + /// Uri to attempt resolving on + /// with an actual resource, or the . + /// Provided is null. + public ResolverResult TryResolveByUri(string uri) + { + if (uri == null) throw Error.ArgumentNull(nameof(uri)); + if (!ResourceIdentity.IsRestResourceIdentity(uri)) + return ResolverException.NotValidResourceIdentity(uri); - public Resource ResolveByUri(string uri) + var id = new ResourceIdentity(uri); + var client = _clientFactory(id.BaseUri); + + try { - return TryResolveByUri(uri).Value; - } + var resultResource = TaskHelper.Await(() => client.ReadAsync(id)) + ?? throw new InvalidOperationException("FhirClient.ReadAsync returned null, which was unexpected."); - public Resource ResolveByCanonicalUri(string uri) + resultResource.SetOrigin(uri); + LastError = null; + return new ResolverResult(resultResource); + } + catch (FhirOperationException foe) { - return ResolveByUri(uri); + LastError = foe; + return ResolverException.OperationFailed("Error occurred during Fhir operation", foe); } - - /// - /// Uses provided to construct and then tries to read the resource from that source. - /// - /// Uri to attempt resolving on - /// with an actual resource, or the . - /// Provided is null. - public ResolverResult TryResolveByUri(string uri) + catch (WebException we) { - if (uri == null) throw Error.ArgumentNull(nameof(uri)); - if (!ResourceIdentity.IsRestResourceIdentity(uri)) - return ResolverException.NotValidResourceIdentity(uri); - - var id = new ResourceIdentity(uri); - var client = _clientFactory?.Invoke(id.BaseUri) - ?? new FhirClient(id.BaseUri); - client.Settings.Timeout = this.TimeOut; - client.Settings.ParserSettings = this.ParserSettings; - - try - { - var resultResource = TaskHelper.Await(() => client.ReadAsync(id)); - resultResource.SetOrigin(uri); - LastError = null; - return resultResource; - } - catch (FhirOperationException foe) - { - LastError = foe; - return ResolverException.OperationFailed("Error occurred during Fhir operation", foe); - } - catch (WebException we) - { - LastError = we; - return ResolverException.OperationFailed("Error occurred during web operation", we); - } - // Other runtime exceptions are fatal... + LastError = we; + return ResolverException.OperationFailed("Error occurred during web operation", we); } + // Other runtime exceptions are fatal... + } - /// - public ResolverResult TryResolveByCanonicalUri(string uri) => TryResolveByUri(uri); - public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); - public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); - /// - public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); - /// - public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + /// + public ResolverResult TryResolveByCanonicalUri(string uri) => TryResolveByUri(uri); + public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); + public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + /// + public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + /// + public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); - // Allow derived classes to override - // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - protected internal virtual string DebuggerDisplay - => $"{GetType().Name}" - + (LastError != null ? $" LastError: '{LastError.Message}'" : null); + // Allow derived classes to override + // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + protected internal virtual string DebuggerDisplay + => $"{GetType().Name}" + + (LastError != null ? $" LastError: '{LastError.Message}'" : null); - } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/RoundTripAttachments.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/RoundTripAttachments.cs index 9ddf5e6ab6..48f3031db5 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/RoundTripAttachments.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/RoundTripAttachments.cs @@ -62,7 +62,7 @@ public void RoundTripAttachmentWithSize() [TestMethod] public void RoundTripAttachmentWithSizeOldParser() { - var parser = new FhirJsonParser(new ParserSettings() { PermissiveParsing = false }); + var parser = new FhirJsonParser(); var attachment = parser.Parse(_attachmentJson); #if R5 attachment.Size.Should().Be(12L); diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs index ad3d7d63d1..3a65a897db 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs @@ -27,17 +27,7 @@ public void JsonInvalidEnumerationValue() } """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var parser = new FhirJsonParser(parserSettings); + var parser = new FhirJsonParser(); var p = parser.Parse(rawData); DebugDump.OutputJson(p); } @@ -53,17 +43,7 @@ public void JsonInvalidEmptyObservation() } """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var parser = new FhirJsonParser(parserSettings); + var parser = new FhirJsonParser(); var p = parser.Parse(rawData); DebugDump.OutputJson(p); } @@ -86,17 +66,7 @@ public void JsonInvalidDateValue() } """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var parser = new FhirJsonParser(parserSettings); + var parser = new FhirJsonParser(); var p = parser.Parse(rawData); DebugDump.OutputJson(p); } @@ -118,17 +88,7 @@ public void JsonInvalidDateValueWithTime() } """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var parser = new FhirJsonParser(parserSettings); + var parser = new FhirJsonParser(); var p = parser.Parse(rawData); DebugDump.OutputJson(p); } @@ -151,17 +111,7 @@ public void JsonInvalidPropertyDetected() } """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var parser = new FhirJsonParser(parserSettings); + var parser = new FhirJsonParser(); var p = parser.Parse(rawData); DebugDump.OutputJson(p); } @@ -245,17 +195,7 @@ public void JsonInvalidDecimalValue() } """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var parser = new FhirJsonParser(parserSettings); + var parser = new FhirJsonParser(); var p = parser.Parse(rawData); DebugDump.OutputJson(p); } @@ -294,19 +234,9 @@ public void JsonMixedInvalidParseIssues() } """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var parser = new FhirJsonParser(parserSettings); + var parser = new FhirJsonParser(); var p = parser.Parse(rawData); DebugDump.OutputJson(p); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs index 5e85cfd8b3..6fd9f874c1 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs @@ -21,17 +21,7 @@ public void XMLInvalidEnumerationValue() """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var xs = new FhirXmlParser(parserSettings); + var xs = new FhirXmlParser(); var p = xs.Parse(xmlPatient); DebugDump.OutputXml(p); } @@ -50,17 +40,7 @@ public void XMLInvalidDateValue() """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var xs = new FhirXmlParser(parserSettings); + var xs = new FhirXmlParser(); var p = xs.Parse(xmlPatient); DebugDump.OutputXml(p); } @@ -80,17 +60,7 @@ public void XMLInvalidDateValueWithTime() """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var xs = new FhirXmlParser(parserSettings); + var xs = new FhirXmlParser(); var p = xs.Parse(xmlPatient); DebugDump.OutputXml(p); } @@ -112,17 +82,7 @@ public void XMLInvalidPropertyOrdering() """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var xs = new FhirXmlParser(parserSettings); + var xs = new FhirXmlParser(); var p = xs.Parse(xmlPatient); DebugDump.OutputXml(p); @@ -140,17 +100,7 @@ public void XMLInvalidPropertyDetected() """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var xs = new FhirXmlParser(parserSettings); + var xs = new FhirXmlParser(); var p = xs.Parse(xmlPatient); DebugDump.OutputXml(p); } @@ -231,19 +181,9 @@ public void XMLInvalidDecimalValue() """; - var parserSettings = new ParserSettings() - { - PermissiveParsing = false, - ExceptionHandler = (sender, notification) => - { - System.Diagnostics.Trace.WriteLine($"{notification.Severity} {notification.Message}"); - // System.Diagnostics.Trace.WriteLine($"{notification.Location}"); - // intentionally not throwing here - } - }; - var xs = new FhirXmlParser(parserSettings); + var xs = new FhirXmlParser(); var p = xs.Parse(xml); DebugDump.OutputXml(p); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXmlPoco.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXmlPoco.cs index 65d0ed2963..5452f098b2 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXmlPoco.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXmlPoco.cs @@ -14,16 +14,14 @@ public class SerializationExceptionHandlersXmlPoco private T SerializeResource(string xml) where T : Resource { - using (var reader = SerializationUtil.XmlReaderFromXmlText(xml)) + using var reader = SerializationUtil.XmlReaderFromXmlText(xml); + var settings = new ParserSettings() { - FhirXmlPocoDeserializerSettings settings = new FhirXmlPocoDeserializerSettings() - { - ValidateOnFailedParse = true, - // Validator = null - }; - FhirXmlPocoDeserializer ds = new FhirXmlPocoDeserializer(settings); - return (T)ds.DeserializeResource(reader); - } + ValidateOnFailedParse = true, + // Validator = null + }; + var ds = new FhirXmlPocoDeserializer(settings); + return (T)ds.DeserializeResource(reader); } [TestMethod] diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs index 9d25c7802a..fccf4a9c72 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs @@ -51,7 +51,7 @@ public async Tasks.Task TestPruneEmptyNodes() public async Tasks.Task CanSerializeFromPoco() { var tp = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.json")); - var pser = new FhirJsonParser(new ParserSettings { DisallowXsiAttributesOnRoot = false } ); + var pser = new FhirJsonParser(new ParserSettings { AllowXsiAttributesOnRoot = true } ); var pat = await pser.ParseAsync(tp); var output = pat.ToJson(); diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs index 177af85849..cd655aa9ea 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs @@ -58,7 +58,7 @@ public void TestElementReordering() public async Tasks.Task CanSerializeFromPoco() { var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var pser = new FhirXmlParser(new ParserSettings { DisallowXsiAttributesOnRoot = false }); + var pser = new FhirXmlParser(new ParserSettings { AllowXsiAttributesOnRoot = true }); var pat = await pser.ParseAsync(tpXml); var nav = pat.ToTypedElement(); diff --git a/src/Hl7.Fhir.Shared.Tests/Rest/FhirClientTests.cs b/src/Hl7.Fhir.Shared.Tests/Rest/FhirClientTests.cs index 218f142879..fa402c9afd 100644 --- a/src/Hl7.Fhir.Shared.Tests/Rest/FhirClientTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Rest/FhirClientTests.cs @@ -135,7 +135,7 @@ public async Tasks.Task FetchConformanceHttpClient() { using var client = new FhirClient(TestEndpoint); - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with {AllowUnrecognizedEnums = true }; var entry = await client.CapabilityStatementAsync(); Assert.IsNotNull(entry); @@ -614,7 +614,7 @@ public async Tasks.Task CallsCallbacksHttpClientHandler() static async Tasks.Task check(HttpClientEventHandler handler, FhirClient client) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; bool calledBefore = false; HttpStatusCode? status = null; @@ -667,7 +667,7 @@ public async Tasks.Task CallsCallbacksHttpClient() { using (FhirClient client = new FhirClient(TestEndpoint, httpClient: httpClient)) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; bool calledBefore = false; HttpStatusCode? status = null; @@ -707,7 +707,7 @@ public async Tasks.Task CallsCallbacksHttpClient() // And use another on the same handler to ensure that it wasn't disposed :O using (FhirClient client = new FhirClient(TestEndpoint, httpClient: httpClient)) { - client.Settings.ParserSettings.AllowUnrecognizedEnums = true; + client.Settings.ParserSettings = client.Settings.ParserSettings with { AllowUnrecognizedEnums = true }; bool calledBefore = false; HttpStatusCode? status = null; diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs index 3a3c5d282d..789b00908c 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs @@ -28,30 +28,33 @@ public void ConfigureFailOnUnknownMember() { const string xml = ""; var parser = new FhirXmlParser(); - parser.Settings.AllowUnrecognizedEnums = true; - parser.Settings.ExceptionHandler = (object source, Utility.ExceptionNotification args) => - { - Debug.WriteLine(args.Message); - if (args.Exception is StructuralTypeException && args.Severity == Utility.ExceptionSeverity.Error) - { - Assert.IsTrue(args.Exception.Message.Contains("Type checking the data: "), "Error message detected"); - throw new StructuralTypeException(args.Exception.Message.Replace("Type checking the data: ", ""), args.Exception.InnerException); - } - }; - - try - { - parser.Parse(xml); - Assert.Fail("Should have failed on unknown member"); - } - catch (StructuralTypeException ste) - { - Debug.WriteLine(ste.Message); - Assert.IsFalse(ste.Message.Contains("Type checking the data: "), "Custom error message should have removed the prefix"); - } - - parser.Settings.AcceptUnknownMembers = true; + parser.Settings = parser.Settings with { AllowUnrecognizedEnums = true }; + + // parser.Settings.ExceptionHandler = (object source, Utility.ExceptionNotification args) => + // { + // Debug.WriteLine(args.Message); + // if (args.Exception is StructuralTypeException && args.Severity == Utility.ExceptionSeverity.Error) + // { + // Assert.IsTrue(args.Exception.Message.Contains("Type checking the data: "), "Error message detected"); + // throw new StructuralTypeException(args.Exception.Message.Replace("Type checking the data: ", ""), args.Exception.InnerException); + // } + // }; + // + // try + // { + // parser.Parse(xml); + // Assert.Fail("Should have failed on unknown member"); + // } + // catch (StructuralTypeException ste) + // { + // Debug.WriteLine(ste.Message); + // Assert.IsFalse(ste.Message.Contains("Type checking the data: "), "Custom error message should have removed the prefix"); + // } + // + parser.Settings = parser.Settings with { AcceptUnknownMembers = true }; var resource = parser.Parse(xml); + + throw new NotImplementedException("Repair this unit test as soon as we have replaced the FhirXmlParser."); } @@ -94,7 +97,7 @@ public void ReturnsLineNumbersJson() public void RequiresHl7Namespace() { var xml = ""; - var parser = new FhirXmlParser(new ParserSettings() { PermissiveParsing = false }); + var parser = new FhirXmlParser(); try { @@ -220,7 +223,7 @@ public async Tasks.Task AcceptUnknownEnums() Assert.AreEqual(AdministrativeGender.Male, p.Gender.Value); // Verify that if we relax the restriction that everything still works - pser.Settings.AllowUnrecognizedEnums = true; + pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; p = await pser.ParseAsync(json); Assert.IsNotNull(p.Gender); @@ -234,7 +237,7 @@ public async Tasks.Task AcceptUnknownEnums() try { - pser.Settings.AllowUnrecognizedEnums = false; + pser.Settings = pser.Settings with { AllowUnrecognizedEnums = false }; await pser.ParseAsync(xml2); Assert.Fail(); } @@ -244,7 +247,7 @@ public async Tasks.Task AcceptUnknownEnums() } // Now, allow unknown enums and check support - pser.Settings.AllowUnrecognizedEnums = true; + pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; p = await pser.ParseAsync(xml2); Assert.ThrowsException(() => p.Gender); Assert.AreEqual("superman", p.GenderElement.ObjectValue); @@ -366,7 +369,7 @@ public async Tasks.Task NarrativeMustBeValidXml() { var json = "{\"resourceType\": \"Patient\", \"text\": {\"status\": \"generated\", \"div\": \"text without div\" } }"; - var patient = await new FhirJsonParser(new ParserSettings { PermissiveParsing = false }).ParseAsync(json); + var patient = await new FhirJsonParser().ParseAsync(json); Assert.Fail("Should have thrown on invalid Div format"); } diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs index 45e2500c8b..4f878b1e68 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs @@ -308,7 +308,7 @@ public async Tasks.Task SerializeJsonWithPlainDiv() { string json = TestDataHelper.ReadTestData(@"TestPatient.json"); Assert.IsNotNull(json); - var parser = new FhirJsonParser { Settings = { PermissiveParsing = true } }; + var parser = new FhirJsonParser( new ParserSettings().UsingMode(DeserializationMode.Recoverable)); var pat = await parser.ParseAsync(json); Assert.IsNotNull(pat); @@ -548,18 +548,5 @@ public void IncludeMandatoryInElementsSummaryTest() Assert.IsTrue(json.ContainsKey("issued")); Assert.IsTrue(json.ContainsKey("status")); } - - [TestMethod] - public void AllowParseDateWithDateTime() - { - const string patientJson = "{ \"resourceType\": \"Patient\", \"birthDate\": \"1991-02-03T11:22:33Z\" }"; - -#pragma warning disable CS0618 // Type or member is obsolete - var parser = new FhirJsonParser(new ParserSettings { TruncateDateTimeToDate = true }); -#pragma warning restore CS0618 // Type or member is obsolete - var patient = parser.Parse(patientJson); - Assert.AreEqual("1991-02-03", patient.BirthDate); - } - } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs index 3e5caad274..500d7742d1 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs @@ -76,7 +76,7 @@ async static Tasks.Task XmlRoundTripAsync(T resource) where T : Resource await File.WriteAllTextAsync(xmlFile, xml); xml = await File.ReadAllTextAsync(xmlFile); - var parsed = await new FhirXmlParser(new ParserSettings { PermissiveParsing = true }).ParseAsync(xml); + var parsed = await new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(xml); return parsed; } @@ -90,7 +90,7 @@ static async Tasks.Task JsonRoundTrip(T resource) where T : Resource await File.WriteAllTextAsync(jsonFile, json); json = await File.ReadAllTextAsync(jsonFile); - var parsed = await new FhirJsonParser(new ParserSettings { PermissiveParsing = true }).ParseAsync(json); + var parsed = await new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(json); return parsed; } diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs index f606b4241f..cfa0b0d099 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs @@ -21,10 +21,10 @@ public FhirXmlPocoDeserializer() : base(ModelInfo.ModelInspector) /// Construct a new FHIR XML deserializer, based on the currently used FHIR version. /// /// Deserialization settings - public FhirXmlPocoDeserializer(FhirXmlPocoDeserializerSettings settings) : base(ModelInfo.ModelInspector, settings) + public FhirXmlPocoDeserializer(ParserSettings settings) : base(ModelInfo.ModelInspector, settings) { } } } -#nullable restore +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs index 8a6b85be01..cc1d6a0650 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ResourceResolverTests.cs @@ -76,7 +76,8 @@ public void ResolveByUriFromZip() [TestMethod, TestCategory("IntegrationTest")] public void RetrieveWebArtifact() { - var wa = new WebResolver() { TimeOut = DefaultTimeOut }; + var settings = new FhirClientSettings { Timeout = DefaultTimeOut }; + var wa = new WebResolver(ep => new FhirClient(ep, settings)); var artifact = wa.TryResolveByUri("http://test.fhir.org/r3/StructureDefinition/Observation").Value; @@ -138,7 +139,9 @@ public void RetrieveWebArtifactCustomFhirClient() [TestMethod,TestCategory("IntegrationTest")] public async Tasks.Task RetrieveArtifactMulti() { - var resolver = new MultiResolver(source, new WebResolver() { TimeOut = DefaultTimeOut }); + var settings = new FhirClientSettings { Timeout = DefaultTimeOut }; + var wa = new WebResolver(ep => new FhirClient(ep, settings)); + var resolver = new MultiResolver(source, wa); var vs = await resolver.ResolveByCanonicalUriAsync("http://hl7.org/fhir/ValueSet/v2-0292"); Assert.IsNotNull(vs); @@ -155,10 +158,10 @@ public async Tasks.Task RetrieveArtifactMulti() [TestMethod, TestCategory("IntegrationTest")] public async Tasks.Task TestSourceCaching() { - var src = new CachedResolver( - new MultiResolver( - ZipSource.CreateValidationSource(), - new WebResolver() { TimeOut = DefaultTimeOut })); + var settings = new FhirClientSettings { Timeout = DefaultTimeOut }; + var wa = new WebResolver(ep => new FhirClient(ep, settings)); + + var src = new CachedResolver(new MultiResolver(ZipSource.CreateValidationSource(), wa)); Stopwatch sw1 = new Stopwatch(); diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs index 26c1ee9c80..c309759670 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs @@ -61,10 +61,8 @@ public class SnapshotGeneratorManifestTests PermissiveParsing = true }; - static readonly ParserSettings _parserSettings = new ParserSettings() - { - PermissiveParsing = true - }; + private static readonly ParserSettings _parserSettings = + new ParserSettings().UsingMode(DeserializationMode.Recoverable); static readonly DirectorySourceSettings _dirSourceSettings = new DirectorySourceSettings() { diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs index f7505ace3e..0dc64913e1 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ResourceResolverTests.cs @@ -76,8 +76,8 @@ public void ResolveByUriFromFhirPackage() [TestMethod, TestCategory("IntegrationTest")] public void RetrieveWebArtifact() { - var wa = new WebResolver() { TimeOut = DefaultTimeOut }; - + var settings = new FhirClientSettings { Timeout = DefaultTimeOut }; + var wa = new WebResolver(ep => new FhirClient(ep, settings)); var artifact = wa.ResolveByUri("http://test.fhir.org/r4/StructureDefinition/Observation"); Assert.IsNotNull(artifact); @@ -138,7 +138,9 @@ public void RetrieveWebArtifactCustomFhirClient() [TestMethod, TestCategory("IntegrationTest")] public async Tasks.Task RetrieveArtifactMulti() { - var resolver = new MultiResolver(source, new WebResolver() { TimeOut = DefaultTimeOut }); + var settings = new FhirClientSettings { Timeout = DefaultTimeOut }; + var wa = new WebResolver(ep => new FhirClient(ep, settings)); + var resolver = new MultiResolver(source, wa); var vs = await resolver.ResolveByUriAsync("http://hl7.org/fhir/ValueSet/v2-0292"); Assert.IsNotNull(vs); @@ -155,10 +157,10 @@ public async Tasks.Task RetrieveArtifactMulti() [TestMethod, TestCategory("IntegrationTest")] public async Tasks.Task TestSourceCaching() { - var src = new CachedResolver( - new MultiResolver( - ZipSource.CreateValidationSource(), - new WebResolver() { TimeOut = DefaultTimeOut })); + var settings = new FhirClientSettings { Timeout = DefaultTimeOut }; + var wa = new WebResolver(ep => new FhirClient(ep, settings)); + + var src = new CachedResolver(new MultiResolver(ZipSource.CreateValidationSource(),wa)); Stopwatch sw1 = new Stopwatch(); diff --git a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs index 8254837560..d7ff29e64c 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs @@ -54,7 +54,7 @@ public void TryDeserializePrimitiveValue(string xmlPrimitive, Type fhirTargetTyp reader.MoveToContent(); //reader.MoveToFirstAttribute(); - var deserializer = getTestDeserializer(new FhirXmlPocoDeserializerSettings()); + var deserializer = getTestDeserializer(new ParserSettings()); var classMapping = ModelInfo.ModelInspector.ImportType(fhirTargetType)!; var target = (PrimitiveType)classMapping.Factory()!; var state = new FhirXmlPocoDeserializerState(); @@ -479,7 +479,7 @@ public void TestValidatorIsCalledDuringDeserialization() var reader = constructReader(xml); reader.Read(); - var serializer = getTestDeserializer(new FhirXmlPocoDeserializerSettings { Validator = validator }); + var serializer = getTestDeserializer(new ParserSettings { Validator = validator }); serializer.TryDeserializeResource(reader, out _, out var issues); var errors = issues.ToList(); @@ -504,7 +504,7 @@ public void TestNewXmlParserNarrativeParsing() var actual = FhirXmlSerializer.Default.SerializeToString(patient); // now parse this back out with the new parser - BaseFhirXmlPocoDeserializer ds = getTestDeserializer(new FhirXmlPocoDeserializerSettings()); + BaseFhirXmlPocoDeserializer ds = getTestDeserializer(new ParserSettings()); var np = ds.DeserializeResource(actual).Should().BeOfType().Subject; Assert.AreEqual(patient.Text.Div, np.Text.Div, "New narrative should be the same"); @@ -538,7 +538,7 @@ private static XmlReader constructReader(string xml) return reader; } - private static BaseFhirXmlPocoDeserializer getTestDeserializer(FhirXmlPocoDeserializerSettings settings) => + private static BaseFhirXmlPocoDeserializer getTestDeserializer(ParserSettings settings) => new(typeof(Patient).Assembly, settings); [TestMethod] diff --git a/src/firely-net-sdk-tests.props b/src/firely-net-sdk-tests.props index 04a0292454..9047700561 100644 --- a/src/firely-net-sdk-tests.props +++ b/src/firely-net-sdk-tests.props @@ -1,7 +1,7 @@ net9.0 - 12.0 + 13.0 true diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index 7dfdaea68e..e5ff24080a 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -33,7 +33,7 @@ - 12.0 + 13.0 True Debug;Release;FullDebug true From f9d19335bca48166ac3405928ca096f23e468fbd Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Fri, 7 Mar 2025 13:25:49 +0100 Subject: [PATCH 2/8] WIP --- ...FhirClientSerializationEngineExtensions.cs | 2 +- .../BaseFhirJsonPocoDeserializer.cs | 43 +++- .../Serialization/BaseFhirParser.cs | 35 --- .../BaseFhirXmlPocoDeserializer.cs | 78 ++++-- .../DeserializationFailedException.cs | 73 +++--- .../Serialization/FhirJsonConverterOptions.cs | 92 +------ .../FhirJsonConverterOptionsExtensions.cs | 6 +- .../Serialization/FhirXmlException.cs | 11 +- .../Serialization/JsonParsingExtensions.cs | 90 +++++++ .../Serialization/ParserSettings.cs | 12 +- .../PocoDeserializationExtensions.cs | 197 ++++++++++++++ .../Serialization/XmlParsingExtensions.cs | 86 +++++++ .../{ => elementmodel}/FhirJsonBuilder.cs | 0 .../{ => elementmodel}/FhirJsonNode.cs | 0 .../{ => elementmodel}/FhirJsonNodeFactory.cs | 0 .../FhirJsonParsingSettings.cs | 0 .../{ => elementmodel}/FhirXmlBuilder.cs | 0 .../{ => elementmodel}/FhirXmlNode.cs | 0 .../{ => elementmodel}/FhirXmlNodeFactory.cs | 0 .../FhirXmlParsingSettings.cs | 0 .../JsonDocumentExtensions.cs | 0 .../{ => elementmodel}/XObjectExtensions.cs | 0 .../XObjectFhirXmlExtensions.cs | 0 .../XmlDocumentExtensions.cs | 0 .../XmlSerializationDetails.cs | 0 .../FhirSerializationEngineFactory.Legacy.cs | 28 +- .../engine/FhirSerializationEngineFactory.cs | 2 +- .../engine/PocoDeserializationExtensions.cs | 186 -------------- .../Utility/SerializationUtil.cs | 4 + .../Serialization/ResourceParsingTests.cs | 8 +- .../ElementModel/PocoTypedElementTests.cs | 4 +- src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs | 12 +- .../Serialization/ResourceParsingTests.cs | 69 ++--- .../Serialization/SerializationTests.cs | 47 ++-- .../ValueQuantityParsingTests.cs | 4 +- .../TestDataVersionCheck.cs | 5 +- ....Fhir.Serialization.Shared.Tests.projitems | 2 - .../SerializationExcexptionHandlersJson.cs | 242 ------------------ .../SerializationExcexptionHandlersXml.cs | 189 -------------- .../SerializeDemoPatientJson.cs | 10 +- .../SerializeDemoPatientXml.cs | 10 +- .../SerializePartialTree.cs | 2 +- .../ElementModel/PocoTypedElementTests.cs | 6 +- .../Model/DeepCopyTest.cs | 6 +- .../Serialization/ResourceParsingTests.cs | 63 ++--- .../Serialization/SerializationTests.cs | 47 ++-- .../ValueQuantityParsingTests.cs | 4 +- .../TestDataVersionCheck.cs | 22 +- .../Serialization/FhirJsonParser.cs | 51 +--- .../Serialization/FhirXmlParser.cs | 51 +--- .../Serialization/FhirXmlPocoDeserializer.cs | 38 ++- .../Source/ArtifactSourceTests.cs | 18 +- .../SpecificationTestDataVersionCheck.cs | 135 +++++----- .../SnapshotGeneratorManifestTests.cs | 2 +- .../Source/ArtifactSourceTests.cs | 18 +- .../SpecificationTestDataVersionCheck.cs | 11 +- .../FhirXmlDeserializationTests.cs | 2 +- .../PocoTests/FhirPathTestDataVersionCheck.cs | 7 +- .../TestData/bundle-contained-references.xml | 8 +- 59 files changed, 827 insertions(+), 1211 deletions(-) delete mode 100644 src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs create mode 100644 src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs create mode 100644 src/Hl7.Fhir.Base/Serialization/PocoDeserializationExtensions.cs create mode 100644 src/Hl7.Fhir.Base/Serialization/XmlParsingExtensions.cs rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirJsonBuilder.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirJsonNode.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirJsonNodeFactory.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirJsonParsingSettings.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirXmlBuilder.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirXmlNode.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirXmlNodeFactory.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/FhirXmlParsingSettings.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/JsonDocumentExtensions.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/XObjectExtensions.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/XObjectFhirXmlExtensions.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/XmlDocumentExtensions.cs (100%) rename src/Hl7.Fhir.Base/Serialization/{ => elementmodel}/XmlSerializationDetails.cs (100%) delete mode 100644 src/Hl7.Fhir.Base/Serialization/engine/PocoDeserializationExtensions.cs delete mode 100644 src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs delete mode 100644 src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs diff --git a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs index 4387612b0b..990018ed14 100644 --- a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs +++ b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs @@ -97,7 +97,7 @@ public static BaseFhirClient WithOstrichModeSerializer(this BaseFhirClient clien public static BaseFhirClient WithCustomIgnoreListSerializer(this BaseFhirClient client, string[] ignoreList) { var xmlSettings = new ParserSettings().Ignoring(ignoreList); - var jsonSettings = new FhirJsonConverterOptions().Ignoring(ignoreList); + var jsonSettings = (FhirJsonConverterOptions)new FhirJsonConverterOptions().Ignoring(ignoreList); client.Settings.SerializationEngine = FhirSerializationEngineFactory.Custom(client.Inspector, jsonSettings, xmlSettings); diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs index b600e441fd..8bbc0255dc 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs @@ -23,11 +23,7 @@ namespace Hl7.Fhir.Serialization; -/// -/// Deserializes a byte stream into FHIR POCO objects. -/// -/// The serializer uses the format documented in https://www.hl7.org/fhir/json.html. -public class BaseFhirJsonPocoDeserializer +public class BaseFhirJsonPocoDeserializer : BaseFhirJsonParser { /// /// Initializes an instance of the deserializer. @@ -35,7 +31,8 @@ public class BaseFhirJsonPocoDeserializer /// Assembly containing the POCO classes to be used for deserialization. [Obsolete("Use the constructor that takes a ModelInspector instead. " + "You can find the right ModelInspector for an assembly by calling ModelInspector.ForAssembly(assembly).")] - public BaseFhirJsonPocoDeserializer(Assembly assembly) : this(ModelInspector.ForAssembly(assembly), new FhirJsonConverterOptions()) + public BaseFhirJsonPocoDeserializer(Assembly assembly) : this(ModelInspector.ForAssembly(assembly), + new FhirJsonConverterOptions()) { // Nothing } @@ -55,15 +52,43 @@ public class BaseFhirJsonPocoDeserializer /// The containing the POCO classes to be used for deserialization. /// A settings object to be used by this instance. public BaseFhirJsonPocoDeserializer(ModelInspector inspector, FhirJsonConverterOptions settings) + : base(inspector, settings) + { + // nothing + } +} + + +/// +/// Deserializes Json into FHIR POCO objects. +/// +/// The serializer uses the format documented in https://www.hl7.org/fhir/json.html. +public class BaseFhirJsonParser +{ + /// + /// Initializes an instance of the deserializer. + /// + /// The containing the POCO classes to be used for deserialization. + public BaseFhirJsonParser(ModelInspector inspector) : this(inspector, new ParserSettings()) + { + // nothing + } + + /// + /// Initializes an instance of the deserializer. + /// + /// The containing the POCO classes to be used for deserialization. + /// A settings object to be used by this instance. + public BaseFhirJsonParser(ModelInspector inspector, ParserSettings? settings) { - Settings = settings; + Settings = settings ?? new ParserSettings(); _inspector = inspector; } /// /// The settings that were passed to the constructor. /// - public FhirJsonConverterOptions Settings { get; } + public ParserSettings Settings { get; set; } private const string INSTANCE_VALIDATION_KEY_SUFFIX = ":instance"; private const string PROPERTY_VALIDATION_KEY_SUFFIX = ":property"; @@ -737,7 +762,7 @@ JsonTokenType.String when string.IsNullOrEmpty(reader.GetString()) => (reader.Ge /// This function tries to map from the json-format "generic" number to the kind of numeric type defined in the POCO. /// /// Reader must be positioned on a number token. This function will not move the reader to the next token. - private static object? tryGetMatchingNumber(ref Utf8JsonReader reader, Type implementingType) + private static object tryGetMatchingNumber(ref Utf8JsonReader reader, Type implementingType) { if (reader.TokenType != JsonTokenType.Number) throw new InvalidOperationException($"Cannot read a numeric when reader is on a {reader.TokenType}. " + diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs deleted file mode 100644 index 38a93006c2..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirParser.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2018, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -#nullable enable - -namespace Hl7.Fhir.Serialization; - -public class BaseFhirParser -{ - internal static PocoBuilderSettings BuildPocoBuilderSettings(ParserSettings ps) => - new() - { - AllowUnrecognizedEnums = ps.AllowUnrecognizedEnums, - IgnoreUnknownMembers = ps.AcceptUnknownMembers, - }; - - internal static FhirXmlParsingSettings BuildXmlParsingSettings(ParserSettings settings) => - new() - { - DisallowSchemaLocation = !settings.AllowXsiAttributesOnRoot, -#pragma warning disable CS0618 // Type or member is obsolete - PermissiveParsing = settings.PermissiveParsing, -#pragma warning restore CS0618 // Type or member is obsolete - }; - - internal static FhirJsonParsingSettings BuildJsonParserSettings(ParserSettings settings) => -#pragma warning disable CS0618 // Type or member is obsolete - new() { AllowJsonComments = false, PermissiveParsing = settings.PermissiveParsing }; -#pragma warning restore CS0618 // Type or member is obsolete -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs index a5c579b70d..831df2a779 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs @@ -1,4 +1,12 @@ -#nullable enable +/* + * Copyright (c) 2021, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; @@ -14,7 +22,7 @@ namespace Hl7.Fhir.Serialization; -public class BaseFhirXmlPocoDeserializer +public class BaseFhirXmlPocoDeserializer : BaseFhirXmlParser { /// /// Initializes an instance of the deserializer. @@ -25,41 +33,70 @@ public class BaseFhirXmlPocoDeserializer // nothing } + /// + /// Initializes an instance of the deserializer. + /// + /// Assembly containing the POCO classes to be used for deserialization. + /// A settings object to be used by this instance. + public BaseFhirXmlPocoDeserializer(Assembly assembly, ParserSettings settings) + : this(ModelInspector.ForAssembly(assembly), settings) + { + // Nothing + } + + /// /// Initializes an instance of the deserializer. /// /// The containing the POCO classes to be used for deserialization. - public BaseFhirXmlPocoDeserializer(ModelInspector inspector) : this(inspector, new()) + public BaseFhirXmlPocoDeserializer(ModelInspector inspector) : this(inspector, new ParserSettings()) { // nothing } + /// /// Initializes an instance of the deserializer. /// - /// Assembly containing the POCO classes to be used for deserialization. + /// The containing the POCO classes to be used for deserialization. /// A settings object to be used by this instance. - public BaseFhirXmlPocoDeserializer(Assembly assembly, ParserSettings settings) + public BaseFhirXmlPocoDeserializer(ModelInspector inspector, ParserSettings settings) : base(inspector, settings) + { + // Nothing + } +} + +/// +/// Deserializes XML into FHIR POCO objects. +/// +/// The serializer uses the format documented in https://www.hl7.org/fhir/xml.html. +public class BaseFhirXmlParser +{ + /// + /// Initializes an instance of the deserializer. + /// + /// The containing the POCO classes to be used for deserialization. + public BaseFhirXmlParser(ModelInspector inspector) : this(inspector, new ParserSettings()) { - Settings = settings; - _inspector = ModelInspector.ForAssembly(assembly ?? throw new ArgumentNullException(nameof(assembly))); + // nothing } + /// /// Initializes an instance of the deserializer. /// /// The containing the POCO classes to be used for deserialization. /// A settings object to be used by this instance. - public BaseFhirXmlPocoDeserializer(ModelInspector inspector, ParserSettings settings) + public BaseFhirXmlParser(ModelInspector inspector, ParserSettings? settings) { - Settings = settings; + Settings = settings ?? new ParserSettings(); _inspector = inspector; } /// /// The settings that were passed to the constructor. /// - public ParserSettings Settings { get; } + public ParserSettings Settings { get; set; } private readonly ModelInspector _inspector; @@ -262,12 +299,12 @@ internal void DeserializeElementInto(Base target, ClassMapping mapping, XmlReade if (error is null && propMapping is not null && validNamespace) { state.Path.EnterElement(propMapping.Name, !propMapping.IsCollection ? null : 0, propMapping.IsPrimitive); - var (order, incorrectOrder) = checkOrder(reader, state, highestOrder, propMapping); + var (order, _) = checkOrder(reader, state, highestOrder, propMapping); highestOrder = order; try { - deserializePropertyValue(target, reader, state, oldErrors, incorrectOrder, propMapping, propValueMapping); + deserializePropertyValue(target, reader, state, oldErrors, propMapping, propValueMapping); } finally { @@ -310,6 +347,7 @@ internal void DeserializeElementInto(Base target, ClassMapping mapping, XmlReade private static (int highestOrder, bool incorrectOrder) checkOrder(XmlReader reader, FhirXmlPocoDeserializerState state, int highestOrder, PropertyMapping propMapping) { var incorrectOrder = false; + //check if element is in the correct order. if (propMapping.Order >= highestOrder) { @@ -320,10 +358,11 @@ private static (int highestOrder, bool incorrectOrder) checkOrder(XmlReader read state.Errors.Add(ERR.ELEMENT_OUT_OF_ORDER(reader, state.Path.GetInstancePath(), reader.LocalName)); incorrectOrder = true; } + return (highestOrder, incorrectOrder); } - private void deserializePropertyValue(Base target, XmlReader reader, FhirXmlPocoDeserializerState state, int oldErrors, bool incorrectOrder, PropertyMapping propMapping, ClassMapping? propValueMapping) + private void deserializePropertyValue(Base target, XmlReader reader, FhirXmlPocoDeserializerState state, int oldErrors, PropertyMapping propMapping, ClassMapping? propValueMapping) { var (lineNumber, position) = reader.GenerateLineInfo(); var name = reader.LocalName; @@ -436,7 +475,7 @@ private void readIntoList(IList targetList, ClassMapping propValueMapping, Prope return result; } - private static void readAttributes(Base target, ClassMapping propValueMapping, XmlReader reader, FhirXmlPocoDeserializerState state) + private void readAttributes(Base target, ClassMapping propValueMapping, XmlReader reader, FhirXmlPocoDeserializerState state) { var elementName = reader.LocalName; //move into first attribute @@ -452,7 +491,8 @@ private static void readAttributes(Base target, ClassMapping propValueMapping, X } else if (reader is { LocalName: "schemaLocation", NamespaceURI: "http://www.w3.org/2001/XMLSchema-instance" }) { - state.Errors.Add(ERR.SCHEMALOCATION_DISALLOWED(reader, state.Path.GetInstancePath())); + if(Settings.DisallowXsiAttributesOnRoot) + state.Errors.Add(ERR.SCHEMALOCATION_DISALLOWED(reader, state.Path.GetInstancePath())); } else { @@ -462,7 +502,7 @@ private static void readAttributes(Base target, ClassMapping propValueMapping, X state.Path.EnterElement(propMapping.Name, !propMapping.IsCollection ? null : 0, propMapping.IsPrimitive); try { - readAttribute(target, propMapping!, elementName, reader, state); + readAttribute(target, propMapping, elementName, reader, state); } finally { @@ -499,7 +539,7 @@ private static void readAttribute(Base target, PropertyMapping propMapping, stri if (string.IsNullOrEmpty(trimmedValue)) state.Errors.Add(ERR.ATTRIBUTE_HAS_EMPTY_VALUE(reader, state.Path.GetInstancePath())); - var parsedValue = parsePrimitiveValue(trimmedValue, propMapping.ImplementingType, state.Path); + var parsedValue = parsePrimitiveValue(trimmedValue, propMapping.ImplementingType); if (target is PrimitiveType primitive && propMapping.Name == "value") { @@ -512,7 +552,7 @@ private static void readAttribute(Base target, PropertyMapping propMapping, stri } } - private static object parsePrimitiveValue(string trimmedValue, Type implementingType, PathStack pathStack) + private static object parsePrimitiveValue(string trimmedValue, Type implementingType) { if (implementingType == typeof(FhirString)) return new FhirString(trimmedValue); @@ -578,7 +618,7 @@ private static (PropertyMapping? propMapping, ClassMapping? propValueMapping, Fh (ClassMapping? propertyValueMapping, FhirXmlException? error) = propertyMapping.Choice switch { ChoiceType.None or ChoiceType.ResourceChoice => - inspector.FindOrImportClassMapping(propertyMapping.GetInstantiableType()) is ClassMapping m + inspector.FindOrImportClassMapping(propertyMapping.GetInstantiableType()) is { } m ? (m, null) : throw new InvalidOperationException($"Encountered property type {propertyMapping.ImplementingType} for which no mapping was found in the model assemblies. " + reader.GenerateLocationMessage()), ChoiceType.DatatypeChoice => getChoiceClassMapping(reader), diff --git a/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs b/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs index a8135bb86a..f5575df445 100644 --- a/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs +++ b/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs @@ -15,45 +15,42 @@ #nullable enable -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +/// +/// Contains the list of errors detected while deserializing data into .NET POCOs. +/// +/// The deserializers will continue deserialization in the face of errors, and so will collect the full +/// set of errors detected using this aggregate exception. +public class DeserializationFailedException : Exception { - /// - /// Contains the list of errors detected while deserializing data into .NET POCOs. - /// - /// The deserializers will continue deserialization in the face of errors, and so will collect the full - /// set of errors detected using this aggregate exception. - public class DeserializationFailedException : Exception + public DeserializationFailedException(Base? partialResult, CodedException innerException) : + this(partialResult, [innerException]) { - public DeserializationFailedException(Base? partialResult, CodedException innerException) : - this(partialResult, new[] { innerException }) - { - // Nothing - } - - public DeserializationFailedException(Base? partialResult, IEnumerable innerExceptions) : - base(generateMessage(innerExceptions)) - { - PartialResult = partialResult; - Exceptions = innerExceptions.ToList(); - } - - private static string generateMessage(IEnumerable exceptions) - { - string b = "One or more errors occurred."; - if (exceptions.Any()) - b += " " + string.Join(" ", exceptions.Select(e => $"({e.Message})")); - - return b; - } - - - /// - /// The best-effort result of deserialization. Maybe invalid or incomplete because of the errors encountered. - /// - public Base? PartialResult { get; private set; } - - public IReadOnlyCollection Exceptions { get; } + // Nothing } -} -#nullable restore + public DeserializationFailedException(Base? partialResult, IEnumerable innerExceptions) : + base(generateMessage(innerExceptions)) + { + PartialResult = partialResult; + Exceptions = innerExceptions.ToList(); + } + + private static string generateMessage(IEnumerable exceptions) + { + string b = "One or more errors occurred."; + if (exceptions.Any()) + b += " " + string.Join(" ", exceptions.Select(e => $"({e.Message})")); + + return b; + } + + + /// + /// The best-effort result of deserialization. Maybe invalid or incomplete because of the errors encountered. + /// + public Base? PartialResult { get; private set; } + + public IReadOnlyCollection Exceptions { get; } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptions.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptions.cs index 726bfb9329..11cb6b1a40 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptions.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptions.cs @@ -9,105 +9,15 @@ #nullable enable -using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; -using System; -using System.Collections.Generic; - namespace Hl7.Fhir.Serialization; /// /// Specify the optional features for Json deserialization. /// -public record FhirJsonConverterOptions +public record FhirJsonConverterOptions : ParserSettings { /// /// Specifies the filter to use for summary serialization. /// public SerializationFilter? SummaryFilter { get; init; } = null; - - /// - /// If set, this validator is invoked before the value is set in the object under construction to validate - /// and possibly alter the value. Setting this property to null will disable validation completely. - /// - public IDeserializationValidator? Validator { get; init; } = DataAnnotationDeserialzationValidator.Default; - - /// - /// Specifies a filter that can be used to filter out exceptions that are not considered fatal. The filter - /// returns true for exceptions that should be ignored, and false otherwise. - /// - public Predicate? ExceptionFilter { get; init; } = null; - - /// - /// Perform the parse time validation on the deserialized object even if parsing issues occurred. - /// - /// - /// This is useful for "strict mode" single-pass validators and may result in spurious error messages - /// from validating incomplete content. - /// - public bool ValidateOnFailedParse { get; init; } = false; - - /// - /// During parsing any contained resources (such as those in a bundle) that encounter some form of parse/validation exception - /// will have a List<CodedException> of these exceptions added as an annotation to the child resource. - /// - /// - /// This is primarily added to ease the processing of bundles during a batch submission. - /// (without requiring processing fhirpath expressions in the issues in the parsing operation outcome to determine if a - /// resource was clean and possibly ok to process). - /// - public bool AnnotateResourceParseExceptions { get; init; } = false; - - /// - /// For performance reasons, validation of Xhtml again the rules specified in the FHIR - /// specification for Narrative (http://hl7.org/fhir/narrative.html#2.4.0) is turned off by - /// default. Set this property to any other value than - /// to perform validation. - /// - public NarrativeValidationKind NarrativeValidation { get; init; } = NarrativeValidationKind.None; - - /// - /// Enables all validation rules that are available. - /// - /// The selected mode to use, see . - /// How strict to validate the XHtml in FHIR Narrative. Only relevant in mode - public FhirJsonConverterOptions WithMode(DeserializationMode mode, - NarrativeValidationKind nvk = NarrativeValidationKind.FhirXhtml) => - mode switch - { - DeserializationMode.Strict => this with - { - ExceptionFilter = null, // No exceptions are ignored - NarrativeValidation = nvk - }, - DeserializationMode.BackwardsCompatible => this with - { - ExceptionFilter = CodedExceptionFilters.IsBackwardsCompatibilityIssue, - NarrativeValidation = NarrativeValidationKind.None - }, - DeserializationMode.Recoverable => this with - { - ExceptionFilter = CodedExceptionFilters.IsRecoverableIssue, - NarrativeValidation = NarrativeValidationKind.None - }, - DeserializationMode.Ostrich => this with - { - Validator = null, // Disable all validations, we don't care. - ExceptionFilter = _ => true, // If there are still errors, ignore. - NarrativeValidation = NarrativeValidationKind.None // We don't care about the narrative. - }, - _ => throw Error.NotSupported("Unknown deserialization mode.") - }; - - /// - /// Alters the options to enforce specific parsing exceptions. - /// - public FhirJsonConverterOptions Enforcing(IEnumerable toEnforce) => - this with { ExceptionFilter = this.ExceptionFilter.Enforce(toEnforce) }; - - /// - /// Alters the options to ignore specific parsing exceptions. - /// - public FhirJsonConverterOptions Ignoring(IEnumerable toIgnore) => - this with { ExceptionFilter = this.ExceptionFilter.Ignore(toIgnore) }; } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptionsExtensions.cs b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptionsExtensions.cs index a17143cb5d..9fa8f8cf56 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptionsExtensions.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirJsonConverterOptionsExtensions.cs @@ -117,7 +117,7 @@ public static JsonSerializerOptions ForFhir(this JsonSerializerOptions options, public static JsonSerializerOptions UsingMode(this JsonSerializerOptions options, DeserializationMode mode) { var ourConverter = getCustomFactoryFromList(options.Converters); - ourConverter.Reconfigure(ourConverter.CurrentOptions.WithMode(mode)); + ourConverter.Reconfigure((FhirJsonConverterOptions)ourConverter.CurrentOptions.UsingMode(mode)); return options; } @@ -132,7 +132,7 @@ public static JsonSerializerOptions UsingMode(this JsonSerializerOptions options public static JsonSerializerOptions Enforcing(this JsonSerializerOptions options, IEnumerable toEnforce) { var ourConverter = getCustomFactoryFromList(options.Converters); - ourConverter.Reconfigure(ourConverter.CurrentOptions.Enforcing(toEnforce)); + ourConverter.Reconfigure((FhirJsonConverterOptions)ourConverter.CurrentOptions.Enforcing(toEnforce)); return options; } @@ -147,7 +147,7 @@ public static JsonSerializerOptions Enforcing(this JsonSerializerOptions options public static JsonSerializerOptions Ignoring(this JsonSerializerOptions options, IEnumerable toIgnore) { var ourConverter = getCustomFactoryFromList(options.Converters); - ourConverter.Reconfigure(ourConverter.CurrentOptions.Ignoring(toIgnore)); + ourConverter.Reconfigure((FhirJsonConverterOptions)ourConverter.CurrentOptions.Ignoring(toIgnore)); return options; } diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs index 1ad94f3dfc..3642a04f17 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs @@ -1,4 +1,13 @@ -#nullable enable +/* + * Copyright (c) 2021, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + + +#nullable enable using Hl7.Fhir.Model; using Hl7.Fhir.Utility; diff --git a/src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs b/src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs new file mode 100644 index 0000000000..4f1e1d65be --- /dev/null +++ b/src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; +using Newtonsoft.Json; +using System; +using System.Threading.Tasks; + +namespace Hl7.Fhir.Serialization; + +public static class JsonParsingExtensions +{ + /// + public static T Parse(this BaseFhirJsonParser parser, string json) where T : Base + => (T)parser. Parse(json, typeof(T)); + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static async Task ParseAsync(this BaseFhirJsonParser parser, string json) where T : Base + => (T)await parser.ParseAsync(json, typeof(T)).ConfigureAwait(false); + + /// + public static T Parse(this BaseFhirJsonParser parser, JsonReader reader) where T : Base + => (T)parser.Parse(reader, typeof(T)); + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static async Task ParseAsync(this BaseFhirJsonParser parser, JsonReader reader) where T : Base + => (T)await parser.ParseAsync(reader, typeof(T)).ConfigureAwait(false); + + /// + /// Deserializes the given Json string into a FHIR resource or datatype. + /// + /// The parser for which this extension method can be called. + /// A string of FHIR Json. + /// Optional. Can be used when deserializing datatypes and + /// will be ignored when parsing resources. + /// Note that there is no official serialization for FHIR datatypes, just for FHIR resources, so + /// deserializing non-resource types might not always work. + public static Base Parse(this BaseFhirJsonParser parser, string json, Type? dataType = null) + { + using var jsonReader = SerializationUtil.JsonReaderFromJsonText(json); + return parse(parser, jsonReader, dataType); + } + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static Task ParseAsync(this BaseFhirJsonParser parser, string json, Type? dataType = null) => + Task.FromResult(parser.Parse(json, dataType)); + + /// + /// Deserializes the Json passed in the JsonReader into a FHIR resource or datatype. + /// + /// The parser for which this extension method can be called. + /// An JsonReader positioned on the first element, or the beginning of the stream. + /// Optional. Can be used when deserializing datatypes and + /// will be ignored when parsing resources. + /// Note that there is no official serialization for FHIR datatypes, just for FHIR resources, so + /// deserializing non-resource types might not always work. + public static Base Parse(this BaseFhirJsonParser parser, JsonReader reader, Type? dataType = null) => + parse(parser, reader, dataType); + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static Task ParseAsync(this BaseFhirJsonParser parser, JsonReader reader, Type? dataType = null) => + Task.FromResult(parser.Parse(reader, dataType)); + + private static Base parse(BaseFhirJsonParser parser, JsonReader json, Type? dataType = null) => + parse(parser, json.ToString()!, dataType); + + private static Base parse(BaseFhirJsonParser parser, string json, Type? dataType = null) + { + if (dataType is null || typeof(Resource).IsAssignableFrom(dataType)) + return parser.DeserializeResource(json); + + return parser.DeserializeObject(dataType, json); + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs index d0357469b8..0cb767317f 100644 --- a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs +++ b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs @@ -21,9 +21,8 @@ public record ParserSettings /// Specifies a filter that can be used to filter out exceptions that are not considered fatal. The filter /// returns true for exceptions that should be ignored, and false otherwise. /// - /// Setting , , - /// or will augment - /// this filter to reflect these settings. + /// Setting , or + /// will augment this filter to reflect these settings. public Predicate? ExceptionFilter { get => augmentFilter(); @@ -65,7 +64,6 @@ public Predicate? ExceptionFilter var ignored = new List(); // Simulate the old behaviour by selectively enforcing errors. - if(AllowXsiAttributesOnRoot) ignored.Add(FhirXmlException.SCHEMALOCATION_DISALLOWED_CODE); if(AllowUnrecognizedEnums) ignored.Add(CodedValidationException.INVALID_CODED_VALUE_CODE); if(AllowUnrecognizedEnums) ignored.AddRange( [FhirXmlException.UNKNOWN_ELEMENT_CODE, FhirXmlException.UNKNOWN_ATTRIBUTE_CODE]); @@ -79,11 +77,9 @@ public Predicate? ExceptionFilter } /// - /// Suppress an error when an xsi:schemaLocation is encountered. + /// Raise an error when an xsi:schemaLocation is encountered. /// - /// This is the same as calling with - /// FhirXmlException.SCHEMALOCATION_DISALLOWED_CODE as the argument. - public bool AllowXsiAttributesOnRoot { get; init; } + public bool DisallowXsiAttributesOnRoot { get; set; } /// /// Do not throw when encountering values not parseable as a member of an enumeration in a Poco. diff --git a/src/Hl7.Fhir.Base/Serialization/PocoDeserializationExtensions.cs b/src/Hl7.Fhir.Base/Serialization/PocoDeserializationExtensions.cs new file mode 100644 index 0000000000..d7c3c96bac --- /dev/null +++ b/src/Hl7.Fhir.Base/Serialization/PocoDeserializationExtensions.cs @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2023, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Text.Json; +using System.Xml; + +namespace Hl7.Fhir.Serialization; + +/// +/// Extension methods that provide additional utility methods for deserialization on top of +/// the TryDeserialize() functions of the json and xml deserializers. +/// +public static class PocoDeserializationExtensions +{ + /// + /// Deserialize the FHIR xml from the reader and create a new POCO resource containing the data from the reader. + /// + /// The deserializer to use. + /// An xml reader positioned on the first element, or the beginning of the stream. + /// A fully initialized POCO with the data from the reader. + public static Resource DeserializeResource(this BaseFhirXmlParser deserializer, XmlReader reader) => + deserializer.TryDeserializeResource(reader, out var instance, out var issues) ? + instance : throw new DeserializationFailedException(instance, issues); + + /// + /// Deserialize the FHIR xml from a string and create a new POCO resource containing the data from the reader. + /// + /// The deserializer to use. + /// A string containing the XML from which to deserialize the resource. + /// A fully initialized POCO with the data from the reader. + public static Resource DeserializeResource(this BaseFhirXmlParser deserializer, string data) + { + using var xmlReader = SerializationUtil.XmlReaderFromXmlText(data); + return deserializer.DeserializeResource(xmlReader); + } + + /// + /// Deserialize the FHIR xml from a string and create a new POCO resource containing the data from the reader. + /// + /// The deserializer to use. + /// A string containing the XML from which to deserialize the resource. + /// The result of deserialization. May be incomplete when there are issues. + /// Issues encountered while deserializing. Will be empty when the function returns true. + /// A fully initialized POCO with the data from the reader. + public static bool TryDeserializeResource( + this BaseFhirXmlParser deserializer, + string data, + [NotNullWhen(true)] out Resource? instance, + out IEnumerable issues) + { + using var xmlReader = SerializationUtil.XmlReaderFromXmlText(data); + return deserializer.TryDeserializeResource(xmlReader, out instance, out issues); + } + + /// + /// Deserialize the FHIR xml from a reader and create a new POCO resource containing the data from the reader. + /// + /// The deserializer to use. + /// A reader for the XML from which to deserialize the resource. + /// The result of deserialization. May be incomplete when there are issues. + /// Issues encountered while deserializing. Will be empty when the function returns true. + /// A fully initialized POCO with the data from the reader. + public static bool TryDeserializeResource( + this BaseFhirXmlParser deserializer, + XmlReader reader, + out Resource? instance, + out IEnumerable issues) + { + return deserializer.TryDeserializeResource(reader, out instance, out issues); + } + + /// + /// Reads a (subtree) of serialized FHIR Xml data into a POCO object. + /// + /// The deserializer to use. + /// The type of POCO to construct and deserialize + /// An xml reader positioned on the first element, or the beginning of the stream. + /// A fully initialized POCO with the data from the reader. + public static Base DeserializeElement(this BaseFhirXmlParser deserializer, Type targetType, XmlReader reader) => + deserializer.TryDeserializeElement(targetType, reader, out var instance, out var issues) ? + instance : throw new DeserializationFailedException(instance, issues); + + /// + /// Reads a (subtree) of serialized FHIR Xml data into a POCO object. + /// + /// The type of POCO to construct and deserialize + /// The deserializer to use. + /// An xml reader positioned on the first element, or the beginning of the stream. + /// A fully initialized POCO with the data from the reader. + public static T DeserializeElement(this BaseFhirXmlParser deserializer, XmlReader reader) where T : Base => + (T)deserializer.DeserializeElement(typeof(T), reader); + + /// + /// Deserialize the FHIR Json from the reader and create a new POCO object containing the data from the reader. + /// + /// The deserializer to use. + /// A json reader positioned on the first token of the object, or the beginning of the stream. + /// A fully initialized POCO with the data from the reader. + public static Resource DeserializeResource(this BaseFhirJsonParser deserializer, ref Utf8JsonReader reader) => + deserializer.TryDeserializeResource(ref reader, out var instance, out var issues) + ? instance : throw new DeserializationFailedException(instance, issues); + + /// + /// Deserialize the FHIR Json from a string and create a new POCO object. + /// + /// The deserializer to use. + /// A string of json. + /// A fully initialized POCO with the data from the reader. + public static Resource DeserializeResource(this BaseFhirJsonParser deserializer, string json) + { + var reader = SerializationUtil.Utf8JsonReaderFromJsonText(json); + return deserializer.DeserializeResource(ref reader); + } + + /// + /// Deserialize the FHIR Json from the reader and create a new POCO object containing the data from the reader. + /// + /// The deserializer to use. + /// A string of json. + /// The result of deserialization. May be incomplete when there are issues. + /// Issues encountered while deserializing. Will be empty when the function returns true. + /// false if there are issues, true otherwise. + public static bool TryDeserializeResource( + this BaseFhirJsonParser deserializer, + string json, + [NotNullWhen(true)] out Resource? instance, + out IEnumerable issues) + { + var reader = SerializationUtil.Utf8JsonReaderFromJsonText(json); + return deserializer.TryDeserializeResource(ref reader, out instance, out issues); + } + + + /// + /// Deserialize the FHIR Json from the reader and create a new POCO object containing the data from the reader. + /// + /// The deserializer to use. + /// The Json reader + /// The result of deserialization. May be incomplete when there are issues. + /// Issues encountered while deserializing. Will be empty when the function returns true. + /// false if there are issues, true otherwise. + public static bool TryDeserializeResource( + this BaseFhirJsonParser deserializer, + Utf8JsonReader reader, + out Resource? instance, + out IEnumerable issues) + { + return deserializer.TryDeserializeResource(ref reader, out instance, out issues); + } + + /// + /// Reads a (subtree) of serialized FHIR Json data into a POCO object. + /// + /// The deserializer to use. + /// The type of POCO to construct and deserialize + /// A json reader positioned on the first token of the object, or the beginning of the stream. + /// A fully initialized POCO with the data from the reader. + public static Base DeserializeObject(this BaseFhirJsonParser deserializer, Type targetType, ref Utf8JsonReader reader) => + deserializer.TryDeserializeObject(targetType, ref reader, out var instance, out var issues) ? + instance! : throw new DeserializationFailedException(instance, issues); + + /// + /// Reads a (subtree) of serialized FHIR Json data from a string into a POCO object. + /// + /// The deserializer to use. + /// The type of POCO to construct and deserialize + /// A string of json. + /// A fully initialized POCO with the data from the reader. + public static Base DeserializeObject(this BaseFhirJsonParser deserializer, Type targetType, string json) + { + var reader = SerializationUtil.Utf8JsonReaderFromJsonText(json); + return deserializer.DeserializeObject(targetType, ref reader); + } + + /// + /// Reads a (subtree) of serialized FHIR Json data into a POCO object. + /// + /// The type of POCO to construct and deserialize + /// The deserializer to use. + /// A json reader positioned on the first token of the object, or the beginning of the stream. + /// A fully initialized POCO with the data from the reader. + public static T DeserializeObject(this BaseFhirJsonParser deserializer, ref Utf8JsonReader reader) where T : Base => + (T)deserializer.DeserializeObject(typeof(T), ref reader); +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/XmlParsingExtensions.cs b/src/Hl7.Fhir.Base/Serialization/XmlParsingExtensions.cs new file mode 100644 index 0000000000..87bff9d12d --- /dev/null +++ b/src/Hl7.Fhir.Base/Serialization/XmlParsingExtensions.cs @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable + +using Hl7.Fhir.Model; +using Hl7.Fhir.Utility; +using System; +using System.Threading.Tasks; +using System.Xml; + +namespace Hl7.Fhir.Serialization; + +public static class XmlParsingExtensions +{ + /// + public static T Parse(this BaseFhirXmlParser parser, XmlReader reader) where T : Base => + (T)parser.Parse(reader, typeof(T)); + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static async Task ParseAsync(this BaseFhirXmlParser parser, XmlReader reader) where T : Base + => (T)(await parser.ParseAsync(reader, typeof(T)).ConfigureAwait(false)); + + /// + public static T Parse(this BaseFhirXmlParser parser, string xml) where T : Base => (T)parser.Parse(xml, typeof(T)); + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static async Task ParseAsync(this BaseFhirXmlParser parser, string xml) where T : Base + => (T)(await parser.ParseAsync(xml, typeof(T)).ConfigureAwait(false)); + + /// + /// Deserializes the given XML string into a FHIR resource or datatype. + /// + /// The parser for which this extension method can be called. + /// A string of FHIR XML. + /// Optional. Can be used when deserializing datatypes and + /// will be ignored when parsing resources. + /// Note that there is no official serialization for FHIR datatypes, just for FHIR resources, so + /// deserializing non-resource types might not always work. + public static Base Parse(this BaseFhirXmlParser parser, string xml, Type? dataType = null) + { + using var xmlReader = SerializationUtil.XmlReaderFromXmlText(xml); + return parse(parser, xmlReader, dataType); + } + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static Task ParseAsync(this BaseFhirXmlParser parser, string xml, Type? dataType = null) => + Task.FromResult(parser.Parse(xml, dataType)); + + /// + /// Deserializes the XML passed in the XmlReader into a FHIR resource or datatype. + /// + /// The parser for which this extension method can be called. + /// An xml reader positioned on the first element, or the beginning of the stream. + /// Optional. Can be used when deserializing datatypes and + /// will be ignored when parsing resources. + /// Note that there is no official serialization for FHIR datatypes, just for FHIR resources, so + /// deserializing non-resource types might not always work. + public static Base Parse(this BaseFhirXmlParser parser, XmlReader reader, Type? dataType = null) => + parse(parser, reader, dataType); + + /// + [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + "you should explicitly call Parse instead.")] + public static Task ParseAsync(this BaseFhirXmlParser parser, XmlReader reader, Type? dataType = null) => + Task.FromResult(parser.Parse(reader, dataType)); + + private static Base parse(BaseFhirXmlParser parser, XmlReader reader, Type? dataType = null) + { + if (dataType is null || typeof(Resource).IsAssignableFrom(dataType)) + return parser.DeserializeResource(reader); + + return parser.DeserializeElement(dataType, reader); + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonBuilder.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonBuilder.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirJsonBuilder.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonBuilder.cs diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonNode.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonNode.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirJsonNode.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonNode.cs diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonNodeFactory.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonNodeFactory.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirJsonNodeFactory.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonNodeFactory.cs diff --git a/src/Hl7.Fhir.Base/Serialization/FhirJsonParsingSettings.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonParsingSettings.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirJsonParsingSettings.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirJsonParsingSettings.cs diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlBuilder.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlBuilder.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirXmlBuilder.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlBuilder.cs diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlNode.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlNode.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirXmlNode.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlNode.cs diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlNodeFactory.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlNodeFactory.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirXmlNodeFactory.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlNodeFactory.cs diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlParsingSettings.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlParsingSettings.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/FhirXmlParsingSettings.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/FhirXmlParsingSettings.cs diff --git a/src/Hl7.Fhir.Base/Serialization/JsonDocumentExtensions.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/JsonDocumentExtensions.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/JsonDocumentExtensions.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/JsonDocumentExtensions.cs diff --git a/src/Hl7.Fhir.Base/Serialization/XObjectExtensions.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/XObjectExtensions.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/XObjectExtensions.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/XObjectExtensions.cs diff --git a/src/Hl7.Fhir.Base/Serialization/XObjectFhirXmlExtensions.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/XObjectFhirXmlExtensions.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/XObjectFhirXmlExtensions.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/XObjectFhirXmlExtensions.cs diff --git a/src/Hl7.Fhir.Base/Serialization/XmlDocumentExtensions.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/XmlDocumentExtensions.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/XmlDocumentExtensions.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/XmlDocumentExtensions.cs diff --git a/src/Hl7.Fhir.Base/Serialization/XmlSerializationDetails.cs b/src/Hl7.Fhir.Base/Serialization/elementmodel/XmlSerializationDetails.cs similarity index 100% rename from src/Hl7.Fhir.Base/Serialization/XmlSerializationDetails.cs rename to src/Hl7.Fhir.Base/Serialization/elementmodel/XmlSerializationDetails.cs diff --git a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.Legacy.cs b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.Legacy.cs index 7384016f4f..2379bc5958 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.Legacy.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.Legacy.cs @@ -63,9 +63,31 @@ private enum Mode /// public static IFhirSerializationEngine FromParserSettings(ModelInspector inspector, ParserSettings settings) => new ElementModelSerializationEngine(inspector, - BaseFhirParser.BuildXmlParsingSettings(settings), - BaseFhirParser.BuildJsonParserSettings(settings), - BaseFhirParser.BuildPocoBuilderSettings(settings)); + buildXmlParsingSettings(settings), + buildJsonParserSettings(settings), + buildPocoBuilderSettings(settings)); + + + private static PocoBuilderSettings buildPocoBuilderSettings(ParserSettings ps) => + new() + { + AllowUnrecognizedEnums = ps.AllowUnrecognizedEnums, + IgnoreUnknownMembers = ps.AcceptUnknownMembers, + }; + + private static FhirXmlParsingSettings buildXmlParsingSettings(ParserSettings settings) => + new() + { + DisallowSchemaLocation = settings.DisallowXsiAttributesOnRoot, +#pragma warning disable CS0618 // Type or member is obsolete + PermissiveParsing = settings.PermissiveParsing, +#pragma warning restore CS0618 // Type or member is obsolete + }; + + private static FhirJsonParsingSettings buildJsonParserSettings(ParserSettings settings) => +#pragma warning disable CS0618 // Type or member is obsolete + new() { AllowJsonComments = false, PermissiveParsing = settings.PermissiveParsing }; +#pragma warning restore CS0618 // Type or member is obsolete /// /// Create an implementation of which uses the legacy parser and serializer diff --git a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs index 174dc148c2..b70b6592ae 100644 --- a/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs +++ b/src/Hl7.Fhir.Base/Serialization/engine/FhirSerializationEngineFactory.cs @@ -82,7 +82,7 @@ public static IFhirSerializationEngine Custom(ModelInspector inspector, private static IFhirSerializationEngine createEngine(ModelInspector inspector, FhirJsonConverterOptions? converterOptions, ParserSettings? xmlSettings, DeserializationMode mode) { - var jsonOptions = (converterOptions ?? new FhirJsonConverterOptions()).WithMode(mode); + var jsonOptions = (FhirJsonConverterOptions)(converterOptions ?? new FhirJsonConverterOptions()).UsingMode(mode); var xmlOptions = (xmlSettings ?? new ParserSettings()).UsingMode(mode); return Custom(inspector, jsonOptions, xmlOptions); diff --git a/src/Hl7.Fhir.Base/Serialization/engine/PocoDeserializationExtensions.cs b/src/Hl7.Fhir.Base/Serialization/engine/PocoDeserializationExtensions.cs deleted file mode 100644 index d2905aaadf..0000000000 --- a/src/Hl7.Fhir.Base/Serialization/engine/PocoDeserializationExtensions.cs +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2023, Firely (info@fire.ly) and contributors - * See the file CONTRIBUTORS for details. - * - * This file is licensed under the BSD 3-Clause license - * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -#nullable enable - -using Hl7.Fhir.Model; -using Hl7.Fhir.Utility; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Text; -using System.Text.Json; -using System.Xml; - -namespace Hl7.Fhir.Serialization -{ - /// - /// Extension methods that provide additional utility methods for deserialization on top of - /// the TryDeserialize() functions of the json and xml deserializers. - /// - public static class PocoDeserializationExtensions - { - /// - /// Deserialize the FHIR xml from the reader and create a new POCO resource containing the data from the reader. - /// - /// The deserializer to use. - /// An xml reader positioned on the first element, or the beginning of the stream. - /// A fully initialized POCO with the data from the reader. - public static Resource DeserializeResource(this BaseFhirXmlPocoDeserializer deserializer, XmlReader reader) => - deserializer.TryDeserializeResource(reader, out var instance, out var issues) ? - instance! : throw new DeserializationFailedException(instance, issues); - - /// - /// Deserialize the FHIR xml from a string and create a new POCO resource containing the data from the reader. - /// - /// The deserializer to use. - /// A string containing the XML from which to deserialize the resource. - /// A fully initialized POCO with the data from the reader. - public static Resource DeserializeResource(this BaseFhirXmlPocoDeserializer deserializer, string data) - { - var xmlReader = SerializationUtil.XmlReaderFromXmlText(data); - return deserializer.DeserializeResource(xmlReader); - } - - /// - /// Deserialize the FHIR xml from a string and create a new POCO resource containing the data from the reader. - /// - /// The deserializer to use. - /// A string containing the XML from which to deserialize the resource. - /// The result of deserialization. May be incomplete when there are issues. - /// Issues encountered while deserializing. Will be empty when the function returns true. - /// A fully initialized POCO with the data from the reader. - public static bool TryDeserializeResource( - this BaseFhirXmlPocoDeserializer deserializer, - string data, - [NotNullWhen(true)] out Resource? instance, - out IEnumerable issues) - { - var xmlReader = SerializationUtil.XmlReaderFromXmlText(data); - return deserializer.TryDeserializeResource(xmlReader, out instance, out issues); - } - - /// - /// Deserialize the FHIR xml from a reader and create a new POCO resource containing the data from the reader. - /// - /// The deserializer to use. - /// A reader for the XML from which to deserialize the resource. - /// The result of deserialization. May be incomplete when there are issues. - /// Issues encountered while deserializing. Will be empty when the function returns true. - /// A fully initialized POCO with the data from the reader. - public static bool TryDeserializeResource( - this BaseFhirXmlPocoDeserializer deserializer, - XmlReader reader, - out Resource? instance, - out IEnumerable issues) - { - return deserializer.TryDeserializeResource(reader, out instance, out issues); - } - - /// - /// Reads a (subtree) of serialized FHIR Xml data into a POCO object. - /// - /// The deserializer to use. - /// The type of POCO to construct and deserialize - /// An xml reader positioned on the first element, or the beginning of the stream. - /// A fully initialized POCO with the data from the reader. - public static Base DeserializeElement(this BaseFhirXmlPocoDeserializer deserializer, Type targetType, XmlReader reader) => - deserializer.TryDeserializeElement(targetType, reader, out var instance, out var issues) ? - instance! : throw new DeserializationFailedException(instance, issues); - - /// - /// Reads a (subtree) of serialized FHIR Xml data into a POCO object. - /// - /// The type of POCO to construct and deserialize - /// The deserializer to use. - /// An xml reader positioned on the first element, or the beginning of the stream. - /// A fully initialized POCO with the data from the reader. - public static T DeserializeElement(this BaseFhirXmlPocoDeserializer deserializer, XmlReader reader) where T : Base => - (T)deserializer.DeserializeElement(typeof(T), reader); - - /// - /// Deserialize the FHIR Json from the reader and create a new POCO object containing the data from the reader. - /// - /// The deserializer to use. - /// A json reader positioned on the first token of the object, or the beginning of the stream. - /// A fully initialized POCO with the data from the reader. - public static Resource DeserializeResource(this BaseFhirJsonPocoDeserializer deserializer, ref Utf8JsonReader reader) => - deserializer.TryDeserializeResource(ref reader, out var instance, out var issues) - ? instance! : throw new DeserializationFailedException(instance, issues); - - /// - /// Deserialize the FHIR Json from the reader and create a new POCO object containing the data from the reader. - /// - /// The deserializer to use. - /// A string of json. - /// A fully initialized POCO with the data from the reader. - public static Resource DeserializeResource(this BaseFhirJsonPocoDeserializer deserializer, string json) - { - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), new() { CommentHandling = JsonCommentHandling.Skip }); - return deserializer.DeserializeResource(ref reader); - } - - /// - /// Deserialize the FHIR Json from the reader and create a new POCO object containing the data from the reader. - /// - /// The deserializer to use. - /// A string of json. - /// The result of deserialization. May be incomplete when there are issues. - /// Issues encountered while deserializing. Will be empty when the function returns true. - /// false if there are issues, true otherwise. - public static bool TryDeserializeResource( - this BaseFhirJsonPocoDeserializer deserializer, - string json, - [NotNullWhen(true)] out Resource? instance, - out IEnumerable issues) - { - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json), new() { CommentHandling = JsonCommentHandling.Skip }); - return deserializer.TryDeserializeResource(ref reader, out instance, out issues); - } - - /// - /// Deserialize the FHIR Json from the reader and create a new POCO object containing the data from the reader. - /// - /// The deserializer to use. - /// The Json reader - /// The result of deserialization. May be incomplete when there are issues. - /// Issues encountered while deserializing. Will be empty when the function returns true. - /// false if there are issues, true otherwise. - public static bool TryDeserializeResource( - this BaseFhirJsonPocoDeserializer deserializer, - Utf8JsonReader reader, - out Resource? instance, - out IEnumerable issues) - { - return deserializer.TryDeserializeResource(ref reader, out instance, out issues); - } - - /// - /// Reads a (subtree) of serialized FHIR Json data into a POCO object. - /// - /// The deserializer to use. - /// The type of POCO to construct and deserialize - /// A json reader positioned on the first token of the object, or the beginning of the stream. - /// A fully initialized POCO with the data from the reader. - public static Base DeserializeObject(this BaseFhirJsonPocoDeserializer deserializer, Type targetType, ref Utf8JsonReader reader) => - deserializer.TryDeserializeObject(targetType, ref reader, out var instance, out var issues) ? - instance! : throw new DeserializationFailedException(instance, issues); - - /// - /// Reads a (subtree) of serialzed FHIR Json data into a POCO object. - /// - /// The type of POCO to construct and deserialize - /// The deserializer to use. - /// A json reader positioned on the first token of the object, or the beginning of the stream. - /// A fully initialized POCO with the data from the reader. - public static T DeserializeObject(this BaseFhirJsonPocoDeserializer deserializer, ref Utf8JsonReader reader) where T : Base => - (T)deserializer.DeserializeObject(typeof(T), ref reader); - } -} - -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs b/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs index f4de032900..8d08122c13 100644 --- a/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs +++ b/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs @@ -135,6 +135,10 @@ public static Task XmlReaderFromXmlTextAsync(string xml, bool ignoreC public static JsonReader JsonReaderFromJsonText(string json) => jsonReaderFromTextReader(new StringReader(json)); + public static Utf8JsonReader Utf8JsonReaderFromJsonText(string json) => + new(Encoding.UTF8.GetBytes(json), + new JsonReaderOptions { CommentHandling = JsonCommentHandling.Skip }); + public static XmlReader XmlReaderFromStream(Stream input, bool ignoreComments = true) => WrapXmlReader(XmlReader.Create(input), ignoreComments); diff --git a/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs index efdd9f71d8..5c2c2ad8b8 100644 --- a/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs @@ -19,10 +19,10 @@ namespace Hl7.Fhir.Tests.Serialization public partial class ResourceParsingTests { [TestMethod] - public async Tasks.Task ParseBinaryForR4andHigher() + public void ParseBinaryForR4andHigher() { var json = "{\"resourceType\":\"Binary\",\"data\":\"ZGF0YQ==\"}"; - var binary = await new FhirJsonParser().ParseAsync(json); + var binary = new FhirJsonParser().Parse(json); var result = new FhirJsonSerializer().SerializeToString(binary); @@ -35,9 +35,11 @@ public async Tasks.Task ParseBinaryForR4andHigher() public async Tasks.Task ParseBinaryForR4andHigherWithUnknownSTU3Element() { var json = "{\"resourceType\":\"Binary\",\"content\":\"ZGF0YQ==\"}"; +#pragma warning disable CS0618 // Type or member is obsolete Func act = () => new FhirJsonParser().ParseAsync(json); +#pragma warning restore CS0618 // Type or member is obsolete await act.Should().ThrowAsync(); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs b/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs index f3ae0d2a65..58209f7fcb 100644 --- a/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs @@ -105,7 +105,7 @@ public async Tasks.Task CompareToOtherElementNavigator() var json = TestDataHelper.ReadTestData("TestPatient.json"); var xml = TestDataHelper.ReadTestData("TestPatient.xml"); - var poco = await (new FhirJsonParser()).ParseAsync(json); + var poco = (new FhirJsonParser()).Parse(json); var pocoP = poco.ToTypedElement(); var jsonP = (await FhirJsonNode.ParseAsync(json, settings: new FhirJsonParsingSettings { AllowJsonComments = true })) .ToTypedElement(new PocoStructureDefinitionSummaryProvider()); @@ -178,4 +178,4 @@ void extract() } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs b/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs index f678de2fe2..debdc9bdf1 100644 --- a/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs +++ b/src/Hl7.Fhir.STU3.Tests/Model/DeepCopyTest.cs @@ -25,23 +25,23 @@ namespace Hl7.Fhir.Tests.Model public class DeepCopyTest { [TestMethod] - public async Task CheckCopyAllFields() + public void CheckCopyAllFields() { string xml = ReadTestData("TestPatient.xml"); - var p = await new FhirXmlParser().ParseAsync(xml); - var p2 = (Patient)p.DeepCopy(); + var p = new FhirXmlParser().Parse(xml); + var p2 = p.DeepCopy(); var xml2 = new FhirXmlSerializer().SerializeToString(p2); XmlAssert.AreSame("TestPatient.xml", xml, xml2); } [TestMethod] - public async Task CheckCopyCarePlan() + public void CheckCopyCarePlan() { string xml = ReadTestData(@"careplan-example-f201-renal.xml"); - var p = await new FhirXmlParser().ParseAsync(xml); - var p2 = (CarePlan)p.DeepCopy(); + var p = new FhirXmlParser().Parse(xml); + var p2 = p.DeepCopy(); var xml2 = new FhirXmlSerializer().SerializeToString(p2); XmlAssert.AreSame("careplan-example-f201-renal.xml", xml, xml2); } diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs index 046d5b45dc..058e527a83 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs @@ -27,7 +27,7 @@ public class ResourceParsingTests public void ConfigureFailOnUnknownMember() { var xml = ""; - var parser = new FhirXmlParser(new ParserSettings { AllowXsiAttributesOnRoot = true }); + var parser = new FhirXmlParser(); // parser.Settings.ExceptionHandler = (object source, Utility.ExceptionNotification args) => // { @@ -157,11 +157,11 @@ public void AcceptNsReassignments() [TestMethod] - public async Tasks.Task RetainSpacesInAttribute() + public void RetainSpacesInAttribute() { var xml = ""; - var basic = await FhirXmlParser.ParseAsync(xml); + var basic = FhirXmlParser.Parse(xml); Assert.IsTrue(basic.GetStringExtension("http://blabla.nl").Contains("\n")); @@ -169,24 +169,27 @@ public async Tasks.Task RetainSpacesInAttribute() Assert.IsTrue(outp.Contains(" ")); } - internal FhirXmlParser FhirXmlParser = new FhirXmlParser(); - internal FhirJsonParser FhirJsonParser = new FhirJsonParser(); - internal FhirXmlSerializer FhirXmlSerializer = new FhirXmlSerializer(); - internal FhirJsonSerializer FhirJsonSerializer = new FhirJsonSerializer(); + // Test legacy behaviour +#pragma warning disable CS0618 // Type or member is obsolete + internal FhirXmlParser FhirXmlParser = new(new ParserSettings { PermissiveParsing = true }); + internal FhirJsonParser FhirJsonParser = new(new ParserSettings { PermissiveParsing = true }); +#pragma warning restore CS0618 // Type or member is obsolete + internal FhirXmlSerializer FhirXmlSerializer = new(); + internal FhirJsonSerializer FhirJsonSerializer = new(); [TestMethod] - public async Tasks.Task ParsePerfJson() + public void ParsePerfJson() { string json = TestDataHelper.ReadTestData("TestPatient.json"); var pser = new FhirJsonParser(); // Assume that we can happily read the patient gender when enums are enforced - var p = await pser.ParseAsync(json); + var p = pser.Parse(json); var sw = new Stopwatch(); sw.Start(); for (var i = 0; i < 500; i++) - p = await pser.ParseAsync(json); + p = pser.Parse(json); sw.Stop(); Debug.WriteLine($"Parsing took {sw.ElapsedMilliseconds / 500.0 * 1000} micros"); } @@ -210,20 +213,20 @@ public void ParsePerfXml() [TestMethod] - public async Tasks.Task AcceptUnknownEnums() + public void AcceptUnknownEnums() { string json = TestDataHelper.ReadTestData("TestPatient.json"); var pser = new FhirJsonParser(); // Assume that we can happily read the patient gender when enums are enforced - var p = await pser.ParseAsync(json); + var p = pser.Parse(json); Assert.IsNotNull(p.Gender); Assert.AreEqual("male", p.GenderElement.ObjectValue); Assert.AreEqual(AdministrativeGender.Male, p.Gender.Value); // Verify that if we relax the restriction that everything still works pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; - p = await pser.ParseAsync(json); + p = pser.Parse(json); Assert.IsNotNull(p.Gender); Assert.AreEqual("male", p.GenderElement.ObjectValue); @@ -237,7 +240,7 @@ public async Tasks.Task AcceptUnknownEnums() try { pser.Settings = pser.Settings with { AllowUnrecognizedEnums = false }; - await pser.ParseAsync(xml2); + pser.Parse(xml2); Assert.Fail(); } catch (FormatException) @@ -247,7 +250,7 @@ public async Tasks.Task AcceptUnknownEnums() // Now, allow unknown enums and check support pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; - p = await pser.ParseAsync(xml2); + p = pser.Parse(xml2); Assert.ThrowsException(() => p.Gender); Assert.AreEqual("superman", p.GenderElement.ObjectValue); } @@ -261,13 +264,13 @@ public async Tasks.Task EdgecaseRoundtrip() string json = TestDataHelper.ReadTestData("json-edge-cases.json"); var tempPath = Path.GetTempPath(); - var poco = await FhirJsonParser.ParseAsync(json); + var poco = FhirJsonParser.Parse(json); Assert.IsNotNull(poco); var xml = FhirXmlSerializer.SerializeToString(poco); Assert.IsNotNull(xml); await File.WriteAllTextAsync(Path.Combine(tempPath, "edgecase.xml"), xml); - poco = await FhirXmlParser.ParseAsync(xml); + poco = FhirXmlParser.Parse(xml); Assert.IsNotNull(poco); var json2 = FhirJsonSerializer.SerializeToString(poco); Assert.IsNotNull(json2); @@ -280,7 +283,7 @@ public async Tasks.Task EdgecaseRoundtrip() } [TestMethod] - public async Tasks.Task ContainedBaseIsNotAddedToId() + public void ContainedBaseIsNotAddedToId() { var p = new Patient() { Id = "jaap" }; var o = new Observation() { Subject = new ResourceReference() { Reference = "#" + p.Id } }; @@ -290,7 +293,7 @@ public async Tasks.Task ContainedBaseIsNotAddedToId() var xml = FhirXmlSerializer.SerializeToString(o); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); - var o2 = await FhirXmlParser.ParseAsync(xml); + var o2 = FhirXmlParser.Parse(xml); o2.ResourceBase = new Uri("http://nu.nl/fhir"); xml = FhirXmlSerializer.SerializeToString(o2); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); @@ -298,21 +301,21 @@ public async Tasks.Task ContainedBaseIsNotAddedToId() [TestMethod] - public async Tasks.Task EmptyRoundTrip() + public void EmptyRoundTrip() { var patient = new Patient { - Identifier = new List - { + Identifier = + [ new Identifier("https://mydomain.com/identifiers/Something", "123"), new Identifier("https://mydomain.com/identifiers/Spaces", " "), new Identifier("https://mydomain.com/identifiers/Empty", string.Empty), new Identifier("https://mydomain.com/identifiers/Null", null) - } + ] }; var json = FhirJsonSerializer.SerializeToString(patient); - var parsedPatient = await FhirJsonParser.ParseAsync(json); + var parsedPatient = FhirJsonParser.Parse(json); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); for (var i = 0; i < patient.Identifier.Count; i++) @@ -329,7 +332,7 @@ public async Tasks.Task EmptyRoundTrip() } var xml = FhirXmlSerializer.SerializeToString(patient); - parsedPatient = await FhirXmlParser.ParseAsync(xml); + parsedPatient = FhirXmlParser.Parse(xml); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); for (var i = 0; i < patient.Identifier.Count; i++) @@ -348,7 +351,7 @@ public async Tasks.Task EmptyRoundTrip() [TestMethod] - public async Tasks.Task SerializeNarrativeWithQuotes() + public void SerializeNarrativeWithQuotes() { var p = new Patient { @@ -356,19 +359,19 @@ public async Tasks.Task SerializeNarrativeWithQuotes() }; var xml = FhirXmlSerializer.SerializeToString(p); - Assert.IsNotNull(await FhirXmlParser.ParseAsync(xml)); + Assert.IsNotNull(FhirXmlParser.Parse(xml)); var json = FhirJsonSerializer.SerializeToString(p); - Assert.IsNotNull(await FhirJsonParser.ParseAsync(json)); + Assert.IsNotNull(FhirJsonParser.Parse(json)); } [TestMethod] - public async Tasks.Task NarrativeMustBeValidXml() + public void NarrativeMustBeValidXml() { try { var json = "{\"resourceType\": \"Patient\", \"text\": {\"status\": \"generated\", \"div\": \"text without div\" } }"; - var patient = await new FhirJsonParser().ParseAsync(json); + _ = new FhirJsonParser().Parse(json); Assert.Fail("Should have thrown on invalid Div format"); } @@ -388,10 +391,10 @@ public void ParseEmptyContained() } [TestMethod] - public async Tasks.Task ParseBinaryForR4andHigher() + public void ParseBinaryForR4andHigher() { var json = "{\"resourceType\":\"Binary\",\"content\":\"ZGF0YQ==\"}"; - var binary = await new FhirJsonParser().ParseAsync(json); + var binary = new FhirJsonParser().Parse(json); var result = new FhirJsonSerializer().SerializeToString(binary); @@ -404,7 +407,9 @@ public async Tasks.Task ParseBinaryForR4andHigher() public async Tasks.Task ParseBinaryForR4andHigherWithUnknownSTU3Element() { var json = "{\"resourceType\":\"Binary\",\"data\":\"ZGF0YQ==\"}"; +#pragma warning disable CS0618 // Type or member is obsolete Func act = () => new FhirJsonParser().ParseAsync(json); +#pragma warning restore CS0618 // Type or member is obsolete await act.Should().ThrowAsync(); } diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs index 61d6a3ebc6..6144cc6c77 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs @@ -44,9 +44,9 @@ public void SerializeMetaJson() } [TestMethod] - public async Tasks.Task ParseMetaXml() + public void ParseMetaXml() { - var poco = (Meta)(await new FhirXmlParser().ParseAsync(metaXml, typeof(Meta))); + var poco = (new FhirXmlParser().Parse(metaXml)); var xml = new FhirXmlSerializer().SerializeToString(poco, rootName: "meta"); Assert.IsTrue(poco.IsExactly(metaPoco)); @@ -70,9 +70,9 @@ public void ParsePatientXmlNullType() internal FhirJsonSerializer FhirJsonSerializer = new FhirJsonSerializer(); [TestMethod] - public async Tasks.Task ParseMetaJson() + public void ParseMetaJson() { - var poco = (Meta)await (new FhirJsonParser().ParseAsync(metaJson, typeof(Meta))); + var poco = (new FhirJsonParser().Parse(metaJson)); var json = FhirJsonSerializer.SerializeToString(poco); Assert.IsTrue(poco.IsExactly(metaPoco)); @@ -80,11 +80,11 @@ public async Tasks.Task ParseMetaJson() } [TestMethod] - public async Tasks.Task ParsePatientJsonNullType() + public void ParsePatientJsonNullType() { string jsonPatient = TestDataHelper.ReadTestData("TestPatient.json"); - var poco = await new FhirJsonParser().ParseAsync(jsonPatient); + var poco = new FhirJsonParser().Parse(jsonPatient); Assert.AreEqual(((Patient)poco).Id, "pat1"); Assert.AreEqual(((Patient)poco).Contained.First().Id, "1"); @@ -171,7 +171,7 @@ public void TestDecimalPrecisionSerializationInJson() } [TestMethod] - public async Tasks.Task TestLongDecimalSerialization() + public void TestLongDecimalSerialization() { var dec = 3.1415926535897932384626433833m; var ext = new FhirDecimal(dec); @@ -179,13 +179,13 @@ public async Tasks.Task TestLongDecimalSerialization() obs.AddExtension("http://example.org/DecimalPrecision", ext); var json = FhirJsonSerializer.SerializeToString(obs); - var obs2 = await FhirJsonParser.ParseAsync(json); + var obs2 = FhirJsonParser.Parse(json); Assert.AreEqual(dec.ToString(CultureInfo.InvariantCulture), ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); } [TestMethod] - public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() + public void TestParseUnkownPolymorphPropertyInJson() { var dec6 = 6m; var ext = new FhirDecimal(dec6); @@ -193,7 +193,7 @@ public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() var json = FhirJsonSerializer.SerializeToString(obs); try { - var obs2 = await FhirJsonParser.ParseAsync(json); + var obs2 = FhirJsonParser.Parse(json); Assert.Fail("valueDecimal is not a known type for Observation"); } catch (FormatException) @@ -261,11 +261,11 @@ public void TryXXEExploit() } [TestMethod] - public async Tasks.Task SerializeUnknownEnums() + public void SerializeUnknownEnums() { string xml = TestDataHelper.ReadTestData("TestPatient.xml"); var pser = new FhirXmlParser(); - var p = await pser.ParseAsync(xml); + var p = pser.Parse(xml); string outp = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(outp.Contains("\"male\"")); @@ -304,12 +304,12 @@ public void TestNullExtensionRemoval() } [TestMethod] - public async Tasks.Task SerializeJsonWithPlainDiv() + public void SerializeJsonWithPlainDiv() { string json = TestDataHelper.ReadTestData(@"TestPatient.json"); Assert.IsNotNull(json); var parser = new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)); - var pat = await parser.ParseAsync(json); + var pat = parser.Parse(json); Assert.IsNotNull(pat); var xml = FhirXmlSerializer.SerializeToString(pat); @@ -475,17 +475,17 @@ public void SummarizeSerializingTest() /// This test proves issue 657: https://github.com/FirelyTeam/firely-net-sdk/issues/657 /// [TestMethod] - public async Tasks.Task DateTimeOffsetAccuracyTest() + public void DateTimeOffsetAccuracyTest() { var patient = new Patient { Meta = new Meta { LastUpdated = DateTimeOffset.UtcNow } }; var json = new FhirJsonSerializer().SerializeToString(patient); - var res = await new FhirJsonParser().ParseAsync(json); + var res = new FhirJsonParser().Parse(json); Assert.IsTrue(patient.IsExactly(res), "1"); // Is the parsing still correct without milliseconds? patient = new Patient { Meta = new Meta { LastUpdated = new DateTimeOffset(2018, 8, 13, 13, 41, 56, TimeSpan.Zero) } }; json = "{\"resourceType\":\"Patient\",\"meta\":{\"lastUpdated\":\"2018-08-13T13:41:56+00:00\"}}"; - res = await new FhirJsonParser().ParseAsync(json); + res = new FhirJsonParser().Parse(json); Assert.IsTrue(patient.IsExactly(res), "2"); // Is the serialization still correct without milliseconds? @@ -495,7 +495,7 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() // Is the parsing still correct with a few milliseconds and TimeZone? patient = new Patient { Meta = new Meta { LastUpdated = new DateTimeOffset(2018, 8, 13, 13, 41, 56, 12, TimeSpan.Zero) } }; json = "{\"resourceType\":\"Patient\",\"meta\":{\"lastUpdated\":\"2018-08-13T13:41:56.012+00:00\"}}"; - res = await new FhirJsonParser().ParseAsync(json); + res = new FhirJsonParser().Parse(json); Assert.IsTrue(patient.IsExactly(res), "4"); // Is the serialization still correct with a few milliseconds? @@ -504,12 +504,15 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() } [TestMethod] - public async Tasks.Task SerializerHandlesEmptyChildObjects() + public void SerializerHandlesEmptyChildObjects() { - var fhirJsonParser = new FhirJsonParser(); + // Test legacy behaviour +#pragma warning disable CS0618 // Type or member is obsolete + var fhirJsonParser = new FhirJsonParser(new ParserSettings { PermissiveParsing = true }); +#pragma warning restore CS0618 // Type or member is obsolete string json = TestDataHelper.ReadTestData("TestPatient.json"); - var poco = await fhirJsonParser.ParseAsync(json); + var poco = fhirJsonParser.Parse(json); Assert.AreEqual(1, poco.Name.Count); @@ -517,7 +520,7 @@ public async Tasks.Task SerializerHandlesEmptyChildObjects() var reserialized = poco.ToJson(); - var newPoco = await fhirJsonParser.ParseAsync(reserialized); + var newPoco = fhirJsonParser.Parse(reserialized); Assert.AreEqual(1, newPoco.Name.Count); } diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs index 6ce78c6989..d515d334bd 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/ValueQuantityParsingTests.cs @@ -73,7 +73,7 @@ async static Tasks.Task XmlRoundTripAsync(T resource) where T : Resource await File.WriteAllTextAsync(xmlFile, xml); xml = await File.ReadAllTextAsync(xmlFile); - var parsed = await new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(xml); + var parsed = new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).Parse(xml); return parsed; } @@ -87,7 +87,7 @@ static async Tasks.Task JsonRoundTrip(T resource) where T : Resource await File.WriteAllTextAsync(jsonFile, json); json = await File.ReadAllTextAsync(jsonFile); - var parsed = await new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(json); + var parsed = new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).Parse(json); return parsed; } diff --git a/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs b/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs index 6bfaebc358..ff1271096b 100644 --- a/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs +++ b/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs @@ -1,4 +1,5 @@ using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; using Hl7.Fhir.Tests; using Hl7.Fhir.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -50,12 +51,12 @@ private async Tasks.Task ValidateFolder(string basePath, string path, StringBuil if (new FileInfo(item).Extension == ".xml") { Console.WriteLine($" {item.Replace(path+"//", "")}"); - await xmlParser.ParseAsync(content); + xmlParser.Parse(content); } else if (new FileInfo(item).Extension == ".json") { Console.WriteLine($" {item.Replace(path + "//", "")}"); - await jsonParser.ParseAsync(content); + jsonParser.Parse(content); } else { diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems b/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems index 25db993374..7d19d27be2 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/Hl7.Fhir.Serialization.Shared.Tests.projitems @@ -18,8 +18,6 @@ - - diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs deleted file mode 100644 index 3a65a897db..0000000000 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersJson.cs +++ /dev/null @@ -1,242 +0,0 @@ -using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using Hl7.Fhir.Tests; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Hl7.Fhir.Serialization.Tests -{ - [TestClass] - public class SerializationExceptionHandlersJson - { - [TestMethod, Ignore] // ignored as this is intended behaviour that the code doesn't do (yet) - public void JsonInvalidEnumerationValue() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string rawData = """ - { - "resourceType": "Patient", - "id": "pat1", - "name": [ - { - "family": "Doe" - } - ], - "chicken": "rubbish prop", - "gender": "cat", - "birthDate": "1 Jan 1970" - } - """; - - var parser = new FhirJsonParser(); - var p = parser.Parse(rawData); - DebugDump.OutputJson(p); - } - - [TestMethod] - public void JsonInvalidEmptyObservation() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string rawData = """ - { - "resourceType": "Observation" - // ,"id": "pat1" - } - """; - - var parser = new FhirJsonParser(); - var p = parser.Parse(rawData); - DebugDump.OutputJson(p); - } - - [TestMethod] - public void JsonInvalidDateValue() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string rawData = """ - { - "resourceType": "Patient", - "id": "pat1", - "name": [ - { - "family": "Doe" - } - ], - "chicken": "rubbish prop", - "birthDate": "1 Jan 1970" - } - """; - - var parser = new FhirJsonParser(); - var p = parser.Parse(rawData); - DebugDump.OutputJson(p); - } - - [TestMethod] - public void JsonInvalidDateValueWithTime() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string rawData = """ - { - "resourceType": "Patient", - "id": "pat1", - "name": [ - { - "family": "Doe" - } - ], - "birthDate": "1970-01-01T12:45:00Z" - } - """; - - var parser = new FhirJsonParser(); - var p = parser.Parse(rawData); - DebugDump.OutputJson(p); - } - - [TestMethod] - public void JsonInvalidPropertyDetected() - { - string rawData = """ - { - "resourceType": "Patient", - "id": "pat1", - "name": [ - { - "family": "Doe" - } - ], - "chicken": "rubbish prop", - "gender": "male", - "birthDate": "1970-01-01" - } - """; - - var parser = new FhirJsonParser(); - var p = parser.Parse(rawData); - DebugDump.OutputJson(p); - } - - [TestMethod, Ignore] // ignored as this is intended behaviour that the code doesn't do (yet) - public void JsonInvalidDecimalValue() - { - string rawData = """ - { - "resourceType": "Observation", - "id": "decimal", - "status": "final", - "code": { - "text": "Decimal Testing Observation" - }, - "component": [ - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": 1.0, - "unit": "g" - } - }, - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": 1.00, - "unit": "g" - } - }, - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": 1.0e0, - "unit": "g" - } - }, - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": 0.00000000000000001, - "unit": "g" - } - }, - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": 10000000000000000, - "unit": "g" - } - }, - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": "1.00000000000000000e-24", - "unit": "g" - } - }, - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": "-1.00000000000000000e245", - "unit": "g" - } - } - ] - } - """; - - var parser = new FhirJsonParser(); - var p = parser.Parse(rawData); - DebugDump.OutputJson(p); - } - - [TestMethod, Ignore] // ignored as this is intended behaviour that the code doesn't do (yet) - public void JsonMixedInvalidParseIssues() - { - string rawData = """ - { - "resourceType": "Observation", - "id": "decimal", - "status": "glarb", - "code": { - "text": "Decimal Testing Observation" - }, - "component": [ - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": "1.00000000000000000e-24", - "unit": "g" - } - }, - { - "code": { - "text": "Component" - }, - "valueQuantity": { - "value": "-1.00000000000000000e245", - "unit": "g" - } - } - ] - } - """; - - var parser = new FhirJsonParser(); - var p = parser.Parse(rawData); - DebugDump.OutputJson(p); - } - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs deleted file mode 100644 index 6fd9f874c1..0000000000 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializationExcexptionHandlersXml.cs +++ /dev/null @@ -1,189 +0,0 @@ -using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Hl7.Fhir.Serialization.Tests -{ - [TestClass] - public class SerializationExceptionHandlersXml - { - [TestMethod, Ignore] // ignored as this is intended behaviour that the code doesn't do (yet) - public void XMLInvalidEnumerationValue() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string xmlPatient = """ - - - - - - - - """; - - var xs = new FhirXmlParser(); - var p = xs.Parse(xmlPatient); - DebugDump.OutputXml(p); - } - - [TestMethod] - public void XMLInvalidDateValue() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string xmlPatient = """ - - - - - - - - """; - - var xs = new FhirXmlParser(); - var p = xs.Parse(xmlPatient); - DebugDump.OutputXml(p); - } - - [TestMethod] - public void XMLInvalidDateValueWithTime() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string xmlPatient = """ - - - - - - - - - """; - - var xs = new FhirXmlParser(); - var p = xs.Parse(xmlPatient); - DebugDump.OutputXml(p); - } - - [TestMethod] - public void XMLInvalidPropertyOrdering() - { - // string containing a FHIR Patient with name John Doe, 17 Jan 1970, an invalid gender and an invalid date of birth - string xmlPatient = """ - - - - - - - - - - - """; - - var xs = new FhirXmlParser(); - var p = xs.Parse(xmlPatient); - - DebugDump.OutputXml(p); - } - - [TestMethod] - public void XMLInvalidPropertyDetected() - { - string xmlPatient = """ - - - - - - - """; - - var xs = new FhirXmlParser(); - var p = xs.Parse(xmlPatient); - DebugDump.OutputXml(p); - } - - [TestMethod, Ignore] // ignored as this is intended behaviour that the code doesn't do (yet) - public void XMLInvalidDecimalValue() - { - string xml = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - """; - - var xs = new FhirXmlParser(); - var p = xs.Parse(xml); - DebugDump.OutputXml(p); - } - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs index fccf4a9c72..7396856873 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs @@ -50,13 +50,13 @@ public async Tasks.Task TestPruneEmptyNodes() [TestMethod] public async Tasks.Task CanSerializeFromPoco() { - var tp = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.json")); - var pser = new FhirJsonParser(new ParserSettings { AllowXsiAttributesOnRoot = true } ); - var pat = await pser.ParseAsync(tp); + var tp = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); + var pser = new FhirJsonParser(); + var pat = pser.Parse(tp); var output = pat.ToJson(); - List errors = new List(); + var errors = new List(); JsonAssert.AreSame(@"TestData\fp-test-patient.json", tp, output, errors); Console.WriteLine(String.Join("\r\n", errors)); Assert.AreEqual(0, errors.Count, "Errors were encountered comparing converted content"); @@ -73,7 +73,7 @@ public async Tasks.Task DoesPretty() var pretty = nav.ToJson(pretty: true); Assert.IsTrue(pretty[..20].Contains('\n')); - var p = await new FhirJsonParser().ParseAsync(json); + var p = new FhirJsonParser().Parse(json); output = new FhirJsonSerializer().SerializeToString(p, pretty: false); Assert.IsFalse(output[..20].Contains('\n')); pretty = new FhirJsonSerializer().SerializeToString(p, pretty: true); diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs index cd655aa9ea..e5f2faf5ea 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs @@ -55,11 +55,11 @@ public void TestElementReordering() } [TestMethod] - public async Tasks.Task CanSerializeFromPoco() + public void CanSerializeFromPoco() { var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var pser = new FhirXmlParser(new ParserSettings { AllowXsiAttributesOnRoot = true }); - var pat = await pser.ParseAsync(tpXml); + var pser = new FhirXmlParser(); + var pat = pser.Parse(tpXml); var nav = pat.ToTypedElement(); var output = nav.ToXml(); @@ -74,7 +74,7 @@ public async Tasks.Task CompareSubtrees() // If on a Unix platform replace \\r\\n in json strings to \\n. if(Environment.NewLine == "\n") tpJson = tpJson.Replace(@"\r\n", @"\n"); - var pat = await (new FhirXmlParser()).ParseAsync(tpXml); + var pat = (new FhirXmlParser()).Parse(tpXml); var navXml = getXmlElement(tpXml); var navJson = await getJsonElement(tpJson); @@ -106,7 +106,7 @@ public async Tasks.Task DoesPretty() var pretty = nav.ToXml(pretty: true); Assert.IsTrue(pretty[..50].Contains('\n')); - var p = await new FhirXmlParser().ParseAsync(xml); + var p = new FhirXmlParser().Parse(xml); output = new FhirXmlSerializer().SerializeToString(p, pretty: false); Assert.IsFalse(output[..50].Contains('\n')); pretty = new FhirXmlSerializer().SerializeToString(p, pretty: true); diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs index 4c9e54ac52..e890cb20b6 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs @@ -37,7 +37,7 @@ public async Tasks.Task CanSerializeSubtree() var tpXml = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.xml")); var tpJson = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); - var pat = await (new FhirXmlParser()).ParseAsync(tpXml); + var pat = (new FhirXmlParser()).Parse(tpXml); // Should work on the parent resource var navXml = getXmlNode(tpXml); diff --git a/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs b/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs index c6424627aa..acc41151fb 100644 --- a/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs @@ -104,7 +104,7 @@ public async Tasks.Task CompareToOtherElementNavigator() var json = TestDataHelper.ReadTestData("TestPatient.json"); var xml = TestDataHelper.ReadTestData("TestPatient.xml"); - var poco = await (new FhirJsonParser()).ParseAsync(json); + var poco = (new FhirJsonParser()).Parse(json); var pocoP = poco.ToTypedElement(); var jsonP = (await FhirJsonNode.ParseAsync(json, settings: new FhirJsonParsingSettings { AllowJsonComments = true })) .ToTypedElement(new PocoStructureDefinitionSummaryProvider()); @@ -178,10 +178,10 @@ void extract() } [TestMethod] - public async Tasks.Task ValidateFiveWs() + public void ValidateFiveWs() { var json = TestDataHelper.ReadTestData("test-observation.json"); - var poco = await (new FhirJsonParser()).ParseAsync(json); + var poco = (new FhirJsonParser()).Parse(json); var inspector = ModelInfo.ModelInspector; var aResourceMapping = inspector.FindClassMapping(typeof(Observation)); diff --git a/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs b/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs index d47e5d11fd..ef3dbc95f9 100644 --- a/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs +++ b/src/Hl7.Fhir.Shared.Tests/Model/DeepCopyTest.cs @@ -22,12 +22,12 @@ namespace Hl7.Fhir.Tests.Model public class DeepCopyTest { [TestMethod] - public async Task CheckCopyAllFields() + public void CheckCopyAllFields() { string xml = ReadTestData("TestPatient.xml"); - var p = await new FhirXmlParser().ParseAsync(xml); - var p2 = (Patient)p.DeepCopy(); + var p = new FhirXmlParser().Parse(xml); + var p2 = p.DeepCopy(); var xml2 = new FhirXmlSerializer().SerializeToString(p2); XmlAssert.AreSame("TestPatient.xml", xml, xml2); } diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs index 789b00908c..be5ca9e43a 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs @@ -158,11 +158,11 @@ public void AcceptNsReassignments() [TestMethod] - public async Tasks.Task RetainSpacesInAttribute() + public void RetainSpacesInAttribute() { var xml = ""; - var basic = await FhirXmlParser.ParseAsync(xml); + var basic = FhirXmlParser.Parse(xml); Assert.IsTrue(basic.GetStringExtension("http://blabla.nl").Contains("\n")); @@ -170,24 +170,27 @@ public async Tasks.Task RetainSpacesInAttribute() Assert.IsTrue(outp.Contains(" ")); } - internal FhirXmlParser FhirXmlParser = new FhirXmlParser(); - internal FhirJsonParser FhirJsonParser = new FhirJsonParser(); - internal FhirXmlSerializer FhirXmlSerializer = new FhirXmlSerializer(); - internal FhirJsonSerializer FhirJsonSerializer = new FhirJsonSerializer(); + // Test legacy behaviour +#pragma warning disable CS0618 // Type or member is obsolete + internal FhirXmlParser FhirXmlParser = new(new ParserSettings { PermissiveParsing = true }); + internal FhirJsonParser FhirJsonParser = new(new ParserSettings { PermissiveParsing = true }); +#pragma warning restore CS0618 // Type or member is obsolete + internal FhirXmlSerializer FhirXmlSerializer = new(); + internal FhirJsonSerializer FhirJsonSerializer = new(); [TestMethod] - public async Tasks.Task ParsePerfJson() + public void ParsePerfJson() { string json = TestDataHelper.ReadTestData("TestPatient.json"); var pser = new FhirJsonParser(); // Assume that we can happily read the patient gender when enums are enforced - var p = await pser.ParseAsync(json); + var p = pser.Parse(json); var sw = new Stopwatch(); sw.Start(); for (var i = 0; i < 500; i++) - p = await pser.ParseAsync(json); + p = pser.Parse(json); sw.Stop(); Debug.WriteLine($"Parsing took {sw.ElapsedMilliseconds / 500.0 * 1000} micros"); } @@ -211,20 +214,20 @@ public void ParsePerfXml() [TestMethod] - public async Tasks.Task AcceptUnknownEnums() + public void AcceptUnknownEnums() { string json = TestDataHelper.ReadTestData("TestPatient.json"); var pser = new FhirJsonParser(); // Assume that we can happily read the patient gender when enums are enforced - var p = await pser.ParseAsync(json); + var p = pser.Parse(json); Assert.IsNotNull(p.Gender); Assert.AreEqual("male", p.GenderElement.ObjectValue); Assert.AreEqual(AdministrativeGender.Male, p.Gender.Value); // Verify that if we relax the restriction that everything still works pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; - p = await pser.ParseAsync(json); + p = pser.Parse(json); Assert.IsNotNull(p.Gender); Assert.AreEqual("male", p.GenderElement.ObjectValue); @@ -238,7 +241,7 @@ public async Tasks.Task AcceptUnknownEnums() try { pser.Settings = pser.Settings with { AllowUnrecognizedEnums = false }; - await pser.ParseAsync(xml2); + pser.Parse(xml2); Assert.Fail(); } catch (FormatException) @@ -248,7 +251,7 @@ public async Tasks.Task AcceptUnknownEnums() // Now, allow unknown enums and check support pser.Settings = pser.Settings with { AllowUnrecognizedEnums = true }; - p = await pser.ParseAsync(xml2); + p = pser.Parse(xml2); Assert.ThrowsException(() => p.Gender); Assert.AreEqual("superman", p.GenderElement.ObjectValue); } @@ -262,26 +265,26 @@ public async Tasks.Task EdgecaseRoundtrip() string json = TestDataHelper.ReadTestData("json-edge-cases.json"); var tempPath = Path.GetTempPath(); - var poco = await FhirJsonParser.ParseAsync(json); + var poco = FhirJsonParser.Parse(json); Assert.IsNotNull(poco); var xml = FhirXmlSerializer.SerializeToString(poco); Assert.IsNotNull(xml); await File.WriteAllTextAsync(Path.Combine(tempPath, "edgecase.xml"), xml); - poco = await FhirXmlParser.ParseAsync(xml); + poco = FhirXmlParser.Parse(xml); Assert.IsNotNull(poco); var json2 = FhirJsonSerializer.SerializeToString(poco); Assert.IsNotNull(json2); await File.WriteAllTextAsync(Path.Combine(tempPath, "edgecase.json"), json2); - List errors = new List(); + List errors = []; JsonAssert.AreSame("edgecase.json", json, json2, errors); Console.WriteLine(String.Join("\r\n", errors)); Assert.AreEqual(0, errors.Count, "Errors were encountered comparing converted content"); } [TestMethod] - public async Tasks.Task ContainedBaseIsNotAddedToId() + public void ContainedBaseIsNotAddedToId() { var p = new Patient() { Id = "jaap" }; var o = new Observation() { Subject = new ResourceReference() { Reference = "#" + p.Id } }; @@ -291,7 +294,7 @@ public async Tasks.Task ContainedBaseIsNotAddedToId() var xml = FhirXmlSerializer.SerializeToString(o); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); - var o2 = await FhirXmlParser.ParseAsync(xml); + var o2 = FhirXmlParser.Parse(xml); o2.ResourceBase = new Uri("http://nu.nl/fhir"); xml = FhirXmlSerializer.SerializeToString(o2); Assert.IsTrue(xml.Contains("value=\"#jaap\"")); @@ -299,21 +302,21 @@ public async Tasks.Task ContainedBaseIsNotAddedToId() [TestMethod] - public async Tasks.Task EmptyRoundTrip() + public void EmptyRoundTrip() { var patient = new Patient { - Identifier = new List - { + Identifier = + [ new Identifier("https://mydomain.com/identifiers/Something", "123"), new Identifier("https://mydomain.com/identifiers/Spaces", " "), new Identifier("https://mydomain.com/identifiers/Empty", string.Empty), new Identifier("https://mydomain.com/identifiers/Null", null) - } + ] }; var json = FhirJsonSerializer.SerializeToString(patient); - var parsedPatient = await FhirJsonParser.ParseAsync(json); + var parsedPatient = FhirJsonParser.Parse(json); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); for (var i = 0; i < patient.Identifier.Count; i++) @@ -330,7 +333,7 @@ public async Tasks.Task EmptyRoundTrip() } var xml = FhirXmlSerializer.SerializeToString(patient); - parsedPatient = await FhirXmlParser.ParseAsync(xml); + parsedPatient = FhirXmlParser.Parse(xml); Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); for (var i = 0; i < patient.Identifier.Count; i++) @@ -349,7 +352,7 @@ public async Tasks.Task EmptyRoundTrip() [TestMethod] - public async Tasks.Task SerializeNarrativeWithQuotes() + public void SerializeNarrativeWithQuotes() { var p = new Patient { @@ -357,19 +360,19 @@ public async Tasks.Task SerializeNarrativeWithQuotes() }; var xml = FhirXmlSerializer.SerializeToString(p); - Assert.IsNotNull(await FhirXmlParser.ParseAsync(xml)); + Assert.IsNotNull(FhirXmlParser.Parse(xml)); var json = FhirJsonSerializer.SerializeToString(p); - Assert.IsNotNull(await FhirJsonParser.ParseAsync(json)); + Assert.IsNotNull(FhirJsonParser.Parse(json)); } [TestMethod] - public async Tasks.Task NarrativeMustBeValidXml() + public void NarrativeMustBeValidXml() { try { var json = "{\"resourceType\": \"Patient\", \"text\": {\"status\": \"generated\", \"div\": \"text without div\" } }"; - var patient = await new FhirJsonParser().ParseAsync(json); + var patient = new FhirJsonParser().Parse(json); Assert.Fail("Should have thrown on invalid Div format"); } diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs index 4f878b1e68..249867eb16 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs @@ -44,9 +44,9 @@ public void SerializeMetaJson() } [TestMethod] - public async Tasks.Task ParseMetaXml() + public void ParseMetaXml() { - var poco = (Meta)(await new FhirXmlParser().ParseAsync(metaXml, typeof(Meta))); + var poco = (new FhirXmlParser().Parse(metaXml)); var xml = new FhirXmlSerializer().SerializeToString(poco, rootName: "meta"); Assert.IsTrue(poco.IsExactly(metaPoco)); @@ -70,9 +70,9 @@ public void ParsePatientXmlNullType() internal FhirJsonSerializer FhirJsonSerializer = new FhirJsonSerializer(); [TestMethod] - public async Tasks.Task ParseMetaJson() + public void ParseMetaJson() { - var poco = (Meta)await (new FhirJsonParser().ParseAsync(metaJson, typeof(Meta))); + var poco = (new FhirJsonParser().Parse(metaJson)); var json = FhirJsonSerializer.SerializeToString(poco); Assert.IsTrue(poco.IsExactly(metaPoco)); @@ -80,11 +80,11 @@ public async Tasks.Task ParseMetaJson() } [TestMethod] - public async Tasks.Task ParsePatientJsonNullType() + public void ParsePatientJsonNullType() { string jsonPatient = TestDataHelper.ReadTestData("TestPatient.json"); - var poco = await new FhirJsonParser().ParseAsync(jsonPatient); + var poco = new FhirJsonParser().Parse(jsonPatient); Assert.AreEqual(((Patient)poco).Id, "pat1"); Assert.AreEqual(((Patient)poco).Contained.First().Id, "1"); @@ -171,7 +171,7 @@ public void TestDecimalPrecisionSerializationInJson() } [TestMethod] - public async Tasks.Task TestLongDecimalSerialization() + public void TestLongDecimalSerialization() { var dec = 3.1415926535897932384626433833m; var ext = new FhirDecimal(dec); @@ -179,13 +179,13 @@ public async Tasks.Task TestLongDecimalSerialization() obs.AddExtension("http://example.org/DecimalPrecision", ext); var json = FhirJsonSerializer.SerializeToString(obs); - var obs2 = await FhirJsonParser.ParseAsync(json); + var obs2 = FhirJsonParser.Parse(json); Assert.AreEqual(dec.ToString(CultureInfo.InvariantCulture), ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); } [TestMethod] - public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() + public void TestParseUnkownPolymorphPropertyInJson() { var dec6 = 6m; var ext = new FhirDecimal(dec6); @@ -193,7 +193,7 @@ public async Tasks.Task TestParseUnkownPolymorphPropertyInJson() var json = FhirJsonSerializer.SerializeToString(obs); try { - var obs2 = await FhirJsonParser.ParseAsync(json); + var obs2 = FhirJsonParser.Parse(json); Assert.Fail("valueDecimal is not a known type for Observation"); } catch (FormatException) @@ -261,11 +261,11 @@ public void TryXXEExploit() } [TestMethod] - public async Tasks.Task SerializeUnknownEnums() + public void SerializeUnknownEnums() { string xml = TestDataHelper.ReadTestData("TestPatient.xml"); var pser = new FhirXmlParser(); - var p = await pser.ParseAsync(xml); + var p = pser.Parse(xml); string outp = FhirXmlSerializer.SerializeToString(p); Assert.IsTrue(outp.Contains("\"male\"")); @@ -304,12 +304,12 @@ public void TestNullExtensionRemoval() } [TestMethod] - public async Tasks.Task SerializeJsonWithPlainDiv() + public void SerializeJsonWithPlainDiv() { string json = TestDataHelper.ReadTestData(@"TestPatient.json"); Assert.IsNotNull(json); var parser = new FhirJsonParser( new ParserSettings().UsingMode(DeserializationMode.Recoverable)); - var pat = await parser.ParseAsync(json); + var pat = parser.Parse(json); Assert.IsNotNull(pat); var xml = FhirXmlSerializer.SerializeToString(pat); @@ -475,17 +475,17 @@ public void SummarizeSerializingTest() /// This test proves issue 657: https://github.com/FirelyTeam/firely-net-sdk/issues/657 /// [TestMethod] - public async Tasks.Task DateTimeOffsetAccuracyTest() + public void DateTimeOffsetAccuracyTest() { var patient = new Patient { Meta = new Meta { LastUpdated = DateTimeOffset.UtcNow } }; var json = new FhirJsonSerializer().SerializeToString(patient); - var res = await new FhirJsonParser().ParseAsync(json); + var res = new FhirJsonParser().Parse(json); Assert.IsTrue(patient.IsExactly(res), "1"); // Is the parsing still correct without milliseconds? patient = new Patient { Meta = new Meta { LastUpdated = new DateTimeOffset(2018, 8, 13, 13, 41, 56, TimeSpan.Zero) } }; json = "{\"resourceType\":\"Patient\",\"meta\":{\"lastUpdated\":\"2018-08-13T13:41:56+00:00\"}}"; - res = await new FhirJsonParser().ParseAsync(json); + res = new FhirJsonParser().Parse(json); Assert.IsTrue(patient.IsExactly(res), "2"); // Is the serialization still correct without milliseconds? @@ -495,7 +495,7 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() // Is the parsing still correct with a few milliseconds and TimeZone? patient = new Patient { Meta = new Meta { LastUpdated = new DateTimeOffset(2018, 8, 13, 13, 41, 56, 12, TimeSpan.Zero) } }; json = "{\"resourceType\":\"Patient\",\"meta\":{\"lastUpdated\":\"2018-08-13T13:41:56.012+00:00\"}}"; - res = await new FhirJsonParser().ParseAsync(json); + res = new FhirJsonParser().Parse(json); Assert.IsTrue(patient.IsExactly(res), "4"); // Is the serialization still correct with a few milliseconds? @@ -504,12 +504,15 @@ public async Tasks.Task DateTimeOffsetAccuracyTest() } [TestMethod] - public async Tasks.Task SerializerHandlesEmptyChildObjects() + public void SerializerHandlesEmptyChildObjects() { - var fhirJsonParser = new FhirJsonParser(); + // Test legacy behaviour +#pragma warning disable CS0618 // Type or member is obsolete + var fhirJsonParser = new FhirJsonParser(new ParserSettings { PermissiveParsing = true }); +#pragma warning restore CS0618 // Type or member is obsolete string json = TestDataHelper.ReadTestData("TestPatient.json"); - var poco = await fhirJsonParser.ParseAsync(json); + var poco = fhirJsonParser.Parse(json); Assert.AreEqual(1, poco.Name.Count); @@ -517,7 +520,7 @@ public async Tasks.Task SerializerHandlesEmptyChildObjects() var reserialized = poco.ToJson(); - var newPoco = await fhirJsonParser.ParseAsync(reserialized); + var newPoco = fhirJsonParser.Parse(reserialized); Assert.AreEqual(1, newPoco.Name.Count); } diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs index 500d7742d1..b0d6bde8a4 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/ValueQuantityParsingTests.cs @@ -76,7 +76,7 @@ async static Tasks.Task XmlRoundTripAsync(T resource) where T : Resource await File.WriteAllTextAsync(xmlFile, xml); xml = await File.ReadAllTextAsync(xmlFile); - var parsed = await new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(xml); + var parsed = new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).Parse(xml); return parsed; } @@ -90,7 +90,7 @@ static async Tasks.Task JsonRoundTrip(T resource) where T : Resource await File.WriteAllTextAsync(jsonFile, json); json = await File.ReadAllTextAsync(jsonFile); - var parsed = await new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).ParseAsync(json); + var parsed = new FhirJsonParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)).Parse(json); return parsed; } diff --git a/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs b/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs index 0a5837b073..4952323e6b 100644 --- a/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs +++ b/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs @@ -1,14 +1,10 @@ using Hl7.Fhir.Model; -using Hl7.Fhir.Tests; -using Hl7.Fhir.Utility; +using Hl7.Fhir.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Text; -using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Tests { @@ -20,21 +16,21 @@ namespace Hl7.Fhir.Tests public class TestDataVersionCheck { [TestMethod] // not everything parses correctly - public async Tasks.Task VerifyAllTestData() + public void VerifyAllTestData() { string location = typeof(TestDataHelper).GetTypeInfo().Assembly.Location; var path = Path.GetDirectoryName(location) + "/TestData"; Console.WriteLine(path); StringBuilder issues = new StringBuilder(); - await ValidateFolder(path, path, issues); + validateFolder(path, path, issues); Console.Write(issues.ToString()); Assert.AreEqual("", issues.ToString()); } - private async Tasks.Task ValidateFolder(string basePath, string path, StringBuilder issues) + private static void validateFolder(string basePath, string path, StringBuilder issues) { - var xmlParser = new Fhir.Serialization.FhirXmlParser(); - var jsonParser = new Fhir.Serialization.FhirJsonParser(); + var xmlParser = new FhirXmlParser(); + var jsonParser = new FhirJsonParser(); Console.WriteLine($"Validating test files in {path.Replace(basePath, "")}"); foreach (var item in Directory.EnumerateFiles(path)) { @@ -50,12 +46,12 @@ private async Tasks.Task ValidateFolder(string basePath, string path, StringBuil if (new FileInfo(item).Extension == ".xml") { Console.WriteLine($" {item.Replace(path+"/", "")}"); - await xmlParser.ParseAsync(content); + xmlParser.Parse(content); } else if (new FileInfo(item).Extension == ".json") { Console.WriteLine($" {item.Replace(path + "/", "")}"); - await jsonParser.ParseAsync(content); + jsonParser.Parse(content); } else { @@ -71,7 +67,7 @@ private async Tasks.Task ValidateFolder(string basePath, string path, StringBuil } foreach (var item in Directory.EnumerateDirectories(path)) { - await ValidateFolder(basePath, item, issues); + validateFolder(basePath, item, issues); } } } diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs index 49d4a9b2ae..e8cbd8589e 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs @@ -16,52 +16,5 @@ namespace Hl7.Fhir.Serialization; -public class FhirJsonParser(ParserSettings? settings = null) : BaseFhirParser -{ - public ParserSettings Settings { get; set; } = settings ?? new ParserSettings(); - - /// - public T Parse(string json) where T : Base => (T)Parse(json, typeof(T)); - - public async Tasks.Task ParseAsync(string json) where T : Base - => (T)await ParseAsync(json, typeof(T)).ConfigureAwait(false); - - /// - public T Parse(JsonReader reader) where T : Base => (T)Parse(reader, typeof(T)); - - public async Tasks.Task ParseAsync(JsonReader reader) where T : Base - => (T)await ParseAsync(reader, typeof(T)).ConfigureAwait(false); - - /// - public Base Parse(string json, Type? dataType = null) - { - var rootName = dataType != null ? ModelInfo.GetFhirTypeNameForType(dataType) : null; - var jsonReader = FhirJsonNode.Parse(json, rootName, BuildJsonParserSettings(Settings)); - return parse(jsonReader, dataType); - } - - public async Tasks.Task ParseAsync(string json, Type? dataType = null) - { - var rootName = dataType != null ? ModelInfo.GetFhirTypeNameForType(dataType) : null; - var jsonReader = await FhirJsonNode.ParseAsync(json, rootName, BuildJsonParserSettings(Settings)).ConfigureAwait(false); - return parse(jsonReader, dataType); - } - - /// - public Base Parse(JsonReader reader, Type? dataType = null) - { - var rootName = dataType != null ? ModelInfo.GetFhirTypeNameForType(dataType) : null; - var jsonReader = FhirJsonNode.Read(reader, rootName, BuildJsonParserSettings(Settings)); - return parse(jsonReader, dataType); - } - - public async Tasks.Task ParseAsync(JsonReader reader, Type? dataType = null) - { - var rootName = dataType != null ? ModelInfo.GetFhirTypeNameForType(dataType) : null; - var jsonReader = await FhirJsonNode.ReadAsync(reader, rootName, BuildJsonParserSettings(Settings)).ConfigureAwait(false); - return parse(jsonReader, dataType); - } - - private Base parse(ISourceNode node, Type? type = null) => - node.ToPoco(ModelInfo.ModelInspector, type, BuildPocoBuilderSettings(Settings)); -} \ No newline at end of file +public class FhirJsonParser(ParserSettings? settings = null) + : BaseFhirJsonParser(ModelInfo.ModelInspector, settings); \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs index 6980d5aa87..f380057257 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs @@ -8,56 +8,9 @@ #nullable enable -using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; -using System; -using System.Xml; -using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Serialization; -public class FhirXmlParser(ParserSettings? settings = null) : BaseFhirParser -{ - public ParserSettings Settings { get; set; } = settings ?? new ParserSettings(); - - /// - public T Parse(XmlReader reader) where T : Base => (T)Parse(reader, typeof(T)); - - public async Tasks.Task ParseAsync(XmlReader reader) where T : Base - => await ParseAsync(reader, typeof(T)).ConfigureAwait(false) as T; - - /// - public T Parse(string xml) where T : Base => (T)Parse(xml, typeof(T)); - - public async Tasks.Task ParseAsync(string xml) where T : Base - => await ParseAsync(xml, typeof(T)).ConfigureAwait(false) as T; - - /// - public Base Parse(string xml, Type? dataType = null) - { - var xmlReader = FhirXmlNode.Parse(xml, BuildXmlParsingSettings(Settings)); - return parse(xmlReader, dataType); - } - - public async Tasks.Task ParseAsync(string xml, Type? dataType = null) - { - var xmlReader = await FhirXmlNode.ParseAsync(xml, BuildXmlParsingSettings(Settings)).ConfigureAwait(false); - return parse(xmlReader, dataType); - } - - /// - public Base Parse(XmlReader reader, Type? dataType = null) - { - var xmlReader = FhirXmlNode.Read(reader, BuildXmlParsingSettings(Settings)); - return parse(xmlReader, dataType); - } - - public async Tasks.Task ParseAsync(XmlReader reader, Type? dataType = null) - { - var xmlReader = await FhirXmlNode.ReadAsync(reader, BuildXmlParsingSettings(Settings)).ConfigureAwait(false); - return parse(xmlReader, dataType); - } - - private Base parse(ISourceNode node, Type? type = null) => - node.ToPoco(ModelInfo.ModelInspector, type, BuildPocoBuilderSettings(Settings)); -} \ No newline at end of file +public class FhirXmlParser(ParserSettings? settings = null) + : BaseFhirXmlParser(ModelInfo.ModelInspector, settings); \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs index cfa0b0d099..fd2cee6a0d 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlPocoDeserializer.cs @@ -1,30 +1,24 @@ #nullable enable -using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; -using System; -using System.Reflection; -namespace Hl7.Fhir.Serialization +namespace Hl7.Fhir.Serialization; + +/// +public class FhirXmlPocoDeserializer : BaseFhirXmlPocoDeserializer { - /// - public class FhirXmlPocoDeserializer : BaseFhirXmlPocoDeserializer + /// + /// Construct a new FHIR XML deserializer, based on the currently used FHIR version. + /// + public FhirXmlPocoDeserializer() : base(ModelInfo.ModelInspector) { - /// - /// Construct a new FHIR XML deserializer, based on the currently used FHIR version. - /// - public FhirXmlPocoDeserializer() : base(ModelInfo.ModelInspector) - { - } - - /// - /// Construct a new FHIR XML deserializer, based on the currently used FHIR version. - /// - /// Deserialization settings - public FhirXmlPocoDeserializer(ParserSettings settings) : base(ModelInfo.ModelInspector, settings) - { - } } -} -#nullable restore \ No newline at end of file + /// + /// Construct a new FHIR XML deserializer, based on the currently used FHIR version. + /// + /// Deserialization settings + public FhirXmlPocoDeserializer(ParserSettings settings) : base(ModelInfo.ModelInspector, settings) + { + } +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs index df0143daf6..bf41c1f7ec 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/Source/ArtifactSourceTests.cs @@ -433,7 +433,7 @@ public void TestAccessPermissions() // https://github.com/FirelyTeam/firely-net-sdk/issues/875 [TestMethod] - public async Tasks.Task OpenDuplicateFileNames() + public void OpenDuplicateFileNames() { var testPath = prepareExampleDirectory(out int _); @@ -451,19 +451,17 @@ public async Tasks.Task OpenDuplicateFileNames() var dirSource = new DirectorySource(testPath, new DirectorySourceSettings() { IncludeSubDirectories = true }); - async Tasks.Task OpenStream(string filePath) + Resource OpenStream(string filePath) { - using (var stream = dirSource.LoadArtifactByName(filePath)) - { - return await new FhirXmlParser().ParseAsync(SerializationUtil.XmlReaderFromStream(stream)); - } + using var stream = dirSource.LoadArtifactByName(filePath); + return new FhirXmlParser().Parse(SerializationUtil.XmlReaderFromStream(stream)); } // Retrieve artifacts by full path var rootFilePath = Path.Combine(testPath, srcFile); Assert.IsTrue(File.Exists(rootFilePath)); - var res = await OpenStream(rootFilePath); + var res = OpenStream(rootFilePath); Assert.IsNotNull(res); // Modify the resource id and save back var dupId = res.Id; @@ -473,7 +471,7 @@ async Tasks.Task OpenStream(string filePath) var dupFilePath = Path.Combine(fullSubFolderPath, srcFile); Assert.IsTrue(File.Exists(dupFilePath)); - res = await OpenStream(dupFilePath); + res = OpenStream(dupFilePath); Assert.IsNotNull(res); // Verify that we received the duplicate file from subfolder, // not the modified file in the root content directory @@ -482,14 +480,14 @@ async Tasks.Task OpenStream(string filePath) // Retrieve artifact by file name // Should return nearest match, i.e. from content directory - res = await OpenStream(srcFile); + res = OpenStream(srcFile); Assert.IsNotNull(res); Assert.AreEqual(dupId, res.Id); // Retrieve artifact by relative path // Should return duplicate from subfolder var relPath = Path.Combine(subFolderName, srcFile); - res = await OpenStream(relPath); + res = OpenStream(relPath); Assert.IsNotNull(res); Assert.AreEqual(dupId, res.Id); } diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs b/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs index 2a4362455d..641864d0ee 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs @@ -1,101 +1,84 @@ using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Xml; -using Tasks = System.Threading.Tasks; -namespace Hl7.Fhir.Specification.Tests +namespace Hl7.Fhir.Specification.Tests; + +/// +/// All this is to do is read all the unit test data to ensure that they are all compatible with STU3 +/// (By just trying to de-serialize all the content) +/// +[TestClass] +public class SpecificationTestDataVersionCheck { - /// - /// All this is to do is read all the unit test data to ensure that they are all compatible with STU3 - /// (By just trying to de-serialize all the content) - /// - [TestClass] - public class SpecificationTestDataVersionCheck + [TestMethod] + public void VerifyAllTestDataSpecification() { - [TestMethod] - public async Tasks.Task VerifyAllTestDataSpecification() - { - string location = typeof(TestDataHelper).GetTypeInfo().Assembly.Location; - var path = Path.GetDirectoryName(location) + "/TestData"; - Console.WriteLine(path); - List issues = new List(); - await ValidateFolder(path, path, issues); - Assert.AreEqual(0, issues.Count); - } + string location = typeof(TestDataHelper).GetTypeInfo().Assembly.Location; + var path = Path.GetDirectoryName(location) + "/TestData"; + Console.WriteLine(path); + List issues = []; + validateFolder(path, path, issues); + Assert.AreEqual(0, issues.Count); + } - private async Tasks.Task ValidateFolder(string basePath, string path, List issues) - { - if (path.Contains("grahame-validation-examples")) - return; - if (path.Contains("source-test")) - return; - if (path.Contains("Type Slicing")) - return; - if (path.Contains("validation-test-suite")) - return; + private static void validateFolder(string basePath, string path, List issues) + { + if (path.Contains("grahame-validation-examples")) + return; + if (path.Contains("source-test")) + return; + if (path.Contains("Type Slicing")) + return; + if (path.Contains("validation-test-suite")) + return; - var xmlParser = new Hl7.Fhir.Serialization.FhirXmlParser(); - var jsonParser = new Serialization.FhirJsonParser(); - Console.WriteLine($"Validating test files in {path.Replace(basePath, "")}"); - foreach (var item in Directory.EnumerateFiles(path)) + var xmlParser = new FhirXmlParser(); + var jsonParser = new FhirJsonParser(); + Console.WriteLine($"Validating test files in {path.Replace(basePath, "")}"); + foreach (var item in Directory.EnumerateFiles(path)) + { + string content = File.ReadAllText(item); + try { - string content = File.ReadAllText(item); - Hl7.Fhir.Model.Resource resource = null; - try + if (item.EndsWith(".dll")) + continue; + if (item.EndsWith(".exe")) + continue; + if (item.EndsWith(".pdb")) + continue; + if (item.EndsWith("manifest.json")) + continue; + if (new FileInfo(item).Extension == ".xml") + { + // Console.WriteLine($" {item.Replace(path + "\\", "")}"); + xmlParser.Parse(content); + } + else if (new FileInfo(item).Extension == ".json") { - if (item.EndsWith(".dll")) - continue; - if (item.EndsWith(".exe")) - continue; - if (item.EndsWith(".pdb")) - continue; - if (item.EndsWith("manifest.json")) - continue; - if (new FileInfo(item).Extension == ".xml") - { - // Console.WriteLine($" {item.Replace(path + "\\", "")}"); - resource = await xmlParser.ParseAsync(content); - } - else if (new FileInfo(item).Extension == ".json") - { - // Console.WriteLine($" {item.Replace(path + "\\", "")}"); - resource = await jsonParser.ParseAsync(content); - } - else - { - Console.WriteLine($" {item} (unknown content)"); - } + // Console.WriteLine($" {item.Replace(path + "\\", "")}"); + jsonParser.Parse(content); } - catch (Exception ex) + else { - Console.WriteLine($" {item} (parse error)"); - Console.WriteLine($" --> {ex.Message}"); - issues.Add($" --> {ex.Message}"); + Console.WriteLine($" {item} (unknown content)"); } } - foreach (var item in Directory.EnumerateDirectories(path)) + catch (Exception ex) { - await ValidateFolder(basePath, item, issues); + Console.WriteLine($" {item} (parse error)"); + Console.WriteLine($" --> {ex.Message}"); + issues.Add($" --> {ex.Message}"); } } - - private void RenameElement(XmlElement element, string oldValue, string newValue, XmlNamespaceManager nm) + foreach (var item in Directory.EnumerateDirectories(path)) { - var nodes = element.SelectNodes("fhir:" +oldValue, nm); - foreach (XmlElement elem in nodes) - { - XmlElement n = element.OwnerDocument.CreateElement(newValue, "http://hl7.org/fhir"); - n.InnerXml = elem.InnerXml; - foreach (XmlAttribute attr in elem.Attributes) - n.Attributes.Append(attr); - element.InsertBefore(n, elem); - element.RemoveChild(elem); - } + validateFolder(basePath, item, issues); } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs index c309759670..f24129ec9a 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs @@ -545,7 +545,7 @@ async Tasks.Task ExecuteTest(SnapshotGenerationManifestTest test) var expected = test.Fail ? null : Load(test.Id, expectedFileNameFormat); Assert.IsTrue(test.Fail || expected.HasSnapshot); - var output = (StructureDefinition)input.DeepCopy(); + var output = input.DeepCopy(); Exception exception = null; try { diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs index aeecf8b601..52ed97f0da 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Source/ArtifactSourceTests.cs @@ -431,7 +431,7 @@ public void TestAccessPermissions() // https://github.com/FirelyTeam/firely-net-sdk/issues/875 [TestMethod] - public async Tasks.Task OpenDuplicateFileNames() + public void OpenDuplicateFileNames() { var testPath = prepareExampleDirectory(out int _); @@ -449,19 +449,17 @@ public async Tasks.Task OpenDuplicateFileNames() var dirSource = new DirectorySource(testPath, new DirectorySourceSettings() { IncludeSubDirectories = true }); - async Tasks.Task OpenStream(string filePath) + Resource OpenStream(string filePath) { - using (var stream = dirSource.LoadArtifactByName(filePath)) - { - return await new FhirXmlParser().ParseAsync(SerializationUtil.XmlReaderFromStream(stream)); - } + using var stream = dirSource.LoadArtifactByName(filePath); + return new FhirXmlParser().Parse(SerializationUtil.XmlReaderFromStream(stream)); } // Retrieve artifacts by full path var rootFilePath = Path.Combine(testPath, srcFile); Assert.IsTrue(File.Exists(rootFilePath)); - var res = await OpenStream(rootFilePath); + var res = OpenStream(rootFilePath); Assert.IsNotNull(res); // Modify the resource id and save back var dupId = res.Id; @@ -471,7 +469,7 @@ async Tasks.Task OpenStream(string filePath) var dupFilePath = Path.Combine(fullSubFolderPath, srcFile); Assert.IsTrue(File.Exists(dupFilePath)); - res = await OpenStream(dupFilePath); + res = OpenStream(dupFilePath); Assert.IsNotNull(res); // Verify that we received the duplicate file from subfolder, // not the modified file in the root content directory @@ -480,14 +478,14 @@ async Tasks.Task OpenStream(string filePath) // Retrieve artifact by file name // Should return nearest match, i.e. from content directory - res = await OpenStream(srcFile); + res = OpenStream(srcFile); Assert.IsNotNull(res); Assert.AreEqual(dupId, res.Id); // Retrieve artifact by relative path // Should return duplicate from subfolder var relPath = Path.Combine(subFolderName, srcFile); - res = await OpenStream(relPath); + res = OpenStream(relPath); Assert.IsNotNull(res); Assert.AreEqual(dupId, res.Id); } diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs b/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs index 1e20396084..3c8fa016be 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs @@ -1,4 +1,5 @@ using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -39,18 +40,18 @@ private async Tasks.Task ValidateFolder(string basePath, string path, List(content); + xmlParser.Parse(content); } else if (new FileInfo(item).Extension == ".json") { // Console.WriteLine($" {item.Replace(path + "\\", "")}"); - resource = await jsonParser.ParseAsync(content); + jsonParser.Parse(content); } else { @@ -132,7 +133,7 @@ private async Tasks.Task ValidateFolder(string basePath, string path, List(xmlDoc.OuterXml); + xmlParser.Parse(xmlDoc.OuterXml); Console.WriteLine($" conversion to {ModelInfo.Version} success {new FileInfo(item).Name}"); // Save this back to the filesystem since it works! @@ -173,4 +174,4 @@ private void RenameElement(XmlElement element, string oldValue, string newValue, } } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs index d7ff29e64c..13a761195d 100644 --- a/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs +++ b/src/Hl7.Fhir.Support.Poco.Tests/NewPocoSerializers/FhirXmlDeserializationTests.cs @@ -190,7 +190,7 @@ public void TryDeserializeResourceWithSchemaAttribute() var reader = constructReader(content); reader.Read(); - var deserializer = getTestDeserializer(new()); + var deserializer = getTestDeserializer(new ParserSettings { DisallowXsiAttributesOnRoot = true }); var state = new FhirXmlPocoDeserializerState(); var resource = deserializer.DeserializeResourceInternal(reader, state); diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs index 7f36df8dcc..03ff2df232 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs @@ -1,4 +1,5 @@ using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; using Hl7.FhirPath.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -38,7 +39,7 @@ private void ValidateFolder(string basePath, string path, List issues) foreach (var item in Directory.EnumerateFiles(path)) { string content = File.ReadAllText(item); - Hl7.Fhir.Model.Resource resource = null; + try { // Exclude the fhirpath unit test config files @@ -62,12 +63,12 @@ private void ValidateFolder(string basePath, string path, List issues) if (new FileInfo(item).Extension == ".xml") { // Console.WriteLine($" {item.Replace(path + "\\", "")}"); - resource = xmlParser.Parse(content); + xmlParser.Parse(content); } else if (new FileInfo(item).Extension == ".json") { // Console.WriteLine($" {item.Replace(path + "\\", "")}"); - resource = jsonParser.Parse(content); + jsonParser.Parse(content); } else { diff --git a/src/Hl7.FhirPath.R4.Tests/TestData/bundle-contained-references.xml b/src/Hl7.FhirPath.R4.Tests/TestData/bundle-contained-references.xml index 7085260ae4..ab7a51b409 100644 --- a/src/Hl7.FhirPath.R4.Tests/TestData/bundle-contained-references.xml +++ b/src/Hl7.FhirPath.R4.Tests/TestData/bundle-contained-references.xml @@ -41,10 +41,6 @@ - - - - @@ -53,6 +49,10 @@ + + + + From c25f3ebd8d6e24ac518f30aa91cb730eda19013e Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Fri, 7 Mar 2025 19:09:35 +0100 Subject: [PATCH 3/8] Made unit tests work --- .../CompatibilitySuppressions.xml | 432 ++++++++++++++++-- .../ElementModel/ElementNodeComparator.cs | 13 +- .../BaseFhirJsonPocoDeserializer.cs | 3 +- .../BaseFhirXmlPocoDeserializer.cs | 30 +- .../DeserializationFailedException.cs | 15 +- .../Serialization/FhirXmlException.cs | 10 +- .../Serialization/JsonParsingExtensions.cs | 63 ++- .../Serialization/ParserSettings.cs | 4 +- .../Utility/SerializationUtil.cs | 2 +- .../CompatibilitySuppressions.xml | 56 +++ .../ElementNodeTests.cs | 2 +- .../Serialization/SerializationTests.cs | 9 +- src/Hl7.Fhir.R4/CompatibilitySuppressions.xml | 266 +++++++++++ .../Serialization/SerializationTests.cs | 5 +- .../CompatibilitySuppressions.xml | 266 +++++++++++ .../Serialization/ResourceParsingTests.cs | 2 +- src/Hl7.Fhir.R5/CompatibilitySuppressions.xml | 266 +++++++++++ .../ElementModel/PocoTypedElementTests.cs | 2 +- .../Serialization/ResourceParsingTests.cs | 68 ++- .../Serialization/SerializationTests.cs | 24 +- .../TestDataVersionCheck.cs | 12 +- .../CompatibilitySuppressions.xml | 322 +++++++++++++ .../SerializeDemoPatientJson.cs | 5 +- .../SerializeDemoPatientXml.cs | 7 +- .../SerializePartialTree.cs | 26 +- .../ElementModel/PocoTypedElementTests.cs | 2 +- .../Serialization/ResourceParsingTests.cs | 66 ++- .../Serialization/SerializationTests.cs | 19 +- .../TestDataVersionCheck.cs | 5 +- .../Validation/ValidatePatient.cs | 2 +- .../Serialization/FhirJsonParser.cs | 18 +- .../Serialization/FhirXmlParser.cs | 29 +- .../SpecificationTestDataVersionCheck.cs | 4 +- .../SnapshotGeneratorManifestTests.cs | 25 +- .../SpecificationTestDataVersionCheck.cs | 6 +- .../PocoTests/FhirPathEvaluatorTest.cs | 2 +- .../FhirPathEvaluatorTestFromSpec.cs | 2 +- .../PocoTests/FhirPathTestDataVersionCheck.cs | 4 +- 38 files changed, 1837 insertions(+), 257 deletions(-) diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index 4964b56bda..51461d2ade 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -92,6 +92,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0001 + T:Hl7.Fhir.Serialization.BaseFhirParser + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0001 T:Hl7.Fhir.Serialization.BaseFhirSerializer @@ -344,6 +351,13 @@ lib/netstandard2.1/Hl7.Fhir.Base.dll true + + CP0001 + T:Hl7.Fhir.Serialization.BaseFhirParser + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + CP0001 T:Hl7.Fhir.Serialization.BaseFhirSerializer @@ -519,13 +533,6 @@ lib/net8.0/Hl7.Fhir.Base.dll true - - CP0002 - F:Hl7.Fhir.Serialization.BaseFhirParser.Settings - lib/net8.0/Hl7.Fhir.Base.dll - lib/net8.0/Hl7.Fhir.Base.dll - true - CP0002 F:Hl7.Fhir.Serialization.FhirJsonException.INCORRECT_BASE64_DATA_CODE @@ -1954,6 +1961,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Rest.FhirClientSerializationEngineExtensions.WithLegacySerializer(Hl7.Fhir.Rest.BaseFhirClient) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Rest.FhirClientSettings.get_CompressRequestBody @@ -2075,35 +2089,21 @@ CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.ParserSettings) - lib/net8.0/Hl7.Fhir.Base.dll - lib/net8.0/Hl7.Fhir.Base.dll - true - - - CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse(Hl7.Fhir.ElementModel.ISourceNode,System.Type) - lib/net8.0/Hl7.Fhir.Base.dll - lib/net8.0/Hl7.Fhir.Base.dll - true - - - CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse(Hl7.Fhir.ElementModel.ITypedElement) + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) lib/net8.0/Hl7.Fhir.Base.dll lib/net8.0/Hl7.Fhir.Base.dll true CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse``1(Hl7.Fhir.ElementModel.ISourceNode) + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) lib/net8.0/Hl7.Fhir.Base.dll lib/net8.0/Hl7.Fhir.Base.dll true CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse``1(Hl7.Fhir.ElementModel.ITypedElement) + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer.get_Settings lib/net8.0/Hl7.Fhir.Base.dll lib/net8.0/Hl7.Fhir.Base.dll true @@ -2227,6 +2227,174 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.Clone + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.CopyTo(Hl7.Fhir.Serialization.ParserSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.CopyTo(Hl7.Fhir.Serialization.PocoBuilderSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.CreateDefault + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.get_ExceptionHandler + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.get_TruncateDateTimeToDate + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_AcceptUnknownMembers(System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_AllowUnrecognizedEnums(System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_DisallowXsiAttributesOnRoot(System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_ExceptionHandler(Hl7.Fhir.Utility.ExceptionNotificationHandler) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_PermissiveParsing(System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_TruncateDateTimeToDate(System.Boolean) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeElement(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Type,System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeElement``1(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeObject(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Type,System.Text.Json.Utf8JsonReader@) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeObject``1(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Text.Json.Utf8JsonReader@) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.String) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Text.Json.Utf8JsonReader@) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.String) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.String,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Text.Json.Utf8JsonReader,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.String,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Xml.XmlReader,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Serialization.PropertyDeserializationContext.get_InstancePath @@ -2549,13 +2717,6 @@ lib/netstandard2.1/Hl7.Fhir.Base.dll true - - CP0002 - F:Hl7.Fhir.Serialization.BaseFhirParser.Settings - lib/netstandard2.1/Hl7.Fhir.Base.dll - lib/netstandard2.1/Hl7.Fhir.Base.dll - true - CP0002 F:Hl7.Fhir.Serialization.FhirJsonException.INCORRECT_BASE64_DATA_CODE @@ -3984,6 +4145,13 @@ lib/netstandard2.1/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Rest.FhirClientSerializationEngineExtensions.WithLegacySerializer(Hl7.Fhir.Rest.BaseFhirClient) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Rest.FhirClientSettings.get_CompressRequestBody @@ -4105,35 +4273,21 @@ CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.ParserSettings) + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) lib/netstandard2.1/Hl7.Fhir.Base.dll lib/netstandard2.1/Hl7.Fhir.Base.dll true CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse(Hl7.Fhir.ElementModel.ISourceNode,System.Type) + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) lib/netstandard2.1/Hl7.Fhir.Base.dll lib/netstandard2.1/Hl7.Fhir.Base.dll true CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse(Hl7.Fhir.ElementModel.ITypedElement) - lib/netstandard2.1/Hl7.Fhir.Base.dll - lib/netstandard2.1/Hl7.Fhir.Base.dll - true - - - CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse``1(Hl7.Fhir.ElementModel.ISourceNode) - lib/netstandard2.1/Hl7.Fhir.Base.dll - lib/netstandard2.1/Hl7.Fhir.Base.dll - true - - - CP0002 - M:Hl7.Fhir.Serialization.BaseFhirParser.Parse``1(Hl7.Fhir.ElementModel.ITypedElement) + M:Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer.get_Settings lib/netstandard2.1/Hl7.Fhir.Base.dll lib/netstandard2.1/Hl7.Fhir.Base.dll true @@ -4257,6 +4411,174 @@ lib/netstandard2.1/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.Clone + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.CopyTo(Hl7.Fhir.Serialization.ParserSettings) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.CopyTo(Hl7.Fhir.Serialization.PocoBuilderSettings) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.CreateDefault + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.get_ExceptionHandler + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.get_TruncateDateTimeToDate + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_AcceptUnknownMembers(System.Boolean) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_AllowUnrecognizedEnums(System.Boolean) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_DisallowXsiAttributesOnRoot(System.Boolean) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_ExceptionHandler(Hl7.Fhir.Utility.ExceptionNotificationHandler) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_PermissiveParsing(System.Boolean) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.ParserSettings.set_TruncateDateTimeToDate(System.Boolean) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeElement(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Type,System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeElement``1(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeObject(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Type,System.Text.Json.Utf8JsonReader@) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeObject``1(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Text.Json.Utf8JsonReader@) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.String) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Text.Json.Utf8JsonReader@) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.String) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.DeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.String,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirJsonPocoDeserializer,System.Text.Json.Utf8JsonReader,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.String,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.PocoDeserializationExtensions.TryDeserializeResource(Hl7.Fhir.Serialization.BaseFhirXmlPocoDeserializer,System.Xml.XmlReader,Hl7.Fhir.Model.Resource@,System.Collections.Generic.IEnumerable{Hl7.Fhir.Utility.CodedException}@) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Serialization.PropertyDeserializationContext.get_InstancePath @@ -5755,4 +6077,18 @@ lib/netstandard2.1/Hl7.Fhir.Base.dll true + + CP0019 + M:Hl7.Fhir.Serialization.ParserSettings.#ctor(Hl7.Fhir.Serialization.ParserSettings) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + + + CP0019 + M:Hl7.Fhir.Serialization.ParserSettings.#ctor(Hl7.Fhir.Serialization.ParserSettings) + lib/netstandard2.1/Hl7.Fhir.Base.dll + lib/netstandard2.1/Hl7.Fhir.Base.dll + true + \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ElementNodeComparator.cs b/src/Hl7.Fhir.Base/ElementModel/ElementNodeComparator.cs index b2423414c7..0392869a58 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ElementNodeComparator.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ElementNodeComparator.cs @@ -23,11 +23,16 @@ public static TreeComparisonResult IsEqualTo(this ITypedElement expected, ITyped { if (expected.Name != actual.Name) return TreeComparisonResult.Fail(actual.Location, $"name: was '{actual.Name}', expected '{expected.Name}'"); - if (!Object.Equals(expected.Value, actual.Value)) - return TreeComparisonResult.Fail(actual.Location, $"value: was '{actual.Value}', expected '{expected.Value}'"); + + var cleanedValueL = expected.Value is string el ? el.Replace("\r", "") : expected.Value; + var cleanedValueR = actual.Value is string er ? er.Replace("\r", "") : actual.Value; + + if (!Equals(cleanedValueL, cleanedValueR)) + return TreeComparisonResult.Fail(actual.Location, $"value: was '{cleanedValueL}', expected '{cleanedValueR}'"); if (expected.InstanceType != actual.InstanceType && actual.InstanceType != null) return TreeComparisonResult.Fail(actual.Location, $"type: was '{actual.InstanceType}', expected '{expected.InstanceType}'"); - if (expected.Location != actual.Location) TreeComparisonResult.Fail(actual.Location, $"Path: was '{actual.Location}', expected '{expected.Location}'"); + if (expected.Location != actual.Location) + TreeComparisonResult.Fail(actual.Location, $"Path: was '{actual.Location}', expected '{expected.Location}'"); // Ignore ordering (only relevant to xml) var childrenExp = expected.Children().OrderBy(e => e.Name); @@ -51,4 +56,4 @@ public static TreeComparisonResult IsEqualTo(this ITypedElement expected, ITyped return TreeComparisonResult.OK; } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs index 8bbc0255dc..2c51b2c56c 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirJsonPocoDeserializer.cs @@ -521,7 +521,6 @@ public void ScheduleDelayedValidation(string key, Action validation) _validations[key] = validation; } - //public CodedValidationException[] Run() => _validations.Values.SelectMany(delayed => delayed()).ToArray(); public void RunDelayedValidation() { foreach (var validation in _validations.Values) validation(); @@ -738,7 +737,7 @@ FhirJsonPocoDeserializerState state (object? partial, ERR? error) result = reader.TokenType switch { JsonTokenType.Null => (null, ERR.EXPECTED_PRIMITIVE_NOT_NULL(ref reader, pathStack.GetInstancePath())), - JsonTokenType.String when string.IsNullOrEmpty(reader.GetString()) => (reader.GetString(), ERR.PROPERTY_MAY_NOT_BE_EMPTY(ref reader, pathStack.GetInstancePath())), + JsonTokenType.String when string.IsNullOrWhiteSpace(reader.GetString()) => (reader.GetString(), ERR.PROPERTY_MAY_NOT_BE_EMPTY(ref reader, pathStack.GetInstancePath())), JsonTokenType.String => (reader.GetString(), null), JsonTokenType.Number => (tryGetMatchingNumber(ref reader, valuePropertyType), null), JsonTokenType.True or JsonTokenType.False => (reader.GetBoolean(), null), diff --git a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs index 831df2a779..660c467dfb 100644 --- a/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs +++ b/src/Hl7.Fhir.Base/Serialization/BaseFhirXmlPocoDeserializer.cs @@ -213,7 +213,7 @@ private static void verifyOpeningElement(XmlReader reader, FhirXmlPocoDeserializ //if we are still not at an opening element, throw user-error. state.Errors.Add(ERR.EXPECTED_OPENING_ELEMENT(reader, state.Path.GetInstancePath(), reader.NodeType.GetLiteral())); //try to recover - while (reader.NodeType != XmlNodeType.Element || !reader.EOF) + while (reader.NodeType != XmlNodeType.Element && !reader.EOF) { reader.ReadToContent(state); } @@ -396,6 +396,7 @@ private void deserializePropertyValue(Base target, XmlReader reader, FhirXmlPoco private static XHtml readXhtml(XmlReader reader) { var xhtml = reader.ReadOuterXml(); + reader.MoveToContent(); return new XHtml(xhtml); } @@ -457,16 +458,27 @@ private void readIntoList(IList targetList, ClassMapping propValueMapping, Prope } // let's move to the actual resource reader.ReadToContent(state); - var result = DeserializeResourceInternal(reader, state); - // now we should be at the closing element of the resource container (e.g. ). We should check that and maybe fix that.) - if (reader.Depth != depth && reader.NodeType != XmlNodeType.EndElement) - { - state.Errors.Add(ERR.UNALLOWED_ELEMENT_IN_RESOURCE_CONTAINER(reader, state.Path.GetInstancePath(), reader.LocalName)); + object? result; - // skip until we're back at the closing of the - while (!(reader.Depth == depth && reader.NodeType == XmlNodeType.EndElement)) + if (reader.NodeType == XmlNodeType.EndElement) + { + state.Errors.Add(ERR.EMPTY_RESOURCE_CONTAINER(reader, state.Path.GetInstancePath())); + result = null; + } + else + { + result = DeserializeResourceInternal(reader, state); + // now we should be at the closing element of the resource container (e.g. ). We should check that and maybe fix that.) + if (reader.Depth != depth && reader.NodeType != XmlNodeType.EndElement) { - reader.Read(); + state.Errors.Add(ERR.UNALLOWED_ELEMENT_IN_RESOURCE_CONTAINER(reader, state.Path.GetInstancePath(), + reader.LocalName)); + + // skip until we're back at the closing of the + while (!(reader.Depth == depth && reader.NodeType == XmlNodeType.EndElement)) + { + reader.Read(); + } } } diff --git a/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs b/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs index f5575df445..172b3e9cb5 100644 --- a/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs +++ b/src/Hl7.Fhir.Base/Serialization/DeserializationFailedException.cs @@ -7,6 +7,7 @@ */ +using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using System; @@ -22,7 +23,8 @@ namespace Hl7.Fhir.Serialization; /// /// The deserializers will continue deserialization in the face of errors, and so will collect the full /// set of errors detected using this aggregate exception. -public class DeserializationFailedException : Exception +public class DeserializationFailedException(Base? partialResult, IEnumerable innerExceptions) + : StructuralTypeException(generateMessage(innerExceptions)) { public DeserializationFailedException(Base? partialResult, CodedException innerException) : this(partialResult, [innerException]) @@ -30,13 +32,6 @@ public DeserializationFailedException(Base? partialResult, CodedException innerE // Nothing } - public DeserializationFailedException(Base? partialResult, IEnumerable innerExceptions) : - base(generateMessage(innerExceptions)) - { - PartialResult = partialResult; - Exceptions = innerExceptions.ToList(); - } - private static string generateMessage(IEnumerable exceptions) { string b = "One or more errors occurred."; @@ -50,7 +45,7 @@ private static string generateMessage(IEnumerable exceptions) /// /// The best-effort result of deserialization. Maybe invalid or incomplete because of the errors encountered. /// - public Base? PartialResult { get; private set; } + public Base? PartialResult { get; private set; } = partialResult; - public IReadOnlyCollection Exceptions { get; } + public IReadOnlyCollection Exceptions { get; } = innerExceptions.ToList(); } \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs b/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs index 3642a04f17..72773e8229 100644 --- a/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs +++ b/src/Hl7.Fhir.Base/Serialization/FhirXmlException.cs @@ -43,6 +43,7 @@ public class FhirXmlException : ExtendedCodedException public const string ENCOUNTERED_DTD_REFERENCES_CODE = "XML119"; public const string ELEMENT_HAS_NO_VALUE_OR_CHILDREN_CODE = "XML120"; public const string INVALID_DUPLICATE_PROPERTY_CODE = "XML121"; + public const string EMPTY_RESOURCE_CONTAINER_CODE = "XML122"; // ========================================== // Unrecoverable Errors - when adding a new error, also add it to the appropriate error collections below. @@ -57,7 +58,8 @@ public class FhirXmlException : ExtendedCodedException internal static FhirXmlException NO_ATTRIBUTES_ALLOWED_ON_RESOURCE_CONTAINER(XmlReader reader, string instancePath, string elementName) => Initialize(reader, instancePath, NO_ATTRIBUTES_ALLOWED_ON_RESOURCE_CONTAINER_CODE, $"Element '{elementName}' has a contained resource and therefore should not have attributes.", OO_Sev.Fatal, OO_Typ.Structure); internal static FhirXmlException UNALLOWED_NODE_TYPE(XmlReader reader, string instancePath, string s0) => Initialize(reader, instancePath, UNALLOWED_NODE_TYPE_CODE, $"Xml node of type '{s0}' is unexpected at this point", OO_Sev.Fatal, OO_Typ.Structure); internal static FhirXmlException EXPECTED_OPENING_ELEMENT(XmlReader reader, string instancePath, string openElementName) => Initialize(reader, instancePath, EXPECTED_OPENING_ELEMENT_CODE, $"Expected opening element, but found {openElementName}.", OO_Sev.Fatal, OO_Typ.Structure); - internal static FhirXmlException INVALID_DUPLICATE_PROPERTY(XmlReader reader, string instancePath, string elementName) => Initialize(reader, instancePath, INVALID_DUPLICATE_PROPERTY_CODE, $"Element '{elementName}' is not permitted to repeat", OO_Sev.Error, OO_Typ.Structure); + internal static FhirXmlException INVALID_DUPLICATE_PROPERTY(XmlReader reader, string instancePath, string elementName) => Initialize(reader, instancePath, INVALID_DUPLICATE_PROPERTY_CODE, $"Element '{elementName}' is not permitted to repeat.", OO_Sev.Error, OO_Typ.Structure); + // ========================================== // Recoverable Errors - when adding a new error, also add it to the appropriate error collections below. @@ -91,10 +93,13 @@ internal static FhirXmlException ELEMENT_HAS_NO_VALUE_OR_CHILDREN(string instanc internal static FhirXmlException SCHEMALOCATION_DISALLOWED(XmlReader reader, string instancePath) => Initialize(reader, instancePath, SCHEMALOCATION_DISALLOWED_CODE, "The 'schemaLocation' attribute is disallowed.", OO_Sev.Warning, OO_Typ.Structure); internal static FhirXmlException ENCOUNTERED_DTD_REFERENCES(XmlReader reader, string instancePath) => Initialize(reader, instancePath, ENCOUNTERED_DTD_REFERENCES_CODE, "There SHALL be no DTD references in FHIR resources (because of the XXE security exploit)", OO_Sev.Warning, OO_Typ.Structure); + // Empty resource containers are not allowed in FHIR, but there is no data loss. + internal static FhirXmlException EMPTY_RESOURCE_CONTAINER(XmlReader reader, string instancePath) => Initialize(reader, instancePath, EMPTY_RESOURCE_CONTAINER_CODE, $"Encountered an empty resource container.", OO_Sev.Error, OO_Typ.Structure); /// /// List of issues which do NOT lead to data loss. Recoverable issues mean that all data present in the parsed data could be retrieved and /// captured in the POCO model, even if the syntax or the data was not fully FHIR compliant. /// + internal static readonly HashSet RECOVERABLE_ISSUES = [ ..CodedValidationException.POCO_VALIDATION_ISSUES, @@ -107,7 +112,8 @@ internal static FhirXmlException ELEMENT_HAS_NO_VALUE_OR_CHILDREN(string instanc ATTRIBUTE_HAS_EMPTY_VALUE_CODE, ELEMENT_HAS_NO_VALUE_OR_CHILDREN_CODE, SCHEMALOCATION_DISALLOWED_CODE, - ENCOUNTERED_DTD_REFERENCES_CODE + ENCOUNTERED_DTD_REFERENCES_CODE, + EMPTY_RESOURCE_CONTAINER_CODE ]; /// diff --git a/src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs b/src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs index 4f1e1d65be..58a48dda32 100644 --- a/src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs +++ b/src/Hl7.Fhir.Base/Serialization/JsonParsingExtensions.cs @@ -9,8 +9,6 @@ #nullable enable using Hl7.Fhir.Model; -using Hl7.Fhir.Utility; -using Newtonsoft.Json; using System; using System.Threading.Tasks; @@ -20,7 +18,7 @@ public static class JsonParsingExtensions { /// public static T Parse(this BaseFhirJsonParser parser, string json) where T : Base - => (T)parser. Parse(json, typeof(T)); + => (T)parser.Parse(json, typeof(T)); /// [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + @@ -28,15 +26,15 @@ public static T Parse(this BaseFhirJsonParser parser, string json) where T : public static async Task ParseAsync(this BaseFhirJsonParser parser, string json) where T : Base => (T)await parser.ParseAsync(json, typeof(T)).ConfigureAwait(false); - /// - public static T Parse(this BaseFhirJsonParser parser, JsonReader reader) where T : Base - => (T)parser.Parse(reader, typeof(T)); + // /// + // public static T Parse(this BaseFhirJsonParser parser, JsonReader reader) where T : Base + // => (T)parser.Parse(reader, typeof(T)); - /// - [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + - "you should explicitly call Parse instead.")] - public static async Task ParseAsync(this BaseFhirJsonParser parser, JsonReader reader) where T : Base - => (T)await parser.ParseAsync(reader, typeof(T)).ConfigureAwait(false); + // /// + // [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + // "you should explicitly call Parse instead.")] + // public static async Task ParseAsync(this BaseFhirJsonParser parser, JsonReader reader) where T : Base + // => (T)await parser.ParseAsync(reader, typeof(T)).ConfigureAwait(false); /// /// Deserializes the given Json string into a FHIR resource or datatype. @@ -47,11 +45,8 @@ public static async Task ParseAsync(this BaseFhirJsonParser parser, JsonRe /// will be ignored when parsing resources. /// Note that there is no official serialization for FHIR datatypes, just for FHIR resources, so /// deserializing non-resource types might not always work. - public static Base Parse(this BaseFhirJsonParser parser, string json, Type? dataType = null) - { - using var jsonReader = SerializationUtil.JsonReaderFromJsonText(json); - return parse(parser, jsonReader, dataType); - } + public static Base Parse(this BaseFhirJsonParser parser, string json, Type? dataType = null) => + parse(parser, json, dataType); /// [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + @@ -59,26 +54,26 @@ public static Base Parse(this BaseFhirJsonParser parser, string json, Type? data public static Task ParseAsync(this BaseFhirJsonParser parser, string json, Type? dataType = null) => Task.FromResult(parser.Parse(json, dataType)); - /// - /// Deserializes the Json passed in the JsonReader into a FHIR resource or datatype. - /// - /// The parser for which this extension method can be called. - /// An JsonReader positioned on the first element, or the beginning of the stream. - /// Optional. Can be used when deserializing datatypes and - /// will be ignored when parsing resources. - /// Note that there is no official serialization for FHIR datatypes, just for FHIR resources, so - /// deserializing non-resource types might not always work. - public static Base Parse(this BaseFhirJsonParser parser, JsonReader reader, Type? dataType = null) => - parse(parser, reader, dataType); + // /// + // /// Deserializes the Json passed in the JsonReader into a FHIR resource or datatype. + // /// + // /// The parser for which this extension method can be called. + // /// An JsonReader positioned on the first element, or the beginning of the stream. + // /// Optional. Can be used when deserializing datatypes and + // /// will be ignored when parsing resources. + // /// Note that there is no official serialization for FHIR datatypes, just for FHIR resources, so + // /// deserializing non-resource types might not always work. + // public static Base Parse(this BaseFhirJsonParser parser, JsonReader reader, Type? dataType = null) => + // parse(parser, reader, dataType); - /// - [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + - "you should explicitly call Parse instead.")] - public static Task ParseAsync(this BaseFhirJsonParser parser, JsonReader reader, Type? dataType = null) => - Task.FromResult(parser.Parse(reader, dataType)); + // /// + // [Obsolete("The current parsers do not support async parsing, so this method is synchronous and " + + // "you should explicitly call Parse instead.")] + // public static Task ParseAsync(this BaseFhirJsonParser parser, JsonReader reader, Type? dataType = null) => + // Task.FromResult(parser.Parse(reader, dataType)); - private static Base parse(BaseFhirJsonParser parser, JsonReader json, Type? dataType = null) => - parse(parser, json.ToString()!, dataType); + // private static Base parse(BaseFhirJsonParser parser, JsonReader json, Type? dataType = null) => + // parse(parser, json.ToString()!, dataType); private static Base parse(BaseFhirJsonParser parser, string json, Type? dataType = null) { diff --git a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs index 0cb767317f..6fce595c2c 100644 --- a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs +++ b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs @@ -65,7 +65,7 @@ public Predicate? ExceptionFilter // Simulate the old behaviour by selectively enforcing errors. if(AllowUnrecognizedEnums) ignored.Add(CodedValidationException.INVALID_CODED_VALUE_CODE); - if(AllowUnrecognizedEnums) ignored.AddRange( + if(AcceptUnknownMembers) ignored.AddRange( [FhirXmlException.UNKNOWN_ELEMENT_CODE, FhirXmlException.UNKNOWN_ATTRIBUTE_CODE]); var augmentedFilter = _exceptionFilter; @@ -79,7 +79,7 @@ public Predicate? ExceptionFilter /// /// Raise an error when an xsi:schemaLocation is encountered. /// - public bool DisallowXsiAttributesOnRoot { get; set; } + public bool DisallowXsiAttributesOnRoot { get; init; } /// /// Do not throw when encountering values not parseable as a member of an enumeration in a Poco. diff --git a/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs b/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs index 8d08122c13..50d402e513 100644 --- a/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs +++ b/src/Hl7.Fhir.Base/Utility/SerializationUtil.cs @@ -137,7 +137,7 @@ public static JsonReader JsonReaderFromJsonText(string json) public static Utf8JsonReader Utf8JsonReaderFromJsonText(string json) => new(Encoding.UTF8.GetBytes(json), - new JsonReaderOptions { CommentHandling = JsonCommentHandling.Skip }); + new JsonReaderOptions { CommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true }); public static XmlReader XmlReaderFromStream(Stream input, bool ignoreComments = true) => WrapXmlReader(XmlReader.Create(input), ignoreComments); diff --git a/src/Hl7.Fhir.Conformance/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Conformance/CompatibilitySuppressions.xml index e1badae5f7..9d30ef81ad 100644 --- a/src/Hl7.Fhir.Conformance/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Conformance/CompatibilitySuppressions.xml @@ -29,6 +29,34 @@ lib/net8.0/Hl7.Fhir.Conformance.dll true + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.get_ParserSettings + lib/net8.0/Hl7.Fhir.Conformance.dll + lib/net8.0/Hl7.Fhir.Conformance.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.get_TimeOut + lib/net8.0/Hl7.Fhir.Conformance.dll + lib/net8.0/Hl7.Fhir.Conformance.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.set_ParserSettings(Hl7.Fhir.Serialization.ParserSettings) + lib/net8.0/Hl7.Fhir.Conformance.dll + lib/net8.0/Hl7.Fhir.Conformance.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.set_TimeOut(System.Int32) + lib/net8.0/Hl7.Fhir.Conformance.dll + lib/net8.0/Hl7.Fhir.Conformance.dll + true + CP0002 M:Hl7.Fhir.Specification.Summary.ArtifactSummaryGenerator.get_Default @@ -71,6 +99,34 @@ lib/netstandard2.1/Hl7.Fhir.Conformance.dll true + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.get_ParserSettings + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.get_TimeOut + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.set_ParserSettings(Hl7.Fhir.Serialization.ParserSettings) + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.CommonWebResolver.set_TimeOut(System.Int32) + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + lib/netstandard2.1/Hl7.Fhir.Conformance.dll + true + CP0002 M:Hl7.Fhir.Specification.Summary.ArtifactSummaryGenerator.get_Default diff --git a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs index 6f74a1bf6b..feec28bfe7 100644 --- a/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs +++ b/src/Hl7.Fhir.ElementModel.Shared.Tests/ElementNodeTests.cs @@ -273,7 +273,7 @@ public void KeepsAnnotations() public void CanBuildFromITypedElement() { var tpXml = File.ReadAllText(@"TestData/fp-test-patient.xml"); - var patientElem = (new FhirXmlParser()).Parse(tpXml).ToTypedElement(); + var patientElem = FhirXmlNode.Parse(tpXml).ToTypedElement(ModelInfo.ModelInspector); var nodes = ElementNode.FromElement(patientElem); Assert.IsTrue(patientElem.IsEqualTo(nodes).Success); } diff --git a/src/Hl7.Fhir.R4.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.R4.Tests/Serialization/SerializationTests.cs index d2cb96c1c8..dca2443038 100644 --- a/src/Hl7.Fhir.R4.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.R4.Tests/Serialization/SerializationTests.cs @@ -21,9 +21,14 @@ public partial class SerializationTests [TestMethod] public void TestExampleScenarioJsonSerialization() { - var es = new ExampleScenario(); + var es = new ExampleScenario() + { + Name = "test", + Status = PublicationStatus.Active + }; es.Instance.Add(new ExampleScenario.InstanceComponent() { + ResourceId = "brian", ResourceType = ResourceType.ExampleScenario, Name = "brian" }); @@ -34,4 +39,4 @@ public void TestExampleScenarioJsonSerialization() Assert.AreEqual("ExampleScenario", c2.Instance[0].ResourceTypeElement.ObjectValue as string); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml index 770bbb3ca4..1341aa4ff4 100644 --- a/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R4/CompatibilitySuppressions.xml @@ -50,6 +50,62 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -113,6 +169,62 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -127,6 +239,13 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -309,6 +428,62 @@ lib/netstandard2.1/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -372,6 +547,62 @@ lib/netstandard2.1/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -386,6 +617,13 @@ lib/netstandard2.1/Hl7.Fhir.R4.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -547,6 +785,13 @@ lib/netstandard2.1/Hl7.Fhir.R4.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -554,6 +799,13 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/net8.0/Hl7.Fhir.R4.dll + lib/net8.0/Hl7.Fhir.R4.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer @@ -561,6 +813,13 @@ lib/net8.0/Hl7.Fhir.R4.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -568,6 +827,13 @@ lib/netstandard2.1/Hl7.Fhir.R4.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/netstandard2.1/Hl7.Fhir.R4.dll + lib/netstandard2.1/Hl7.Fhir.R4.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer diff --git a/src/Hl7.Fhir.R4B.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.R4B.Tests/Serialization/SerializationTests.cs index d2cb96c1c8..e4ab284134 100644 --- a/src/Hl7.Fhir.R4B.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.R4B.Tests/Serialization/SerializationTests.cs @@ -21,9 +21,10 @@ public partial class SerializationTests [TestMethod] public void TestExampleScenarioJsonSerialization() { - var es = new ExampleScenario(); + var es = new ExampleScenario() { Name = "test", Status = PublicationStatus.Active }; es.Instance.Add(new ExampleScenario.InstanceComponent() { + ResourceId = "brian", ResourceType = ResourceType.ExampleScenario, Name = "brian" }); @@ -34,4 +35,4 @@ public void TestExampleScenarioJsonSerialization() Assert.AreEqual("ExampleScenario", c2.Instance[0].ResourceTypeElement.ObjectValue as string); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml index 94c3323a15..d82bda6b7c 100644 --- a/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R4B/CompatibilitySuppressions.xml @@ -50,6 +50,62 @@ lib/net8.0/Hl7.Fhir.R4B.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -113,6 +169,62 @@ lib/net8.0/Hl7.Fhir.R4B.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -127,6 +239,13 @@ lib/net8.0/Hl7.Fhir.R4B.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -309,6 +428,62 @@ lib/netstandard2.1/Hl7.Fhir.R4B.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -372,6 +547,62 @@ lib/netstandard2.1/Hl7.Fhir.R4B.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -386,6 +617,13 @@ lib/netstandard2.1/Hl7.Fhir.R4B.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -547,6 +785,13 @@ lib/netstandard2.1/Hl7.Fhir.R4B.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -554,6 +799,13 @@ lib/net8.0/Hl7.Fhir.R4B.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/net8.0/Hl7.Fhir.R4B.dll + lib/net8.0/Hl7.Fhir.R4B.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer @@ -561,6 +813,13 @@ lib/net8.0/Hl7.Fhir.R4B.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -568,6 +827,13 @@ lib/netstandard2.1/Hl7.Fhir.R4B.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/netstandard2.1/Hl7.Fhir.R4B.dll + lib/netstandard2.1/Hl7.Fhir.R4B.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer diff --git a/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs index 5c2c2ad8b8..2099330156 100644 --- a/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.R5.Tests/Serialization/ResourceParsingTests.cs @@ -21,7 +21,7 @@ public partial class ResourceParsingTests [TestMethod] public void ParseBinaryForR4andHigher() { - var json = "{\"resourceType\":\"Binary\",\"data\":\"ZGF0YQ==\"}"; + var json = "{\"resourceType\":\"Binary\",\"contentType\":\"text/plain\",\"data\":\"ZGF0YQ==\"}"; var binary = new FhirJsonParser().Parse(json); var result = new FhirJsonSerializer().SerializeToString(binary); diff --git a/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml b/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml index ea156e4321..5f2778a4a0 100644 --- a/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.R5/CompatibilitySuppressions.xml @@ -50,6 +50,62 @@ lib/net8.0/Hl7.Fhir.R5.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -113,6 +169,62 @@ lib/net8.0/Hl7.Fhir.R5.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -127,6 +239,13 @@ lib/net8.0/Hl7.Fhir.R5.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -309,6 +428,62 @@ lib/netstandard2.1/Hl7.Fhir.R5.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -372,6 +547,62 @@ lib/netstandard2.1/Hl7.Fhir.R5.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -386,6 +617,13 @@ lib/netstandard2.1/Hl7.Fhir.R5.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -547,6 +785,13 @@ lib/netstandard2.1/Hl7.Fhir.R5.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -554,6 +799,13 @@ lib/net8.0/Hl7.Fhir.R5.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/net8.0/Hl7.Fhir.R5.dll + lib/net8.0/Hl7.Fhir.R5.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer @@ -561,6 +813,13 @@ lib/net8.0/Hl7.Fhir.R5.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -568,6 +827,13 @@ lib/netstandard2.1/Hl7.Fhir.R5.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/netstandard2.1/Hl7.Fhir.R5.dll + lib/netstandard2.1/Hl7.Fhir.R5.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer diff --git a/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs b/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs index 58209f7fcb..8a0d53dc78 100644 --- a/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/ElementModel/PocoTypedElementTests.cs @@ -145,7 +145,7 @@ public void IncorrectPathInTwoSuccessiveRepeatingMembers() public void PocoTypedElementPerformance() { var xml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var cs = (new FhirXmlParser()).Parse(xml); + var cs = FhirXmlParser.OSTRICH.Parse(xml); var nav = cs.ToTypedElement(); TypedElementPerformance(nav); diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs index 058e527a83..53b6f71c87 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/ResourceParsingTests.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Tests.Serialization @@ -26,34 +27,24 @@ public class ResourceParsingTests [TestMethod] public void ConfigureFailOnUnknownMember() { - var xml = ""; + const string xml = ""; var parser = new FhirXmlParser(); + parser.Settings = parser.Settings with { AllowUnrecognizedEnums = true }; - // parser.Settings.ExceptionHandler = (object source, Utility.ExceptionNotification args) => - // { - // Debug.WriteLine(args.Message); - // if (args.Exception is StructuralTypeException && args.Severity == Utility.ExceptionSeverity.Error) - // { - // Assert.IsTrue(args.Exception.Message.Contains("Type checking the data: "), "Error message detected"); - // throw new StructuralTypeException(args.Exception.Message.Replace("Type checking the data: ", ""), args.Exception.InnerException); - // } - // }; - - // try - // { - // var r2 = parser.Parse(xml); - // Assert.Fail("Should have failed on unknown member"); - // } - // catch (StructuralTypeException ste) - // { - // Debug.WriteLine(ste.Message); - // Assert.IsFalse(ste.Message.Contains("Type checking the data: "), "Custom error message should have removed the prefix"); - // } - // - // parser.Settings.AcceptUnknownMembers = true; - var resource = parser.Parse(xml); + try + { + parser.Parse(xml); + Assert.Fail("Should have failed on unknown member"); + } + catch (StructuralTypeException ste) + { + Debug.WriteLine(ste.Message); + Assert.IsTrue(ste.Message.Contains("daytona")); + Assert.IsFalse(ste.Message.Contains("ox")); + } - throw new NotImplementedException("Repair this unit test as soon as we have replaced the FhirXmlParser."); + parser.Settings = parser.Settings with { AcceptUnknownMembers = true }; + var resource = parser.Parse(xml); } @@ -310,26 +301,24 @@ public void EmptyRoundTrip() new Identifier("https://mydomain.com/identifiers/Something", "123"), new Identifier("https://mydomain.com/identifiers/Spaces", " "), new Identifier("https://mydomain.com/identifiers/Empty", string.Empty), - new Identifier("https://mydomain.com/identifiers/Null", null) + new Identifier("https://mydomain.com/identifiers/Null", null!) ] }; var json = FhirJsonSerializer.SerializeToString(patient); - var parsedPatient = FhirJsonParser.Parse(json); + Assert.IsFalse(FhirJsonParser.STRICT.TryDeserializeResource(json, out var resource, out var errors)); + + errors.Count().Should().Be(2); + errors.Select(e => e.ErrorCode).Should().AllBe(FhirJsonException.PROPERTY_MAY_NOT_BE_EMPTY_CODE); + + var parsedPatient = resource as Patient; Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); for (var i = 0; i < patient.Identifier.Count; i++) { Assert.AreEqual(patient.Identifier[i].System, parsedPatient.Identifier[i].System); - if (string.IsNullOrWhiteSpace(patient.Identifier[i].Value)) - { - Assert.IsNull(parsedPatient.Identifier[i].Value); - } - else - { Assert.AreEqual(patient.Identifier[i].Value, parsedPatient.Identifier[i].Value); } - } var xml = FhirXmlSerializer.SerializeToString(patient); parsedPatient = FhirXmlParser.Parse(xml); @@ -340,7 +329,7 @@ public void EmptyRoundTrip() Assert.AreEqual(patient.Identifier[i].System, parsedPatient.Identifier[i].System); if (string.IsNullOrWhiteSpace(patient.Identifier[i].Value)) { - Assert.IsNull(parsedPatient.Identifier[i].Value); + Assert.IsTrue(string.IsNullOrWhiteSpace(parsedPatient.Identifier[i].Value)); } else { @@ -371,7 +360,10 @@ public void NarrativeMustBeValidXml() { var json = "{\"resourceType\": \"Patient\", \"text\": {\"status\": \"generated\", \"div\": \"text without div\" } }"; - _ = new FhirJsonParser().Parse(json); + + new FhirJsonParser( + new ParserSettings { NarrativeValidation = NarrativeValidationKind.FhirXhtml }) + .Parse(json); Assert.Fail("Should have thrown on invalid Div format"); } @@ -387,13 +379,13 @@ public void ParseEmptyContained() var xml = ""; var parser = new FhirXmlParser(); - ExceptionAssert.Throws(() => parser.Parse(xml)); + ExceptionAssert.Throws(() => parser.Parse(xml)); } [TestMethod] public void ParseBinaryForR4andHigher() { - var json = "{\"resourceType\":\"Binary\",\"content\":\"ZGF0YQ==\"}"; + var json = "{\"resourceType\":\"Binary\",\"contentType\":\"text/plain\",\"content\":\"ZGF0YQ==\"}"; var binary = new FhirJsonParser().Parse(json); var result = new FhirJsonSerializer().SerializeToString(binary); diff --git a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs index 6144cc6c77..121fbe07a8 100644 --- a/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.STU3.Tests/Serialization/SerializationTests.cs @@ -17,6 +17,7 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Xml; using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Tests.Serialization @@ -136,6 +137,7 @@ public void BundleLinksUnaltered() { var b = new Bundle { + Type = Bundle.BundleType.Batch, NextLink = new Uri("Organization/123456/_history/123456", UriKind.Relative) }; @@ -152,7 +154,12 @@ public void TestDecimalPrecisionSerializationInJson() var dec6 = 6m; var dec60 = 6.0m; var ext = new FhirDecimal(dec6); - var obs = new Observation(); + var obs = new Observation + { + Code = new CodeableConcept("http://nu.nl", "bla"), + Status = ObservationStatus.Cancelled + }; + obs.AddExtension("http://example.org/DecimalPrecision", ext); var json = FhirJsonSerializer.SerializeToString(obs); @@ -161,7 +168,12 @@ public void TestDecimalPrecisionSerializationInJson() Assert.AreEqual("6", ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); ext = new FhirDecimal(dec60); - obs = new Observation(); + obs = new Observation + { + Code = new CodeableConcept("http://nu.nl", "bla"), + Status = ObservationStatus.Cancelled + }; + obs.AddExtension("http://example.org/DecimalPrecision", ext); json = FhirJsonSerializer.SerializeToString(obs); @@ -175,7 +187,11 @@ public void TestLongDecimalSerialization() { var dec = 3.1415926535897932384626433833m; var ext = new FhirDecimal(dec); - var obs = new Observation(); + var obs = new Observation + { + Code = new CodeableConcept("http://nu.nl", "bla"), + Status = ObservationStatus.Cancelled + }; obs.AddExtension("http://example.org/DecimalPrecision", ext); var json = FhirJsonSerializer.SerializeToString(obs); @@ -254,7 +270,7 @@ public void TryXXEExploit() Assert.Fail(); } - catch (FormatException e) + catch (XmlException e) { Assert.IsTrue(e.Message.Contains("DTD is prohibited")); } diff --git a/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs b/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs index ff1271096b..bc9d7186f2 100644 --- a/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs +++ b/src/Hl7.Fhir.STU3.Tests/TestDataVersionCheck.cs @@ -21,21 +21,21 @@ namespace Hl7.Fhir.Tests public class TestDataVersionCheck { [TestMethod] // not everything parses correctly - public async Tasks.Task VerifyAllTestData() + public void VerifyAllTestData() { string location = typeof(TestDataHelper).GetTypeInfo().Assembly.Location; var path = Path.GetDirectoryName(location) + "/TestData"; Console.WriteLine(path); StringBuilder issues = new StringBuilder(); - await ValidateFolder(path, path, issues); + validateFolder(path, path, issues); Console.Write(issues.ToString()); Assert.AreEqual("", issues.ToString()); } - private async Tasks.Task ValidateFolder(string basePath, string path, StringBuilder issues) + private static void validateFolder(string basePath, string path, StringBuilder issues) { - var xmlParser = new Fhir.Serialization.FhirXmlParser(); - var jsonParser = new Fhir.Serialization.FhirJsonParser(); + var xmlParser = FhirXmlParser.OSTRICH; + var jsonParser = FhirJsonParser.OSTRICH; Console.WriteLine($"Validating test files in {path.Replace(basePath, "")}"); foreach (var item in Directory.EnumerateFiles(path)) { @@ -72,7 +72,7 @@ private async Tasks.Task ValidateFolder(string basePath, string path, StringBuil } foreach (var item in Directory.EnumerateDirectories(path)) { - await ValidateFolder(basePath, item, issues); + validateFolder(basePath, item, issues); } } } diff --git a/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml b/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml index 541e3b0577..6fed2c0b26 100644 --- a/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.STU3/CompatibilitySuppressions.xml @@ -78,6 +78,62 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -141,6 +197,62 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -155,6 +267,13 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -309,6 +428,34 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.get_ParserSettings + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.get_TimeOut + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.set_ParserSettings(Hl7.Fhir.Serialization.ParserSettings) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.set_TimeOut(System.Int32) + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Specification.Terminology.ValueSetExpander.Expand(Hl7.Fhir.Model.ValueSet) @@ -365,6 +512,62 @@ lib/netstandard2.1/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(Newtonsoft.Json.JsonReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(Newtonsoft.Json.JsonReader) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirJsonParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirJsonPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirJsonPocoDeserializerSettings) @@ -428,6 +631,62 @@ lib/netstandard2.1/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.String) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.Parse``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.String,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync(System.Xml.XmlReader,System.Type) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.String) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlParser.ParseAsync``1(System.Xml.XmlReader) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Introspection.ModelInspector,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -442,6 +701,13 @@ lib/netstandard2.1/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Serialization.FhirXmlPocoDeserializer.#ctor(System.Reflection.Assembly,Hl7.Fhir.Serialization.FhirXmlPocoDeserializerSettings) @@ -596,6 +862,34 @@ lib/netstandard2.1/Hl7.Fhir.STU3.dll true + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.get_ParserSettings + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.get_TimeOut + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.set_ParserSettings(Hl7.Fhir.Serialization.ParserSettings) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + + + CP0002 + M:Hl7.Fhir.Specification.Source.WebResolver.set_TimeOut(System.Int32) + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + CP0002 M:Hl7.Fhir.Specification.Terminology.ValueSetExpander.Expand(Hl7.Fhir.Model.ValueSet) @@ -603,6 +897,13 @@ lib/netstandard2.1/Hl7.Fhir.STU3.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -610,6 +911,13 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/net8.0/Hl7.Fhir.STU3.dll + lib/net8.0/Hl7.Fhir.STU3.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer @@ -617,6 +925,13 @@ lib/net8.0/Hl7.Fhir.STU3.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirJsonParser + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirJsonSerializer @@ -624,6 +939,13 @@ lib/netstandard2.1/Hl7.Fhir.STU3.dll true + + CP0007 + T:Hl7.Fhir.Serialization.FhirXmlParser + lib/netstandard2.1/Hl7.Fhir.STU3.dll + lib/netstandard2.1/Hl7.Fhir.STU3.dll + true + CP0007 T:Hl7.Fhir.Serialization.FhirXmlSerializer diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs index 7396856873..8f7308ec61 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientJson.cs @@ -51,8 +51,7 @@ public async Tasks.Task TestPruneEmptyNodes() public async Tasks.Task CanSerializeFromPoco() { var tp = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); - var pser = new FhirJsonParser(); - var pat = pser.Parse(tp); + var pat = FhirJsonParser.OSTRICH.Parse(tp); var output = pat.ToJson(); @@ -73,7 +72,7 @@ public async Tasks.Task DoesPretty() var pretty = nav.ToJson(pretty: true); Assert.IsTrue(pretty[..20].Contains('\n')); - var p = new FhirJsonParser().Parse(json); + var p = FhirJsonParser.OSTRICH.Parse(json); output = new FhirJsonSerializer().SerializeToString(p, pretty: false); Assert.IsFalse(output[..20].Contains('\n')); pretty = new FhirJsonSerializer().SerializeToString(p, pretty: true); diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs index e5f2faf5ea..9ffadb02db 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializeDemoPatientXml.cs @@ -58,8 +58,7 @@ public void TestElementReordering() public void CanSerializeFromPoco() { var tpXml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var pser = new FhirXmlParser(); - var pat = pser.Parse(tpXml); + var pat = FhirXmlParser.OSTRICH.Parse(tpXml); var nav = pat.ToTypedElement(); var output = nav.ToXml(); @@ -74,7 +73,7 @@ public async Tasks.Task CompareSubtrees() // If on a Unix platform replace \\r\\n in json strings to \\n. if(Environment.NewLine == "\n") tpJson = tpJson.Replace(@"\r\n", @"\n"); - var pat = (new FhirXmlParser()).Parse(tpXml); + var pat = FhirXmlParser.RECOVERABLE.Parse(tpXml); var navXml = getXmlElement(tpXml); var navJson = await getJsonElement(tpJson); @@ -106,7 +105,7 @@ public async Tasks.Task DoesPretty() var pretty = nav.ToXml(pretty: true); Assert.IsTrue(pretty[..50].Contains('\n')); - var p = new FhirXmlParser().Parse(xml); + var p = FhirXmlParser.OSTRICH.Parse(xml); output = new FhirXmlSerializer().SerializeToString(p, pretty: false); Assert.IsFalse(output[..50].Contains('\n')); pretty = new FhirXmlSerializer().SerializeToString(p, pretty: true); diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs index e890cb20b6..7fb614ec9f 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/SerializePartialTree.cs @@ -1,13 +1,10 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; using Hl7.Fhir.Specification; using Hl7.Fhir.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Serialization.Tests @@ -37,38 +34,38 @@ public async Tasks.Task CanSerializeSubtree() var tpXml = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.xml")); var tpJson = await File.ReadAllTextAsync(Path.Combine("TestData", "fp-test-patient.json")); - var pat = (new FhirXmlParser()).Parse(tpXml); + var pat = FhirXmlParser.OSTRICH.Parse(tpXml); // Should work on the parent resource var navXml = getXmlNode(tpXml); var navJson = await getJsonNode(tpJson); var navPoco = pat.ToTypedElement(); - await testSubtree(navXml, navJson, navPoco); + testSubtree(navXml, navJson, navPoco); // An on a child that's a normal datatype var subnavXml = navXml.Children("photo").First(); var subnavJson = navJson.Children("photo").First(); var subnavPoco = navPoco.Children("photo").First(); - await testSubtree(subnavXml, subnavJson, subnavPoco); + testSubtree(subnavXml, subnavJson, subnavPoco); // And on a contained resource subnavXml = navXml.Children("contained").First(); subnavJson = navJson.Children("contained").First(); subnavPoco = navPoco.Children("contained").First(); - await testSubtree(subnavXml, subnavJson, subnavPoco); + testSubtree(subnavXml, subnavJson, subnavPoco); // And on a child of the contained resource subnavXml = navXml.Children("contained").First().Children("name").First(); subnavJson = navJson.Children("contained").First().Children("name").First(); subnavPoco = navPoco.Children("contained").First().Children("name").First(); - await testSubtree(subnavXml, subnavJson, subnavPoco); + testSubtree(subnavXml, subnavJson, subnavPoco); } - private async Tasks.Task testSubtree(ITypedElement navXml, ITypedElement navJson, ITypedElement navPoco) + private void testSubtree(ITypedElement navXml, ITypedElement navJson, ITypedElement navPoco) { assertAreNavsEqual(navXml, navJson, navPoco); - var navRtXml = await JsonParsingHelpers.ParseToTypedElement(navXml.ToJson(), navXml.InstanceType, + var navRtXml = JsonParsingHelpers.ParseToTypedElement(navXml.ToJson(), navXml.InstanceType, new PocoStructureDefinitionSummaryProvider(), navXml.Name); var navRtJson = navJson.ToPoco().ToTypedElement(navJson.Name); var navRtPoco = XmlParsingHelpers.ParseToTypedElement(navPoco.ToXml(), navPoco.InstanceType, @@ -104,17 +101,19 @@ internal static async Tasks.Task ParseToTypedElementAsync(string if (json == null) throw Error.ArgumentNull(nameof(json)); if (provider == null) throw Error.ArgumentNull(nameof(provider)); + json = json.Replace("\r",""); return (await FhirJsonNode.ParseAsync(json, rootName, settings)).ToTypedElement(provider, null, tnSettings); } - internal static async Tasks.Task ParseToTypedElement(string json, string type, IStructureDefinitionSummaryProvider provider, string rootName = null, + internal static ITypedElement ParseToTypedElement(string json, string type, IStructureDefinitionSummaryProvider provider, string rootName = null, FhirJsonParsingSettings settings = null, TypedElementSettings tnSettings = null) { if (json == null) throw Error.ArgumentNull(nameof(json)); if (type == null) throw Error.ArgumentNull(nameof(type)); if (provider == null) throw Error.ArgumentNull(nameof(provider)); - return (await FhirJsonNode.ParseAsync(json, rootName, settings)).ToTypedElement(provider, type, tnSettings); + json = json.Replace("\r",""); + return (FhirJsonNode.Parse(json, rootName, settings)).ToTypedElement(provider, type, tnSettings); } } @@ -125,6 +124,8 @@ public static ITypedElement ParseToTypedElement(string xml, IStructureDefinition if (xml == null) throw Error.ArgumentNull(nameof(xml)); if (provider == null) throw Error.ArgumentNull(nameof(provider)); + xml = xml.Replace("\r",""); + return FhirXmlNode.Parse(xml, settings).ToTypedElement(provider, null, tnSettings); } @@ -135,6 +136,7 @@ public static ITypedElement ParseToTypedElement(string xml, string type, IStruct if (type == null) throw Error.ArgumentNull(nameof(type)); if (provider == null) throw Error.ArgumentNull(nameof(provider)); + xml = xml.Replace("\r",""); return FhirXmlNode.Parse(xml, settings).ToTypedElement(provider, type, tnSettings); } diff --git a/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs b/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs index acc41151fb..1651ed4f75 100644 --- a/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/ElementModel/PocoTypedElementTests.cs @@ -144,7 +144,7 @@ public void IncorrectPathInTwoSuccessiveRepeatingMembers() public void PocoTypedElementPerformance() { var xml = File.ReadAllText(Path.Combine("TestData", "fp-test-patient.xml")); - var cs = (new FhirXmlParser()).Parse(xml); + var cs = FhirXmlParser.RECOVERABLE.Parse(xml); var nav = cs.ToTypedElement(); TypedElementPerformance(nav); diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs index be5ca9e43a..36ddb6425b 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/ResourceParsingTests.cs @@ -10,12 +10,14 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; +using Hl7.Fhir.Utility; using Hl7.Fhir.Validation; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Tests.Serialization @@ -30,31 +32,20 @@ public void ConfigureFailOnUnknownMember() var parser = new FhirXmlParser(); parser.Settings = parser.Settings with { AllowUnrecognizedEnums = true }; - // parser.Settings.ExceptionHandler = (object source, Utility.ExceptionNotification args) => - // { - // Debug.WriteLine(args.Message); - // if (args.Exception is StructuralTypeException && args.Severity == Utility.ExceptionSeverity.Error) - // { - // Assert.IsTrue(args.Exception.Message.Contains("Type checking the data: "), "Error message detected"); - // throw new StructuralTypeException(args.Exception.Message.Replace("Type checking the data: ", ""), args.Exception.InnerException); - // } - // }; - // - // try - // { - // parser.Parse(xml); - // Assert.Fail("Should have failed on unknown member"); - // } - // catch (StructuralTypeException ste) - // { - // Debug.WriteLine(ste.Message); - // Assert.IsFalse(ste.Message.Contains("Type checking the data: "), "Custom error message should have removed the prefix"); - // } - // + try + { + parser.Parse(xml); + Assert.Fail("Should have failed on unknown member"); + } + catch (StructuralTypeException ste) + { + Debug.WriteLine(ste.Message); + Assert.IsTrue(ste.Message.Contains("daytona")); + Assert.IsFalse(ste.Message.Contains("ox")); + } + parser.Settings = parser.Settings with { AcceptUnknownMembers = true }; var resource = parser.Parse(xml); - - throw new NotImplementedException("Repair this unit test as soon as we have replaced the FhirXmlParser."); } @@ -133,7 +124,7 @@ public void AcceptXsiStuffOnRoot() parser.Parse(xml); // Now, enforce xsi: attributes are no longer accepted - parser.Settings.DisallowXsiAttributesOnRoot = true; + parser.Settings = parser.Settings with { DisallowXsiAttributesOnRoot = true }; try { @@ -311,25 +302,23 @@ public void EmptyRoundTrip() new Identifier("https://mydomain.com/identifiers/Something", "123"), new Identifier("https://mydomain.com/identifiers/Spaces", " "), new Identifier("https://mydomain.com/identifiers/Empty", string.Empty), - new Identifier("https://mydomain.com/identifiers/Null", null) + new Identifier("https://mydomain.com/identifiers/Null", null!) ] }; var json = FhirJsonSerializer.SerializeToString(patient); - var parsedPatient = FhirJsonParser.Parse(json); + Assert.IsFalse(FhirJsonParser.STRICT.TryDeserializeResource(json, out var resource, out var errors)); + + errors.Count().Should().Be(2); + errors.Select(e => e.ErrorCode).Should().AllBe(FhirJsonException.PROPERTY_MAY_NOT_BE_EMPTY_CODE); + + var parsedPatient = resource as Patient; Assert.AreEqual(patient.Identifier.Count, parsedPatient.Identifier.Count); for (var i = 0; i < patient.Identifier.Count; i++) { Assert.AreEqual(patient.Identifier[i].System, parsedPatient.Identifier[i].System); - if (string.IsNullOrWhiteSpace(patient.Identifier[i].Value)) - { - Assert.IsNull(parsedPatient.Identifier[i].Value); - } - else - { - Assert.AreEqual(patient.Identifier[i].Value, parsedPatient.Identifier[i].Value); - } + Assert.AreEqual(patient.Identifier[i].Value, parsedPatient.Identifier[i].Value); } var xml = FhirXmlSerializer.SerializeToString(patient); @@ -341,7 +330,7 @@ public void EmptyRoundTrip() Assert.AreEqual(patient.Identifier[i].System, parsedPatient.Identifier[i].System); if (string.IsNullOrWhiteSpace(patient.Identifier[i].Value)) { - Assert.IsNull(parsedPatient.Identifier[i].Value); + Assert.IsTrue(string.IsNullOrWhiteSpace(parsedPatient.Identifier[i].Value)); } else { @@ -372,7 +361,10 @@ public void NarrativeMustBeValidXml() { var json = "{\"resourceType\": \"Patient\", \"text\": {\"status\": \"generated\", \"div\": \"text without div\" } }"; - var patient = new FhirJsonParser().Parse(json); + + new FhirJsonParser( + new ParserSettings { NarrativeValidation = NarrativeValidationKind.FhirXhtml }) + .Parse(json); Assert.Fail("Should have thrown on invalid Div format"); } @@ -388,7 +380,7 @@ public void ParseEmptyContained() var xml = ""; var parser = new FhirXmlParser(); - ExceptionAssert.Throws(() => parser.Parse(xml)); + ExceptionAssert.Throws(() => parser.Parse(xml)); } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs index 249867eb16..7a99e40720 100644 --- a/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs +++ b/src/Hl7.Fhir.Shared.Tests/Serialization/SerializationTests.cs @@ -17,6 +17,7 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Xml; using Tasks = System.Threading.Tasks; namespace Hl7.Fhir.Tests.Serialization @@ -136,6 +137,7 @@ public void BundleLinksUnaltered() { var b = new Bundle { + Type = Bundle.BundleType.Batch, NextLink = new Uri("Organization/123456/_history/123456", UriKind.Relative) }; @@ -152,7 +154,11 @@ public void TestDecimalPrecisionSerializationInJson() var dec6 = 6m; var dec60 = 6.0m; var ext = new FhirDecimal(dec6); - var obs = new Observation(); + var obs = new Observation + { + Code = new CodeableConcept("http://nu.nl", "bla"), + Status = ObservationStatus.Cancelled + }; obs.AddExtension("http://example.org/DecimalPrecision", ext); var json = FhirJsonSerializer.SerializeToString(obs); @@ -161,7 +167,12 @@ public void TestDecimalPrecisionSerializationInJson() Assert.AreEqual("6", ((FhirDecimal)obs2.GetExtension("http://example.org/DecimalPrecision").Value).Value.Value.ToString(CultureInfo.InvariantCulture)); ext = new FhirDecimal(dec60); - obs = new Observation(); + obs = new Observation + { + Code = new CodeableConcept("http://nu.nl", "bla"), + Status = ObservationStatus.Cancelled + }; + obs.AddExtension("http://example.org/DecimalPrecision", ext); json = FhirJsonSerializer.SerializeToString(obs); @@ -175,7 +186,7 @@ public void TestLongDecimalSerialization() { var dec = 3.1415926535897932384626433833m; var ext = new FhirDecimal(dec); - var obs = new Observation(); + var obs = new Observation() { Status = ObservationStatus.Amended, Code = new CodeableConcept("http://nu.nl", "bla") }; obs.AddExtension("http://example.org/DecimalPrecision", ext); var json = FhirJsonSerializer.SerializeToString(obs); @@ -254,7 +265,7 @@ public void TryXXEExploit() Assert.Fail(); } - catch (FormatException e) + catch (XmlException e) { Assert.IsTrue(e.Message.Contains("DTD is prohibited")); } diff --git a/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs b/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs index 4952323e6b..87d3e228c6 100644 --- a/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs +++ b/src/Hl7.Fhir.Shared.Tests/TestDataVersionCheck.cs @@ -29,8 +29,9 @@ public void VerifyAllTestData() private static void validateFolder(string basePath, string path, StringBuilder issues) { - var xmlParser = new FhirXmlParser(); - var jsonParser = new FhirJsonParser(); + var xmlParser = FhirXmlParser.OSTRICH; + var jsonParser = FhirJsonParser.OSTRICH; + Console.WriteLine($"Validating test files in {path.Replace(basePath, "")}"); foreach (var item in Directory.EnumerateFiles(path)) { diff --git a/src/Hl7.Fhir.Shared.Tests/Validation/ValidatePatient.cs b/src/Hl7.Fhir.Shared.Tests/Validation/ValidatePatient.cs index 37ee920056..a87bbdfdae 100644 --- a/src/Hl7.Fhir.Shared.Tests/Validation/ValidatePatient.cs +++ b/src/Hl7.Fhir.Shared.Tests/Validation/ValidatePatient.cs @@ -48,4 +48,4 @@ public void ValidateDemoPatient() Assert.IsTrue(results.Count > 0); } } -} +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs index e8cbd8589e..c3ac8e10bc 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirJsonParser.cs @@ -17,4 +17,20 @@ namespace Hl7.Fhir.Serialization; public class FhirJsonParser(ParserSettings? settings = null) - : BaseFhirJsonParser(ModelInfo.ModelInspector, settings); \ No newline at end of file + : BaseFhirJsonParser(ModelInfo.ModelInspector, settings) +{ + /// + public static readonly FhirJsonParser DEFAULT = new(); + + /// + public static readonly FhirJsonParser STRICT = new(new ParserSettings().UsingMode(DeserializationMode.Strict)); + + /// + public static readonly FhirJsonParser RECOVERABLE = new(new ParserSettings().UsingMode(DeserializationMode.Recoverable)); + + /// + public static readonly FhirJsonParser BACKWARDSCOMPATIBLE = new(new ParserSettings().UsingMode(DeserializationMode.BackwardsCompatible)); + + /// + public static readonly FhirJsonParser OSTRICH = new(new ParserSettings().UsingMode(DeserializationMode.Ostrich)); +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs index f380057257..3e90e4c4aa 100644 --- a/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs +++ b/src/Hl7.Fhir.Shims.STU3AndUp/Serialization/FhirXmlParser.cs @@ -13,4 +13,31 @@ namespace Hl7.Fhir.Serialization; public class FhirXmlParser(ParserSettings? settings = null) - : BaseFhirXmlParser(ModelInfo.ModelInspector, settings); \ No newline at end of file + : BaseFhirXmlParser(ModelInfo.ModelInspector, settings) +{ + /// + /// A parser with default settings: strict validation, only XML is not validated. + /// + public static readonly FhirXmlParser DEFAULT = new(); + + /// + /// A parser with the most strict settings, will detect all issues we know about. + /// + public static readonly FhirXmlParser STRICT = new(new ParserSettings().UsingMode(DeserializationMode.Strict)); + + /// + /// A parser that allows all errors that will not lead to dataloss when roundtripping. + /// + public static readonly FhirXmlParser RECOVERABLE = new(new ParserSettings().UsingMode(DeserializationMode.Recoverable)); + + /// + /// A parser that allows all errors that result from reading data from other FHIR versions: it allows + /// unknown elements and coded values. This will be roundtrippable. + /// + public static readonly FhirXmlParser BACKWARDSCOMPATIBLE = new(new ParserSettings().UsingMode(DeserializationMode.BackwardsCompatible)); + + /// + /// A parser that continues to parse, ignoring all errors. May result in data loss. + /// + public static readonly FhirXmlParser OSTRICH = new(new ParserSettings().UsingMode(DeserializationMode.Ostrich)); +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs b/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs index 641864d0ee..3cbf03e3cd 100644 --- a/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs +++ b/src/Hl7.Fhir.Specification.STU3.Tests/SpecificationTestDataVersionCheck.cs @@ -38,8 +38,8 @@ private static void validateFolder(string basePath, string path, List is return; - var xmlParser = new FhirXmlParser(); - var jsonParser = new FhirJsonParser(); + var xmlParser = FhirXmlParser.OSTRICH; + var jsonParser = FhirJsonParser.OSTRICH; Console.WriteLine($"Validating test files in {path.Replace(basePath, "")}"); foreach (var item in Directory.EnumerateFiles(path)) { diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs index f24129ec9a..b4db7f3d0c 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/Snapshot/SnapshotGeneratorManifestTests.cs @@ -62,27 +62,27 @@ public class SnapshotGeneratorManifestTests }; private static readonly ParserSettings _parserSettings = - new ParserSettings().UsingMode(DeserializationMode.Recoverable); + new ParserSettings().UsingMode(DeserializationMode.Ostrich); static readonly DirectorySourceSettings _dirSourceSettings = new DirectorySourceSettings() { IncludeSubDirectories = true, // Exclude expected output, to prevent canonical url conflicts // Also include duplicate input file "t24a", conflicts with "t24a-input" - Excludes = new string[] { "manifest.xml", "*-expected*", "*-output*", "t24a.xml" }, + Excludes = ["manifest.xml", "*-expected*", "*-output*", "t24a.xml"], FormatPreference = DirectorySource.DuplicateFilenameResolution.PreferXml, XmlParserSettings = _fhirXmlParserSettings }; - static readonly SnapshotGeneratorSettings _snapGenSettings = new SnapshotGeneratorSettings() + static readonly SnapshotGeneratorSettings _snapGenSettings = new() { ForceRegenerateSnapshots = true, GenerateSnapshotForExternalProfiles = true }; - static readonly FhirXmlParser _fhirXmlParser = new FhirXmlParser(_parserSettings); - static readonly FhirJsonParser _fhirJsonParser = new FhirJsonParser(_parserSettings); - static readonly FhirXmlSerializer _fhirXmlSerializer = new FhirXmlSerializer(); + private static readonly FhirXmlParser FHIR_XML_PARSER = new(_parserSettings); + private static readonly FhirJsonParser FHIR_JSON_PARSER = new(_parserSettings); + private static readonly FhirXmlSerializer FHIR_XML_SERIALIZER = new(); string _testPath; DirectorySource _dirSource; @@ -636,20 +636,15 @@ static StructureDefinition Load(string filePath) using (var stream = File.OpenRead(filePath)) using (var reader = new XmlTextReader(stream)) { - return _fhirXmlParser.Parse(reader); + return FHIR_XML_PARSER.Parse(reader); } } filePath = Path.ChangeExtension(filePath, "json"); if (File.Exists(filePath)) { - //using (var stream = _dirSource.LoadArtifactByName(filePath)) - using (var stream = File.OpenRead(filePath)) - using (var textReader = new StreamReader(stream)) - using (var reader = new JsonTextReader(textReader)) - { - return _fhirJsonParser.Parse(reader); - } + var text = File.ReadAllText(filePath); + return FHIR_JSON_PARSER.Parse(text); } Assert.Fail($"File not found: '{filePath}'"); @@ -667,7 +662,7 @@ static void SaveOutput(string id, StructureDefinition output) static void Save(string filePath, Base output) { - var xml = _fhirXmlSerializer.SerializeToString(output, pretty: true); + var xml = FHIR_XML_SERIALIZER.SerializeToString(output, pretty: true); File.WriteAllText(filePath, xml); } diff --git a/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs b/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs index 3c8fa016be..2927cea9b7 100644 --- a/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs +++ b/src/Hl7.Fhir.Specification.Shared.Tests/SpecificationTestDataVersionCheck.cs @@ -23,7 +23,7 @@ public async Tasks.Task VerifyAllTestDataSpecification() string location = typeof(TestDataHelper).GetTypeInfo().Assembly.Location; var path = Path.GetDirectoryName(location) + "/TestData"; Console.WriteLine(path); - List issues = new List(); + List issues = []; await ValidateFolder(path, path, issues); Assert.AreEqual(0, issues.Count); } @@ -32,8 +32,8 @@ private async Tasks.Task ValidateFolder(string basePath, string path, List(tpXml); diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTestFromSpec.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTestFromSpec.cs index 28269d9649..bdcad894c7 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTestFromSpec.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathEvaluatorTestFromSpec.cs @@ -181,7 +181,7 @@ private void runTests(string pathToTest, IEnumerable ignoreTestcases) if (!_cache.ContainsKey(inputfile)) { - _cache.Add(inputfile, new FhirXmlParser().Parse( + _cache.Add(inputfile, FhirXmlParser.OSTRICH.Parse( File.ReadAllText(Path.Combine(basepath, inputfile)))); } // Now perform this unit test diff --git a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs index 03ff2df232..bc3db42fbe 100644 --- a/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs +++ b/src/Hl7.FhirPath.R4.Tests/PocoTests/FhirPathTestDataVersionCheck.cs @@ -33,8 +33,8 @@ private void ValidateFolder(string basePath, string path, List issues) if (path.Contains("source-test")) return; - var xmlParser = new Hl7.Fhir.Serialization.FhirXmlParser(); - var jsonParser = new Fhir.Serialization.FhirJsonParser(); + var xmlParser = FhirXmlParser.OSTRICH; // There are many errors in the test data + var jsonParser = FhirJsonParser.OSTRICH; // Idem Console.WriteLine($"Validating test files in {path.Replace(basePath, "")}"); foreach (var item in Directory.EnumerateFiles(path)) { From 46999fa24452d20be0eb595c9cb5fcc723ce5b59 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Sat, 8 Mar 2025 07:58:09 +0100 Subject: [PATCH 4/8] Fix long-running test failures detected by pipeline. --- ...ValidateAllExamplesSearchExtractionTest.cs | 258 +++++++++--------- .../Validation/SearchDataExtraction.cs | 220 ++++++++------- .../Validation/SearchDataExtraction.cs | 247 ++++++++--------- 3 files changed, 359 insertions(+), 366 deletions(-) diff --git a/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs b/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs index 4e243e4798..f0baeafc4f 100644 --- a/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs +++ b/src/Hl7.Fhir.STU3.Tests/Model/ValidateAllExamplesSearchExtractionTest.cs @@ -19,180 +19,184 @@ using Hl7.Fhir.Utility; using Hl7.Fhir.ElementModel; -namespace Hl7.Fhir.Tests.Model +namespace Hl7.Fhir.Tests.Model; + +[TestClass] +public class ValidateSearchExtractionAllExamplesTest { - [TestClass] - public class ValidateSearchExtractionAllExamplesTest - { - public ILookup SpList; + public ILookup SpList; + [TestMethod] + [TestCategory("LongRunner")] + public void SearchExtractionAllExamples() + { + SpList = ModelInfo.SearchParameters + .Where(spd => !String.IsNullOrEmpty(spd.Expression)) + .Select(spd => + new { Rt = (ResourceType)Enum.Parse(typeof(ResourceType), spd.Resource), Def = spd }) + .ToLookup(ks => ks.Rt, es => es.Def); - [TestMethod] - [TestCategory("LongRunner")] - public void SearchExtractionAllExamples() - { - SpList = ModelInfo.SearchParameters - .Where(spd => !String.IsNullOrEmpty(spd.Expression)) - .Select(spd => - new { Rt = (ResourceType)Enum.Parse(typeof(ResourceType), spd.Resource), Def = spd }) - .ToLookup(ks => ks.Rt, es => es.Def); - - SearchExtractionAllExamplesInternal(); - // SearchExtractionAllExamplesInternal(); - } + searchExtractionAllExamplesInternal(); + } - private void SearchExtractionAllExamplesInternal() + private void searchExtractionAllExamplesInternal() + { + var parser = FhirXmlParser.RECOVERABLE; + int errorCount = 0; + int parserErrorCount = 0; + int testFileCount = 0; + var exampleSearchValues = new Dictionary(); + var zip = TestDataHelper.ReadTestZip("examples.zip"); + + using (zip) { - var parser = new FhirXmlParser(new ParserSettings().UsingMode(DeserializationMode.Recoverable)); - int errorCount = 0; - int parserErrorCount = 0; - int testFileCount = 0; - var exampleSearchValues = new Dictionary(); - var zip = TestDataHelper.ReadTestZip("examples.zip"); - - using (zip) + foreach (var entry in zip.Entries) { - foreach (var entry in zip.Entries) + Stream file = entry.Open(); + using (file) { - Stream file = entry.Open(); - using (file) - { - // Verified examples that fail validations + // Verified examples that fail validations - //// vsd-3, vsd-8 - //if (file.EndsWith("valueset-ucum-common(ucum-common).xml")) - // continue; + //// vsd-3, vsd-8 + //if (file.EndsWith("valueset-ucum-common(ucum-common).xml")) + // continue; - testFileCount++; - try - { - // Debug.WriteLine(String.Format("Validating {0}", file)); - var reader = SerializationUtil.WrapXmlReader(XmlReader.Create(file)); - var resource = parser.Parse(reader); + testFileCount++; + try + { + // Debug.WriteLine(String.Format("Validating {0}", file)); + var reader = SerializationUtil.WrapXmlReader(XmlReader.Create(file)); + var resource = parser.Parse(reader); - ExtractValuesForSearchParameterFromFile(exampleSearchValues, resource); + extractValuesForSearchParameterFromFile(exampleSearchValues, resource); - if (resource is Bundle) + if (resource is Bundle bundle) + { + foreach (var item in bundle.Entry) { - foreach (var item in (resource as Bundle).Entry) + if (item.Resource != null) { - if (item.Resource != null) - { - ExtractValuesForSearchParameterFromFile(exampleSearchValues, item.Resource); - } + extractValuesForSearchParameterFromFile(exampleSearchValues, item.Resource); } } } - catch (Exception ex) - { - System.Diagnostics.Trace.WriteLine("Error processing file " + entry.Name + ": " + ex.Message); - parserErrorCount++; - } - } + catch (Exception ex) + { + Trace.WriteLine("Error processing file " + entry.Name + ": " + ex.Message); + parserErrorCount++; + } + } } + } - var missingSearchValues = exampleSearchValues.Where(i => i.Value.count == 0); + var missingSearchValues = exampleSearchValues.Where(i => i.Value.Count == 0).ToArray(); - if (missingSearchValues.Count() > 0) + if (missingSearchValues.Any()) + { + Debug.WriteLine( + $"\r\n------------------\r\n" + + $"Validation failed, missing data in {missingSearchValues.Length} of " + + $"{exampleSearchValues.Count} search parameters"); + + foreach (var item in missingSearchValues) { - Debug.WriteLine(String.Format("\r\n------------------\r\nValidation failed, missing data in {0} of {1} search parameters", missingSearchValues.Count(), exampleSearchValues.Count)); - foreach (var item in missingSearchValues) - { - Trace.WriteLine("\t" + item.Key.Resource.ToString() + "_" + item.Key.Name); - } - // Trace.WriteLine(outcome.ToString()); - errorCount++; + Trace.WriteLine("\t" + item.Key.Resource + "_" + item.Key.Name); } - - Assert.IsTrue(43 >= errorCount, String.Format("Failed search parameter data extraction, missing data in {0} of {1} search parameters", missingSearchValues.Count(), exampleSearchValues.Count)); - Assert.AreEqual(0, parserErrorCount, String.Format("Failed search parameter data extraction, {0} files failed parsing", parserErrorCount)); + // Trace.WriteLine(outcome.ToString()); + errorCount++; } - private void ExtractValuesForSearchParameterFromFile(Dictionary exampleSearchValues, Resource resource) + Assert.IsTrue(43 >= errorCount, + $"Failed search parameter data extraction, missing data in {missingSearchValues.Length} of " + + $"{exampleSearchValues.Count} search parameters"); + Assert.AreEqual(0, parserErrorCount, + $"Failed search parameter data extraction, {parserErrorCount} files failed parsing"); + } + + private void extractValuesForSearchParameterFromFile(Dictionary exampleSearchValues, Resource resource) + { + // Extract the search properties + resource.TryDeriveResourceType(out var rt); + var searchparameters = SpList[rt]; + foreach (var index in searchparameters) { - // Extract the search properties - resource.TryDeriveResourceType(out var rt); - var searchparameters = SpList[rt]; - foreach (var index in searchparameters) - { - // prepare the search data cache - if (!exampleSearchValues.ContainsKey(index)) - exampleSearchValues.Add(index, new Holder()); + // prepare the search data cache + if (!exampleSearchValues.ContainsKey(index)) + exampleSearchValues.Add(index, new Holder()); - // Extract the values from the example - ExtractExamplesFromResource(exampleSearchValues, resource, index); - } + // Extract the values from the example + extractExamplesFromResource(exampleSearchValues, resource, index); + } - // If there are any contained resources, extract index data from those too! - if (resource is DomainResource) + // If there are any contained resources, extract index data from those too! + if (resource is DomainResource domainResource) + { + if (domainResource.Contained is { Count: > 0 }) { - if ((resource as DomainResource).Contained != null && (resource as DomainResource).Contained.Count > 0) + foreach (var conResource in domainResource.Contained) { - foreach (var conResource in (resource as DomainResource).Contained) - { - ExtractValuesForSearchParameterFromFile(exampleSearchValues, conResource); - } + extractValuesForSearchParameterFromFile(exampleSearchValues, conResource); } } } + } - class Holder - { - public int count; - } + private class Holder + { + public int Count; + } - - - private static void ExtractExamplesFromResource(Dictionary exampleSearchValues, Resource resource, - SearchParamDefinition index ) - { - var resourceModel = resource.ToTypedElement(); - try + + private static void extractExamplesFromResource(Dictionary exampleSearchValues, Resource resource, + SearchParamDefinition index ) + { + var resourceModel = resource.ToTypedElement(); + + try + { + var results = resourceModel.Select(index.Expression, new EvaluationContext()).ToArray(); + if (results.Any()) { - var results = resourceModel.Select(index.Expression, new EvaluationContext()); - if (results.Count() > 0) + foreach (var t2 in results) { - foreach (var t2 in results) + if (t2 != null) { - if (t2 != null) - { - exampleSearchValues[index].count++; + exampleSearchValues[index].Count++; - if (t2 is PocoElementNode && (t2 as PocoElementNode).FhirValue != null) - { - // Validate the type of data returned against the type of search parameter - // Debug.Write(index.Resource + "." + index.Name + ": "); - // Debug.WriteLine((t2 as FhirPath.ModelNavigator).FhirValue.ToString());// + "\r\n"; + if (t2 is PocoElementNode { FhirValue: not null }) + { + // Validate the type of data returned against the type of search parameter + // Debug.Write(index.Resource + "." + index.Name + ": "); + // Debug.WriteLine((t2 as FhirPath.ModelNavigator).FhirValue.ToString());// + "\r\n"; - } - //else if (t2.Value is Hl7.FhirPath.ConstantValue) - //{ - // // Debug.Write(index.Resource + "." + index.Name + ": "); - // // Debug.WriteLine((t2.Value as Hl7.FhirPath.ConstantValue).Value); - //} - else if (t2.Value is bool) - { - // Debug.Write(index.Resource + "." + index.Name + ": "); - // Debug.WriteLine((bool)t2.Value); - } - else - { - Debug.Write(index.Resource + "." + index.Name + ": "); - Debug.WriteLine(t2.Value); - } + } + //else if (t2.Value is Hl7.FhirPath.ConstantValue) + //{ + // // Debug.Write(index.Resource + "." + index.Name + ": "); + // // Debug.WriteLine((t2.Value as Hl7.FhirPath.ConstantValue).Value); + //} + else if (t2.Value is bool) + { + // Debug.Write(index.Resource + "." + index.Name + ": "); + // Debug.WriteLine((bool)t2.Value); + } + else + { + Debug.Write(index.Resource + "." + index.Name + ": "); + Debug.WriteLine(t2.Value); } } } } - catch (ArgumentException ex) - { - Debug.WriteLine("FATAL: Error parsing expression in search index {0}.{1} {2}\r\n\t{3}", index.Resource, index.Name, index.Expression, ex.Message); - } + } + catch (ArgumentException ex) + { + Debug.WriteLine("FATAL: Error parsing expression in search index {0}.{1} {2}\r\n\t{3}", index.Resource, index.Name, index.Expression, ex.Message); } } } \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3.Tests/Validation/SearchDataExtraction.cs b/src/Hl7.Fhir.STU3.Tests/Validation/SearchDataExtraction.cs index ec7cc1febd..87e56ffcce 100644 --- a/src/Hl7.Fhir.STU3.Tests/Validation/SearchDataExtraction.cs +++ b/src/Hl7.Fhir.STU3.Tests/Validation/SearchDataExtraction.cs @@ -11,7 +11,6 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Serialization; using Hl7.Fhir.Utility; -using Hl7.FhirPath; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -22,152 +21,149 @@ using System.Xml; using FhirEvaluationContext = Hl7.Fhir.FhirPath.FhirEvaluationContext; -namespace Hl7.Fhir.Test.Validation +namespace Hl7.Fhir.Test.Validation; + +[TestClass] +public class ValidateSearchExtractionAllExamplesTest { - [TestClass] -#if PORTABLE45 - public class PortableValidateSearchExtractionAllExamplesTest -#else - public class ValidateSearchExtractionAllExamplesTest -#endif + [TestMethod] + [TestCategory("LongRunner")] + public void SearchExtractionAllExamples() { - [TestMethod] - [TestCategory("LongRunner")] - public void SearchExtractionAllExamples() - { - string examplesZip = @"TestData/examples.zip"; + string examplesZip = @"TestData/examples.zip"; - FhirXmlParser parser = new FhirXmlParser(); - int errorCount = 0; - int parserErrorCount = 0; - int testFileCount = 0; - Dictionary exampleSearchValues = new Dictionary(); - Dictionary failedInvariantCodes = new Dictionary(); + FhirXmlParser parser = FhirXmlParser.RECOVERABLE; + int errorCount = 0; + int parserErrorCount = 0; + int testFileCount = 0; + var exampleSearchValues = new Dictionary(); - using var zip = ZipFile.OpenRead(examplesZip); - foreach (var entry in zip.Entries) + using var zip = ZipFile.OpenRead(examplesZip); + foreach (var entry in zip.Entries) + { + Stream file = entry.Open(); + using (file) { - Stream file = entry.Open(); - using (file) - { - // Verified examples that fail validations + // Verified examples that fail validations - //// vsd-3, vsd-8 - //if (file.EndsWith("valueset-ucum-common(ucum-common).xml")) - // continue; + //// vsd-3, vsd-8 + //if (file.EndsWith("valueset-ucum-common(ucum-common).xml")) + // continue; - testFileCount++; + testFileCount++; - try - { - // Debug.WriteLine(String.Format("Validating {0}", file)); - var reader = SerializationUtil.WrapXmlReader(XmlReader.Create(file)); - var resource = parser.Parse(reader); + try + { + // Debug.WriteLine(String.Format("Validating {0}", file)); + var reader = SerializationUtil.WrapXmlReader(XmlReader.Create(file)); + var resource = parser.Parse(reader); - ExtractValuesForSearchParameterFromFile(exampleSearchValues, resource); + extractValuesForSearchParameterFromFile(exampleSearchValues, resource); - if (resource is Bundle) + if (resource is Bundle bundle) + { + foreach (var item in bundle.Entry) { - foreach (var item in (resource as Bundle).Entry) + if (item.Resource != null) { - if (item.Resource != null) - { - ExtractValuesForSearchParameterFromFile(exampleSearchValues, item.Resource); - } + extractValuesForSearchParameterFromFile(exampleSearchValues, item.Resource); } } } - catch (Exception ex) - { - System.Diagnostics.Trace.WriteLine("Error processing file " + entry.Name + ": " + ex.Message); - parserErrorCount++; - } } - } - - var missingSearchValues = exampleSearchValues.Where(i => i.Value == 0); - if (missingSearchValues.Count() > 0) - { - Debug.WriteLine(String.Format("\r\n------------------\r\nValidation failed, missing data in {0} of {1} search parameters", missingSearchValues.Count(), exampleSearchValues.Count)); - foreach (var item in missingSearchValues) + catch (Exception ex) { - Trace.WriteLine("\t" + item.Key); + Trace.WriteLine("Error processing file " + entry.Name + ": " + ex.Message); + parserErrorCount++; } - // Trace.WriteLine(outcome.ToString()); - errorCount++; } - - Assert.IsTrue(43 >= errorCount, String.Format("Failed Validating, missing data in {0} of {1} search parameters", missingSearchValues.Count(), exampleSearchValues.Count)); - Assert.AreEqual(0, parserErrorCount, String.Format("Failed search parameter data extraction, {0} files failed parsing", parserErrorCount)); } - private static void ExtractValuesForSearchParameterFromFile(Dictionary exampleSearchValues, Resource resource) + var missingSearchValues = exampleSearchValues.Where(i => i.Value == 0).ToArray(); + if (missingSearchValues.Any()) { - // Extract the search properties - var searchparameters = ModelInfo.SearchParameters.Where(r => r.Resource == resource.TypeName && !String.IsNullOrEmpty(r.Expression)); - foreach (var index in searchparameters) - { - // prepare the search data cache - string key = resource.TypeName + "_" + index.Name; - if (!exampleSearchValues.ContainsKey(key)) - exampleSearchValues.Add(key, 0); + Debug.WriteLine( + $"\r\n------------------\r\n" + + $"Validation failed, missing data in {missingSearchValues.Length} of " + + $"{exampleSearchValues.Count} search parameters"); - // Extract the values from the example - ExtractExamplesFromResource(exampleSearchValues, resource, index, key); + foreach (var item in missingSearchValues) + { + Trace.WriteLine("\t" + item.Key); } + // Trace.WriteLine(outcome.ToString()); + errorCount++; + } + + Assert.IsTrue(43 >= errorCount, + $"Failed Validating, missing data in {missingSearchValues.Length} of " + + $"{exampleSearchValues.Count} search parameters"); + Assert.AreEqual(0, parserErrorCount, + $"Failed search parameter data extraction, {parserErrorCount} files failed parsing"); + } + + private static void extractValuesForSearchParameterFromFile(Dictionary exampleSearchValues, Resource resource) + { + // Extract the search properties + var searchparameters = ModelInfo.SearchParameters.Where(r => r.Resource == resource.TypeName && !String.IsNullOrEmpty(r.Expression)); + foreach (var index in searchparameters) + { + // prepare the search data cache + string key = resource.TypeName + "_" + index.Name; + exampleSearchValues.TryAdd(key, 0); - // If there are any contained resources, extract index data from those too! - if (resource is DomainResource) + // Extract the values from the example + extractExamplesFromResource(exampleSearchValues, resource, index, key); + } + + // If there are any contained resources, extract index data from those too! + if (resource is DomainResource domainResource) + { + if (domainResource.Contained is { Count: > 0 }) { - if ((resource as DomainResource).Contained != null && (resource as DomainResource).Contained.Count > 0) + foreach (var conResource in domainResource.Contained) { - foreach (var conResource in (resource as DomainResource).Contained) - { - ExtractValuesForSearchParameterFromFile(exampleSearchValues, conResource); - } + extractValuesForSearchParameterFromFile(exampleSearchValues, conResource); } } } + } - private static void ExtractExamplesFromResource(Dictionary exampleSearchValues, Resource resource, SearchParamDefinition index, string key) + private static void extractExamplesFromResource(Dictionary exampleSearchValues, Resource resource, SearchParamDefinition index, string key) + { + var results = resource.Select(index.Expression, new FhirEvaluationContext()).ToArray(); + + if (results.Any()) { - var results = resource.Select(index.Expression, new FhirEvaluationContext()); - if (results.Any()) + // we perform the Select on a Poco, because then we get the FHIR dialect of FhirPath as well. + foreach (var t2 in results.Select(r => r.ToTypedElement())) { - // we perform the Select on a Poco, because then we get the FHIR dialect of FhirPath as well. - foreach (var t2 in results.Select(r => r.ToTypedElement())) + var fhirValueProvider = t2.Annotation(); + if (fhirValueProvider?.FhirValue != null) { - if (t2 != null) - { - var fhirValueProvider = t2.Annotation(); - if (fhirValueProvider?.FhirValue != null) - { - // Validate the type of data returned against the type of search parameter - // Debug.Write(index.Resource + "." + index.Name + ": "); - // Debug.WriteLine((t2 as FluentPath.PocoNavigator).FhirValue.ToString());// + "\r\n"; - exampleSearchValues[key]++; - // System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}", xpath.Value, t2.AsStringRepresentation())); - } - //else if (t2.Value is Hl7.FhirPath.ConstantValue) - //{ - // // Debug.Write(index.Resource + "." + index.Name + ": "); - // // Debug.WriteLine((t2.Value as Hl7.FluentPath.ConstantValue).Value); - // exampleSearchValues[key]++; - //} - else if (t2.Value is bool) - { - // Debug.Write(index.Resource + "." + index.Name + ": "); - // Debug.WriteLine((bool)t2.Value); - exampleSearchValues[key]++; - } - else - { - Debug.Write(index.Resource + "." + index.Name + ": "); - Debug.WriteLine(t2.Value); - exampleSearchValues[key]++; - } - } + // Validate the type of data returned against the type of search parameter + // Debug.Write(index.Resource + "." + index.Name + ": "); + // Debug.WriteLine((t2 as FluentPath.PocoNavigator).FhirValue.ToString());// + "\r\n"; + // System.Diagnostics.Trace.WriteLine(string.Format("{0}: {1}", xpath.Value, t2.AsStringRepresentation())); } + //else if (t2.Value is Hl7.FhirPath.ConstantValue) + //{ + // // Debug.Write(index.Resource + "." + index.Name + ": "); + // // Debug.WriteLine((t2.Value as Hl7.FluentPath.ConstantValue).Value); + // exampleSearchValues[key]++; + //} + else if (t2.Value is bool) + { + // Debug.Write(index.Resource + "." + index.Name + ": "); + // Debug.WriteLine((bool)t2.Value); + } + else + { + Debug.Write(index.Resource + "." + index.Name + ": "); + Debug.WriteLine(t2.Value); + } + + exampleSearchValues[key]++; } } } diff --git a/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs b/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs index 538a0fcd3e..5215451729 100644 --- a/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs +++ b/src/Hl7.Fhir.Shared.Tests/Validation/SearchDataExtraction.cs @@ -12,7 +12,6 @@ using Hl7.Fhir.Rest; using Hl7.Fhir.Serialization; using Hl7.Fhir.Utility; -using Hl7.FhirPath; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -21,171 +20,165 @@ using System.IO.Compression; using System.Linq; using System.Xml; -using static Hl7.Fhir.Model.ModelInfo; -namespace Hl7.Fhir.Test.Validation +namespace Hl7.Fhir.Test.Validation; + +[TestClass] +public partial class ValidateSearchExtractionAllExamplesTest { - [TestClass] - public partial class ValidateSearchExtractionAllExamplesTest + [TestMethod] + [TestCategory("LongRunner")] + public void SearchExtractionAllExamples() { - [TestMethod] - [TestCategory("LongRunner")] - public void SearchExtractionAllExamples() - { - string examplesZip = @"TestData/examples.zip"; + string examplesZip = @"TestData/examples.zip"; - FhirXmlParser parser = new(); - int errorCount = 0; - int parserErrorCount = 0; - int testFileCount = 0; - Dictionary exampleSearchValues = new(); - Dictionary failedInvariantCodes = new(); + FhirXmlParser parser = FhirXmlParser.RECOVERABLE; + int errorCount = 0; + int parserErrorCount = 0; + Dictionary exampleSearchValues = new(); - using var zip = ZipFile.OpenRead(examplesZip); - foreach (var entry in zip.Entries) - { - if (mustSkip(entry.Name)) continue; + using var zip = ZipFile.OpenRead(examplesZip); + foreach (var entry in zip.Entries) + { + if (mustSkip(entry.Name)) continue; - Stream file = entry.Open(); - using (file) + Stream file = entry.Open(); + using (file) + { + try { - testFileCount++; - - try - { - // Debug.WriteLine(String.Format("Validating {0}", file)); - var reader = SerializationUtil.WrapXmlReader(XmlReader.Create(file)); - var resource = parser.Parse(reader); + // Debug.WriteLine(String.Format("Validating {0}", file)); + var reader = SerializationUtil.WrapXmlReader(XmlReader.Create(file)); + var resource = parser.Parse(reader); + extractValuesForSearchParameterFromFile(exampleSearchValues, resource); - ExtractValuesForSearchParameterFromFile(exampleSearchValues, resource); - - if (resource is Bundle) + if (resource is Bundle bundle) + { + foreach (var item in bundle.Entry.Where(item => item.Resource != null)) { - foreach (var item in (resource as Bundle).Entry) - { - if (item.Resource != null) - { - ExtractValuesForSearchParameterFromFile(exampleSearchValues, item.Resource); - } - } + extractValuesForSearchParameterFromFile(exampleSearchValues, item.Resource); } } - catch (Exception ex) - { - System.Diagnostics.Trace.WriteLine("Error processing file " + entry.Name + ": " + ex.Message); - parserErrorCount++; - } } - } - - var missingSearchValues = exampleSearchValues.Where(i => i.Value == 0); - if (missingSearchValues.Any()) - { - Debug.WriteLine(String.Format("\r\n------------------\r\nValidation failed, missing data in {0} of {1} search parameters", missingSearchValues.Count(), exampleSearchValues.Count)); - foreach (var item in missingSearchValues) + catch (Exception ex) { - Trace.WriteLine("\t" + item.Key); + Trace.WriteLine("Error processing file " + entry.Name + ": " + ex.Message); + parserErrorCount++; } - // Trace.WriteLine(outcome.ToString()); - errorCount++; } + } - Assert.IsTrue(43 >= errorCount, String.Format("Failed Validating, missing data in {0} of {1} search parameters", missingSearchValues.Count(), exampleSearchValues.Count)); - Assert.AreEqual(0, parserErrorCount, String.Format("Failed search parameter data extraction, {0} files failed parsing", parserErrorCount)); + var missingSearchValues = exampleSearchValues.Where(i => i.Value == 0).ToArray(); + if (missingSearchValues.Any()) + { + Debug.WriteLine( + $"\r\n------------------\r\n" + + $"Validation failed, missing data in {missingSearchValues.Length} of " + + $"{exampleSearchValues.Count} search parameters"); - bool mustSkip(string fileName) => _filesToBeSkipped.Any(s => s.StartsWith(fileName)); + foreach (var item in missingSearchValues) + { + Trace.WriteLine("\t" + item.Key); + } + + errorCount++; } - private static void ExtractValuesForSearchParameterFromFile(Dictionary exampleSearchValues, Resource resource) + Assert.IsTrue(43 >= errorCount, + $"Failed Validating, missing data in {missingSearchValues.Length} of " + + $"{exampleSearchValues.Count} search parameters"); + Assert.AreEqual(0, parserErrorCount, + $"Failed search parameter data extraction, {parserErrorCount} files failed parsing"); + + bool mustSkip(string fileName) => _filesToBeSkipped.Any(s => s.StartsWith(fileName)); + } + + private static void extractValuesForSearchParameterFromFile(Dictionary exampleSearchValues, Resource resource) + { + // Extract the search properties + var searchparameters = ModelInfo.SearchParameters.Where(r => r.Resource == resource.TypeName && !String.IsNullOrEmpty(r.Expression)); + foreach (var index in searchparameters) { - // Extract the search properties - var searchparameters = ModelInfo.SearchParameters.Where(r => r.Resource == resource.TypeName && !String.IsNullOrEmpty(r.Expression)); - foreach (var index in searchparameters) - { - // prepare the search data cache - string key = resource.TypeName + "_" + index.Name; - if (!exampleSearchValues.ContainsKey(key)) - exampleSearchValues.Add(key, 0); + // prepare the search data cache + string key = resource.TypeName + "_" + index.Name; + exampleSearchValues.TryAdd(key, 0); - // Extract the values from the example - ExtractExamplesFromResource(exampleSearchValues, resource, index, key); - } + // Extract the values from the example + extractExamplesFromResource(exampleSearchValues, resource, index, key); + } - // If there are any contained resources, extract index data from those too! - if (resource is DomainResource) + // If there are any contained resources, extract index data from those too! + if (resource is DomainResource domainResource) + { + if (domainResource.Contained is { Count: > 0 }) { - if ((resource as DomainResource).Contained != null && (resource as DomainResource).Contained.Count > 0) + foreach (var conResource in domainResource.Contained) { - foreach (var conResource in (resource as DomainResource).Contained) - { - ExtractValuesForSearchParameterFromFile(exampleSearchValues, conResource); - } + extractValuesForSearchParameterFromFile(exampleSearchValues, conResource); } } } + } - private static void ExtractExamplesFromResource(Dictionary exampleSearchValues, Resource resource, SearchParamDefinition index, string key) + private static void extractExamplesFromResource(Dictionary exampleSearchValues, Resource resource, SearchParamDefinition index, string key) + { + List results; + try { - IEnumerable results; - try + // we perform the Select on a Poco, because then we get the FHIR dialect of FhirPath as well. + results = resource.Select(index.Expression!, + new FhirEvaluationContext { ElementResolver = mockResolver }).ToList(); + } + catch (Exception) + { + Trace.WriteLine($"Failed processing search expression {index.Name}: {index.Expression}"); + throw; + } + + if (!results.Any()) return; + + foreach (var t2 in results.Select(r => r.ToTypedElement())) + { + var fhirval = t2.Annotation(); + if (fhirval?.FhirValue != null) { - // we perform the Select on a Poco, because then we get the FHIR dialect of FhirPath as well. - results = resource.Select(index.Expression!, new FhirEvaluationContext { ElementResolver = mockResolver }); + // Validate the type of data returned against the type of search parameter + // Debug.Write(index.Resource + "." + index.Name + ": "); + // Debug.WriteLine((t2 as FhirPath.ModelNavigator).FhirValue.ToString());// + "\r\n"; } - catch (Exception) + //else if (t2.Value is Hl7.FhirPath.ConstantValue) + //{ + // // Debug.Write(index.Resource + "." + index.Name + ": "); + // // Debug.WriteLine((t2.Value as Hl7.FhirPath.ConstantValue).Value); + // exampleSearchValues[key]++; + //} + else if (t2.Value is bool) { - Trace.WriteLine($"Failed processing search expression {index.Name}: {index.Expression}"); - throw; + // Debug.Write(index.Resource + "." + index.Name + ": "); + // Debug.WriteLine((bool)t2.Value); } - if (results.Any()) + else { - foreach (var t2 in results.Select(r => r.ToTypedElement())) - { - if (t2 != null) - { - var fhirval = t2.Annotation(); - if (fhirval?.FhirValue != null) - { - // Validate the type of data returned against the type of search parameter - // Debug.Write(index.Resource + "." + index.Name + ": "); - // Debug.WriteLine((t2 as FhirPath.ModelNavigator).FhirValue.ToString());// + "\r\n"; - exampleSearchValues[key]++; - } - //else if (t2.Value is Hl7.FhirPath.ConstantValue) - //{ - // // Debug.Write(index.Resource + "." + index.Name + ": "); - // // Debug.WriteLine((t2.Value as Hl7.FhirPath.ConstantValue).Value); - // exampleSearchValues[key]++; - //} - else if (t2.Value is bool) - { - // Debug.Write(index.Resource + "." + index.Name + ": "); - // Debug.WriteLine((bool)t2.Value); - exampleSearchValues[key]++; - } - else - { - Debug.Write(index.Resource + "." + index.Name + ": "); - Debug.WriteLine(t2.Value); - exampleSearchValues[key]++; - } - } - } + Debug.Write(index.Resource + "." + index.Name + ": "); + Debug.WriteLine(t2.Value); } + + exampleSearchValues[key]++; } + } + + private static PocoNode mockResolver(string url) + { + var ri = new ResourceIdentity(url); - private static PocoNode mockResolver(string url) + if (!string.IsNullOrEmpty(ri.ResourceType)) { - ResourceIdentity ri = new ResourceIdentity(url); - if (!string.IsNullOrEmpty(ri.ResourceType)) - { - var type = ModelInfo.GetTypeForFhirType(ri.ResourceType)!; - DomainResource res = Activator.CreateInstance(type) as DomainResource; - res!.Id = ri.Id; - return res.ToPocoNode(); - } - return null; + var type = ModelInfo.GetTypeForFhirType(ri.ResourceType)!; + DomainResource res = Activator.CreateInstance(type) as DomainResource; + res!.Id = ri.Id; + return res.ToPocoNode(); } + return null; } } \ No newline at end of file From be834caf51a7644b73a1ad30e07a8295052d1a99 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Sat, 8 Mar 2025 08:31:30 +0100 Subject: [PATCH 5/8] Small changes after my own PR review. --- .../Rest/FhirClientSerializationEngineExtensions.cs | 7 +++++-- src/Hl7.Fhir.Base/Serialization/ParserSettings.cs | 10 +++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs index 990018ed14..508f15665b 100644 --- a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs +++ b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs @@ -20,10 +20,13 @@ public static class FhirClientSerializationEngineExtensions /// /// Configures the FhirClient to use the legacy serialization behaviour, using the . /// - public static BaseFhirClient WithLegacySerializer(this BaseFhirClient client, ParserSettings? parserSettings = null) + public static BaseFhirClient WithLegacySerializer(this BaseFhirClient client) { client.Settings.SerializationEngine = - FhirSerializationEngineFactory.Legacy.FromParserSettings(client.Inspector, parserSettings ?? new ParserSettings()); + FhirSerializationEngineFactory.Legacy.FromParserSettings(client.Inspector, +#pragma warning disable CS0618 // Type or member is obsolete + client.Settings.ParserSettings ?? new ParserSettings()); +#pragma warning restore CS0618 // Type or member is obsolete return client; } diff --git a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs index 6fce595c2c..74ee4e6205 100644 --- a/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs +++ b/src/Hl7.Fhir.Base/Serialization/ParserSettings.cs @@ -1,4 +1,12 @@ -#nullable enable +/* + * Copyright (c) 2016, Firely (info@fire.ly) and contributors + * See the file CONTRIBUTORS for details. + * + * This file is licensed under the BSD 3-Clause license + * available at https://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + */ + +#nullable enable using Hl7.Fhir.Utility; using Hl7.Fhir.Validation; From 30072dd5450481f5c5c7503f20b6c046dd54a435 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Sat, 8 Mar 2025 09:57:14 +0100 Subject: [PATCH 6/8] One more --- src/Hl7.Fhir.Base/CompatibilitySuppressions.xml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index 51461d2ade..70414eccf8 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -1961,13 +1961,6 @@ lib/net8.0/Hl7.Fhir.Base.dll true - - CP0002 - M:Hl7.Fhir.Rest.FhirClientSerializationEngineExtensions.WithLegacySerializer(Hl7.Fhir.Rest.BaseFhirClient) - lib/net8.0/Hl7.Fhir.Base.dll - lib/net8.0/Hl7.Fhir.Base.dll - true - CP0002 M:Hl7.Fhir.Rest.FhirClientSettings.get_CompressRequestBody @@ -4145,13 +4138,6 @@ lib/netstandard2.1/Hl7.Fhir.Base.dll true - - CP0002 - M:Hl7.Fhir.Rest.FhirClientSerializationEngineExtensions.WithLegacySerializer(Hl7.Fhir.Rest.BaseFhirClient) - lib/netstandard2.1/Hl7.Fhir.Base.dll - lib/netstandard2.1/Hl7.Fhir.Base.dll - true - CP0002 M:Hl7.Fhir.Rest.FhirClientSettings.get_CompressRequestBody From 07409d297be05faf9deff4fed20cb52597990eae Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Mon, 10 Mar 2025 13:15:31 +0100 Subject: [PATCH 7/8] Cleanup of roundtrip with the new serializers in place. --- .../RoundtripAllSerializers.cs | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/src/Hl7.Fhir.Serialization.Shared.Tests/RoundtripAllSerializers.cs b/src/Hl7.Fhir.Serialization.Shared.Tests/RoundtripAllSerializers.cs index 09beaae327..2d6906aa14 100644 --- a/src/Hl7.Fhir.Serialization.Shared.Tests/RoundtripAllSerializers.cs +++ b/src/Hl7.Fhir.Serialization.Shared.Tests/RoundtripAllSerializers.cs @@ -22,18 +22,19 @@ internal interface IRoundTripper string RoundTripJson(string original); } -internal class FhirSerializationEngineRoundtripper(IFhirSerializationEngine engine) : IRoundTripper +internal class FhirXmlJsonParserRoundtripper() : IRoundTripper { - public string RoundTripXml(string original) => - engine.SerializeToXml( - engine.DeserializeFromJson( - engine.SerializeToJson( - (engine.DeserializeFromXml(original))!))!); - public string RoundTripJson(string original) => - engine.SerializeToJson( - (engine.DeserializeFromXml( - engine.SerializeToXml( - engine.DeserializeFromJson(original)!)))!); + public string RoundTripXml(string original) => + FhirXmlSerializer.Default.SerializeToString( + FhirJsonParser.RECOVERABLE.Parse( + FhirJsonSerializer.Default.SerializeToString( + FhirXmlParser.RECOVERABLE.Parse(original)))); + + public string RoundTripJson(string original) => + FhirJsonSerializer.Default.SerializeToString( + FhirXmlParser.RECOVERABLE.Parse( + FhirXmlSerializer.Default.SerializeToString( + FhirJsonParser.RECOVERABLE.Parse(original)))); } internal class TypedElementBasedRoundtripper(IStructureDefinitionSummaryProvider provider) : IRoundTripper @@ -62,15 +63,8 @@ public string RoundTripJson(string original) [TestClass] public class RoundtripAllSerializers { - private static readonly IFhirSerializationEngine NEW_POCO_ENGINE = - FhirSerializationEngineFactory.Ostrich(ModelInfo.ModelInspector); - private static readonly IRoundTripper NEW_POCO_ROUNDTRIPPER = - new FhirSerializationEngineRoundtripper(NEW_POCO_ENGINE); - - private static readonly IRoundTripper OLD_POCO_ROUNDTRIPPER = - new FhirSerializationEngineRoundtripper( - FhirSerializationEngineFactory.Legacy.Ostrich(ModelInfo.ModelInspector)); + new FhirXmlJsonParserRoundtripper(); private static readonly IRoundTripper TYPEDELEM_POCOSDPROV = new TypedElementBasedRoundtripper(new PocoStructureDefinitionSummaryProvider()); @@ -92,15 +86,6 @@ public void FullRoundtripOfAllExamplesNewPocoSerializer(ZipArchiveEntry entry) doRoundTrip(entry, NEW_POCO_ROUNDTRIPPER); } - [DynamicData(nameof(prepareExampleZipFiles), DynamicDataSourceType.Method, - DynamicDataDisplayName = nameof(GetTestDisplayNames))] - [DataTestMethod] - [TestCategory("LongRunner")] - public void FullRoundtripOfAllExamplesOldPocoSerializer(ZipArchiveEntry entry) - { - doRoundTrip(entry, OLD_POCO_ROUNDTRIPPER); - } - [DynamicData(nameof(prepareExampleZipFiles), DynamicDataSourceType.Method, DynamicDataDisplayName = nameof(GetTestDisplayNames))] [DataTestMethod] @@ -246,8 +231,8 @@ public void TestMatchAndExactly(ZipArchiveEntry entry) var name = entry.Name; var resource = name.EndsWith(".xml") - ? NEW_POCO_ENGINE.DeserializeFromXml(input) - : NEW_POCO_ENGINE.DeserializeFromJson(input); + ? FhirXmlParser.RECOVERABLE.Parse(input) + : FhirJsonParser.RECOVERABLE.Parse(input); var r2 = resource!.DeepCopy(); Assert.IsTrue(resource.Matches(r2), From c16a03089f4c4ecda5f1edd402969f77efc1069a Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Mon, 10 Mar 2025 14:03:24 +0100 Subject: [PATCH 8/8] Make sure that, at least internally, we use RECOVERABLE as default ParserSettings. --- src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs | 6 +- ...FhirClientSerializationEngineExtensions.cs | 2 +- src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs | 6 +- .../Serialization/NavigatorStreamFactory.cs | 2 +- .../Source/DirectorySourceSettings.cs | 2 +- .../Specification/Source/DirectorySource.cs | 1701 ++++++++--------- .../Source/DirectorySourceSettings.cs | 53 +- .../Source/Summary/ArtifactSummary.cs | 243 ++- .../Summary/ArtifactSummaryHarvesters.cs | 730 +++---- 9 files changed, 1367 insertions(+), 1378 deletions(-) diff --git a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs index a5e335740e..830a9a58fa 100644 --- a/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs +++ b/src/Hl7.Fhir.Base/Rest/BaseFhirClient.cs @@ -975,7 +975,7 @@ private static bool isPostOrPutOrPatch(HttpMethod method) => private IFhirSerializationEngine getSerializationEngine() { #pragma warning disable CS0618 // Type or member is obsolete - return Settings.SerializationEngine ?? FhirSerializationEngineFactory.Legacy.FromParserSettings(Inspector, Settings.ParserSettings ?? new ParserSettings()); + return Settings.SerializationEngine ?? FhirSerializationEngineFactory.Legacy.FromParserSettings(Inspector, Settings.ParserSettings); #pragma warning restore CS0618 // Type or member is obsolete } @@ -1061,6 +1061,4 @@ public void Dispose() GC.SuppressFinalize(this); } #endregion -} - -#nullable restore \ No newline at end of file +} \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs index 508f15665b..9c78f6aeb1 100644 --- a/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs +++ b/src/Hl7.Fhir.Base/Rest/FhirClientSerializationEngineExtensions.cs @@ -25,7 +25,7 @@ public static BaseFhirClient WithLegacySerializer(this BaseFhirClient client) client.Settings.SerializationEngine = FhirSerializationEngineFactory.Legacy.FromParserSettings(client.Inspector, #pragma warning disable CS0618 // Type or member is obsolete - client.Settings.ParserSettings ?? new ParserSettings()); + client.Settings.ParserSettings); #pragma warning restore CS0618 // Type or member is obsolete return client; } diff --git a/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs b/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs index b5cc51201f..b9ddd6565c 100644 --- a/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs +++ b/src/Hl7.Fhir.Base/Rest/FhirClientSettings.cs @@ -93,8 +93,9 @@ public class FhirClientSettings /// /// ParserSettings for the pre-5.0 SDK parsers. Are only used when is not set. /// - [Obsolete("Use the SerializationEngine setting instead, chosing one of the options on FhirSerializationEngineFactory.")] - public ParserSettings? ParserSettings = new(); + [Obsolete( + "Use the SerializationEngine setting instead, chosing one of the options on FhirSerializationEngineFactory.")] + public ParserSettings ParserSettings = new ParserSettings().UsingMode(DeserializationMode.Recoverable); /// /// How to transfer binary data when sending data to a Binary endpoint. @@ -105,7 +106,6 @@ public class FhirClientSettings /// Whether we ask the server to return us binary data or a Binary resource. /// public BinaryTransferBehaviour BinaryReceivePreference = BinaryTransferBehaviour.UseData; - public FhirClientSettings() { } diff --git a/src/Hl7.Fhir.Base/Serialization/NavigatorStreamFactory.cs b/src/Hl7.Fhir.Base/Serialization/NavigatorStreamFactory.cs index 40bf8a24c4..3a25257365 100644 --- a/src/Hl7.Fhir.Base/Serialization/NavigatorStreamFactory.cs +++ b/src/Hl7.Fhir.Base/Serialization/NavigatorStreamFactory.cs @@ -102,7 +102,7 @@ public static INavigatorStream Create(string path, bool disposeStream) public class ConfigurableNavigatorStreamFactory { /// Create a new instance for the default parser settings. - internal static ConfigurableNavigatorStreamFactory CreateDefault() => new ConfigurableNavigatorStreamFactory(null, null); + internal static ConfigurableNavigatorStreamFactory CreateDefault() => new(null, null); /// Determines serialization format by inspecting the file extension. /// File path to a FHIR artifact. diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs b/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs index b4f177501f..c54ef89ead 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/DirectorySourceSettings.cs @@ -296,7 +296,7 @@ public string Mask public ParserSettings ParserSettings { get => _parserSettings; - set => _parserSettings = value ?? new ParserSettings(); + set => _parserSettings = value ?? new ParserSettings().UsingMode(DeserializationMode.Recoverable); } /// diff --git a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs index fbafb2c95b..3aec6d6745 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySource.cs @@ -6,15 +6,10 @@ * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE */ -// [WMR 20171023] TODO -// - Allow configuration of duplicate canonical url handling strategy - using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; -using Hl7.Fhir.Rest; using Hl7.Fhir.Serialization; using Hl7.Fhir.Specification.Summary; -using Hl7.Fhir.Support; using Hl7.Fhir.Utility; using System; using System.Collections.Generic; @@ -24,829 +19,826 @@ using System.Threading; using Tasks = System.Threading.Tasks; -namespace Hl7.Fhir.Specification.Source +namespace Hl7.Fhir.Specification.Source; + +/// Reads FHIR artifacts (Profiles, ValueSets, ...) from a directory on disk. Thread-safe. +[DebuggerDisplay(@"\{{DebuggerDisplay,nq}}")] +public class DirectorySource : ISummarySource, IConformanceSource, IArtifactSource, IAsyncResourceResolver { - /// Reads FHIR artifacts (Profiles, ValueSets, ...) from a directory on disk. Thread-safe. - [DebuggerDisplay(@"\{{DebuggerDisplay,nq}}")] - public class DirectorySource : ISummarySource, IConformanceSource, IArtifactSource, IResourceResolver, IAsyncResourceResolver + private static readonly StringComparer PathComparer = StringComparer.InvariantCultureIgnoreCase; + private static readonly StringComparison PathComparison = StringComparison.InvariantCultureIgnoreCase; + + // Files with following extensions are ALWAYS excluded from the result + private static readonly string[] ExecutableExtensions = [".exe", ".dll", ".cpl", ".scr"]; + + // Instance fields + private readonly DirectorySourceSettings _settings; + private readonly ArtifactSummaryGenerator _summaryGenerator; + private readonly ConfigurableNavigatorStreamFactory _navigatorFactory; + private readonly NavigatorStreamFactory _navigatorFactoryDelegate; + + // [WMR 20180813] NEW + // Use Lazy to synchronize collection (re-)loading (=> lock-free reading) + private Lazy> _lazyArtifactSummaries; + + /// + /// Create a new instance to browse and resolve resources + /// from the default + /// and using the default . + /// + /// Initialization is thread-safe. The source ensures that only a single thread will + /// collect the artifact summaries, while any other threads will block. + /// + /// + public DirectorySource() + : this(SpecificationDirectory, null, false) { } + + /// + /// Create a new instance to browse and resolve resources + /// from the specified + /// and using the default . + /// + /// + /// Initialization is thread-safe. The source ensures that only a single thread will + /// collect the artifact summaries, while any other threads will block. + /// + /// The file path of the target directory. + public DirectorySource(string contentDirectory) + : this(contentDirectory, null, false) { } + + /// + /// Create a new instance to browse and resolve resources + /// using the specified . + /// + /// Configuration settings that control the behavior of the . + /// One of the specified arguments is null. + public DirectorySource(DirectorySourceSettings settings) + : this(SpecificationDirectory, settings, true) { } + + /// + /// Create a new instance to browse and resolve resources + /// from the specified + /// and using the specified . + /// + /// Initialization is thread-safe. The source ensures that only a single thread will + /// collect the artifact summaries, while any other threads will block. + /// + /// + /// The file path of the target directory. + /// Configuration settings that control the behavior of the . + /// One of the specified arguments is null. + public DirectorySource(string contentDirectory, DirectorySourceSettings settings) + : this(contentDirectory, settings, true) { } + + // Internal ctor + private DirectorySource(string contentDirectory, DirectorySourceSettings settings, bool cloneSettings) { - private static readonly StringComparer PathComparer = StringComparer.InvariantCultureIgnoreCase; - private static readonly StringComparison PathComparison = StringComparison.InvariantCultureIgnoreCase; - - // Files with following extensions are ALWAYS excluded from the result - private static readonly string[] ExecutableExtensions = { ".exe", ".dll", ".cpl", ".scr" }; - - // Instance fields - private readonly DirectorySourceSettings _settings; - private readonly ArtifactSummaryGenerator _summaryGenerator; - private readonly ConfigurableNavigatorStreamFactory _navigatorFactory; - private readonly NavigatorStreamFactory _navigatorFactoryDelegate; - - // [WMR 20180813] NEW - // Use Lazy to synchronize collection (re-)loading (=> lock-free reading) - private Lazy> _lazyArtifactSummaries; - - /// - /// Create a new instance to browse and resolve resources - /// from the default - /// and using the default . - /// - /// Initialization is thread-safe. The source ensures that only a single thread will - /// collect the artifact summaries, while any other threads will block. - /// - /// - public DirectorySource() - : this(SpecificationDirectory, null, false) { } - - /// - /// Create a new instance to browse and resolve resources - /// from the specified - /// and using the default . - /// - /// - /// Initialization is thread-safe. The source ensures that only a single thread will - /// collect the artifact summaries, while any other threads will block. - /// - /// The file path of the target directory. - public DirectorySource(string contentDirectory) - : this(contentDirectory, null, false) { } - - /// - /// Create a new instance to browse and resolve resources - /// using the specified . - /// - /// Configuration settings that control the behavior of the . - /// One of the specified arguments is null. - public DirectorySource(DirectorySourceSettings settings) - : this(SpecificationDirectory, settings, true) { } - - /// - /// Create a new instance to browse and resolve resources - /// from the specified - /// and using the specified . - /// - /// Initialization is thread-safe. The source ensures that only a single thread will - /// collect the artifact summaries, while any other threads will block. - /// - /// - /// The file path of the target directory. - /// Configuration settings that control the behavior of the . - /// One of the specified arguments is null. - public DirectorySource(string contentDirectory, DirectorySourceSettings settings) - : this(contentDirectory, settings, true) { } - - // Internal ctor - DirectorySource(string contentDirectory, DirectorySourceSettings settings, bool cloneSettings) + // [WMR 20190305] FilePatternFilter requires ContentDirectory to be a full, absolute path + //ContentDirectory = contentDirectory ?? throw Error.ArgumentNull(nameof(contentDirectory)); + if (contentDirectory is null) { throw Error.ArgumentNull(nameof(contentDirectory)); } + ContentDirectory = Path.GetFullPath(contentDirectory); + + // [WMR 20171023] Clone specified settings to prevent shared state + _settings = settings != null + ? (cloneSettings ? new DirectorySourceSettings(settings) : settings) + : DirectorySourceSettings.CreateDefault(); + _summaryGenerator = new ArtifactSummaryGenerator(_settings.ExcludeSummariesForUnknownArtifacts); + _navigatorFactory = new ConfigurableNavigatorStreamFactory(_settings.XmlParserSettings, _settings.JsonParserSettings) { - // [WMR 20190305] FilePatternFilter requires ContentDirectory to be a full, absolute path - //ContentDirectory = contentDirectory ?? throw Error.ArgumentNull(nameof(contentDirectory)); - if (contentDirectory is null) { throw Error.ArgumentNull(nameof(contentDirectory)); } - ContentDirectory = Path.GetFullPath(contentDirectory); - - // [WMR 20171023] Clone specified settings to prevent shared state - _settings = settings != null - ? (cloneSettings ? new DirectorySourceSettings(settings) : settings) - : DirectorySourceSettings.CreateDefault(); - _summaryGenerator = new ArtifactSummaryGenerator(_settings.ExcludeSummariesForUnknownArtifacts); - _navigatorFactory = new ConfigurableNavigatorStreamFactory(_settings.XmlParserSettings, _settings.JsonParserSettings) - { - ThrowOnUnsupportedFormat = false - }; - _navigatorFactoryDelegate = new NavigatorStreamFactory(_navigatorFactory.Create); - // Initialize Lazy - Refresh(); - } + ThrowOnUnsupportedFormat = false + }; + _navigatorFactoryDelegate = _navigatorFactory.Create; - /// Returns the full path to the content directory. - public string ContentDirectory { get; } - - /// - /// The default directory this artifact source will access for its files. - /// - public static string SpecificationDirectory => DirectorySourceSettings.SpecificationDirectory; - - /// - /// Gets or sets a value that determines wether the should - /// also include artifacts from (nested) subdirectories of the specified content directory. - /// - /// Returns false by default. - /// - /// - /// - /// Enabling this setting requires the instance - /// to recursively scan all xml and json files that exist in the target directory - /// structure, which could unexpectedly turn into a long running operation. - /// Therefore, consumers should usually try to avoid to enable this setting when - /// the DirectorySource is targeting: - /// - /// - /// Directories with many deeply nested subdirectories - /// - /// - /// Common folders such as Desktop, My Documents etc. - /// - /// - /// Drive root folders such as C:\ - /// - /// - /// - public bool IncludeSubDirectories - { - get { return _settings.IncludeSubDirectories; } - set { _settings.IncludeSubDirectories = value; Refresh(); } - } + // Initialize Lazy + Refresh(); + } - /// - /// Gets or sets the search string to match against the names of files in the content directory. - /// The source will only provide resources from files that match the specified mask. - /// The source will ignore all files that don't match the specified mask. - /// Multiple masks can be split by '|'. - /// - /// - /// Mask filters are applied first, before any and filters. - /// - /// - /// Supported wildcards: - /// - /// - /// * - /// Matches zero or more characters within a file or directory name. - /// - /// - /// ? - /// Matches any single character - /// - /// - /// - /// - /// Mask = "v2*.*|*.StructureDefinition.*"; - /// - public string Mask - { - get { return _settings.Mask; } - set { _settings.Mask = value; Refresh(); } - } + /// Returns the full path to the content directory. + public string ContentDirectory { get; } + + /// + /// The default directory this artifact source will access for its files. + /// + public static string SpecificationDirectory => DirectorySourceSettings.SpecificationDirectory; + + /// + /// Gets or sets a value that determines wether the should + /// also include artifacts from (nested) subdirectories of the specified content directory. + /// + /// Returns false by default. + /// + /// + /// + /// Enabling this setting requires the instance + /// to recursively scan all xml and json files that exist in the target directory + /// structure, which could unexpectedly turn into a long running operation. + /// Therefore, consumers should usually try to avoid to enable this setting when + /// the DirectorySource is targeting: + /// + /// + /// Directories with many deeply nested subdirectories + /// + /// + /// Common folders such as Desktop, My Documents etc. + /// + /// + /// Drive root folders such as C:\ + /// + /// + /// + public bool IncludeSubDirectories + { + get { return _settings.IncludeSubDirectories; } + set { _settings.IncludeSubDirectories = value; Refresh(); } + } - /// - /// Gets or sets an array of search strings to match against the names of files in the content directory. - /// The source will only provide resources from files that match the specified mask. - /// The source will ignore all files that don't match the specified mask. - /// - /// - /// Mask filters are applied first, before and filters. - /// - /// - /// Supported wildcards: - /// - /// - /// * - /// Matches zero or more characters within a file or directory name. - /// - /// - /// ? - /// Matches any single character - /// - /// - /// - /// - /// Masks = new string[] { "v2*.*", "*.StructureDefinition.*" }; - /// - public string[] Masks - { - get { return _settings.Masks; } - set { _settings.Masks = value; Refresh(); } - } + /// + /// Gets or sets the search string to match against the names of files in the content directory. + /// The source will only provide resources from files that match the specified mask. + /// The source will ignore all files that don't match the specified mask. + /// Multiple masks can be split by '|'. + /// + /// + /// Mask filters are applied first, before any and filters. + /// + /// + /// Supported wildcards: + /// + /// + /// * + /// Matches zero or more characters within a file or directory name. + /// + /// + /// ? + /// Matches any single character + /// + /// + /// + /// + /// Mask = "v2*.*|*.StructureDefinition.*"; + /// + public string Mask + { + get { return _settings.Mask; } + set { _settings.Mask = value; Refresh(); } + } - /// - /// Gets or sets an array of search strings to match against the names of subdirectories of the content directory. - /// The source will only provide resources from subdirectories that match the specified include mask(s). - /// The source will ignore all subdirectories that don't match the specified include mask(s). - /// - /// - /// Include filters are applied after filters and before filters. - /// - /// - /// Supported wildcards: - /// - /// - /// * - /// Matches zero or more characters within a directory name. - /// - /// - /// ** - /// - /// Recursive wildcard. - /// For example, /hello/**/* matches all descendants of /hello. - /// - /// - /// - /// - /// - /// Includes = new string[] { "profiles/**/*", "**/valuesets" }; - /// - public string[] Includes - { - get { return _settings.Includes; } - set { _settings.Includes = value; Refresh(); } - } + /// + /// Gets or sets an array of search strings to match against the names of files in the content directory. + /// The source will only provide resources from files that match the specified mask. + /// The source will ignore all files that don't match the specified mask. + /// + /// + /// Mask filters are applied first, before and filters. + /// + /// + /// Supported wildcards: + /// + /// + /// * + /// Matches zero or more characters within a file or directory name. + /// + /// + /// ? + /// Matches any single character + /// + /// + /// + /// + /// Masks = new string[] { "v2*.*", "*.StructureDefinition.*" }; + /// + public string[] Masks + { + get { return _settings.Masks; } + set { _settings.Masks = value; Refresh(); } + } - /// - /// Gets or sets an array of search strings to match against the names of subdirectories of the content directory. - /// The source will ignore all subdirectories that match the specified exclude mask(s). - /// The source will only provide resources from subdirectories that don't match the specified exclude mask(s). - /// - /// - /// Exclude filters are applied last, after any and filters. - /// - /// - /// Supported wildcards: - /// - /// - /// * - /// Matches zero or more characters within a directory name. - /// - /// - /// ** - /// - /// Recursive wildcard. - /// For example, /hello/**/* matches all descendants of /hello. - /// - /// - /// - /// - /// - /// Excludes = new string[] { "profiles/**/old", "temp/**/*" }; - /// - public string[] Excludes - { - get { return _settings.Excludes; } - set { _settings.Excludes = value; Refresh(); } - } + /// + /// Gets or sets an array of search strings to match against the names of subdirectories of the content directory. + /// The source will only provide resources from subdirectories that match the specified include mask(s). + /// The source will ignore all subdirectories that don't match the specified include mask(s). + /// + /// + /// Include filters are applied after filters and before filters. + /// + /// + /// Supported wildcards: + /// + /// + /// * + /// Matches zero or more characters within a directory name. + /// + /// + /// ** + /// + /// Recursive wildcard. + /// For example, /hello/**/* matches all descendants of /hello. + /// + /// + /// + /// + /// + /// Includes = new string[] { "profiles/**/*", "**/valuesets" }; + /// + public string[] Includes + { + get { return _settings.Includes; } + set { _settings.Includes = value; Refresh(); } + } - // Note: DuplicateFilenameResolution must be in sync with FhirSerializationFormats + /// + /// Gets or sets an array of search strings to match against the names of subdirectories of the content directory. + /// The source will ignore all subdirectories that match the specified exclude mask(s). + /// The source will only provide resources from subdirectories that don't match the specified exclude mask(s). + /// + /// + /// Exclude filters are applied last, after any and filters. + /// + /// + /// Supported wildcards: + /// + /// + /// * + /// Matches zero or more characters within a directory name. + /// + /// + /// ** + /// + /// Recursive wildcard. + /// For example, /hello/**/* matches all descendants of /hello. + /// + /// + /// + /// + /// + /// Excludes = new string[] { "profiles/**/old", "temp/**/*" }; + /// + public string[] Excludes + { + get { return _settings.Excludes; } + set { _settings.Excludes = value; Refresh(); } + } - /// - /// Specifies how the should process duplicate files with multiple serialization formats. - /// - public enum DuplicateFilenameResolution - { - /// Prefer file with ".xml" extension over duplicate file with ".json" extension. - PreferXml, - /// Prefer file with ".json" extension over duplicate file with ".xml" extension. - PreferJson, - /// Return all files, do not filter duplicates. - KeepBoth - } + // Note: DuplicateFilenameResolution must be in sync with FhirSerializationFormats - /// Gets or sets a value that determines how to process duplicate files with multiple serialization formats. - /// The default value is . - public DuplicateFilenameResolution FormatPreference - { - get { return _settings.FormatPreference; } - set { _settings.FormatPreference = value; Refresh(); } - } + /// + /// Specifies how the should process duplicate files with multiple serialization formats. + /// + public enum DuplicateFilenameResolution + { + /// Prefer file with ".xml" extension over duplicate file with ".json" extension. + PreferXml, + /// Prefer file with ".json" extension over duplicate file with ".xml" extension. + PreferJson, + /// Return all files, do not filter duplicates. + KeepBoth + } - /// - /// Determines if the instance should harvest artifact - /// summary information in parallel on the thread pool. - /// - /// - /// By default, the harvests artifact summaries serially - /// on the calling thread. However if this option is enabled, then the DirectorySource - /// performs summary harvesting in parallel on the thread pool, in order to speed up - /// the process. This is especially effective when the content directory contains many - /// (nested) subfolders and files. - /// - public bool MultiThreaded - { - get { return _settings.MultiThreaded; } - set { _settings.MultiThreaded = value; } // Refresh(); - } + /// Gets or sets a value that determines how to process duplicate files with multiple serialization formats. + /// The default value is . + public DuplicateFilenameResolution FormatPreference + { + get { return _settings.FormatPreference; } + set { _settings.FormatPreference = value; Refresh(); } + } - /// - /// Determines wether the should exclude - /// artifact summaries for non-parseable (invalid or non-FHIR) content files. - /// - /// By default (false), the source will generate summaries for all files - /// that exist in the specified content directory and match the specified mask, - /// including files that cannot be parsed (e.g. invalid or non-FHIR content). - /// - /// - /// If true, then the source will only generate summaries for valid - /// FHIR artifacts that exist in the specified content directory and match the - /// specified mask. Unparseable files are ignored and excluded from the list - /// of artifact summaries. - /// - /// - public bool ExcludeSummariesForUnknownArtifacts + /// + /// Determines if the instance should harvest artifact + /// summary information in parallel on the thread pool. + /// + /// + /// By default, the harvests artifact summaries serially + /// on the calling thread. However if this option is enabled, then the DirectorySource + /// performs summary harvesting in parallel on the thread pool, in order to speed up + /// the process. This is especially effective when the content directory contains many + /// (nested) subfolders and files. + /// + public bool MultiThreaded + { + get { return _settings.MultiThreaded; } + set { _settings.MultiThreaded = value; } // Refresh(); + } + + /// + /// Determines wether the should exclude + /// artifact summaries for non-parseable (invalid or non-FHIR) content files. + /// + /// By default (false), the source will generate summaries for all files + /// that exist in the specified content directory and match the specified mask, + /// including files that cannot be parsed (e.g. invalid or non-FHIR content). + /// + /// + /// If true, then the source will only generate summaries for valid + /// FHIR artifacts that exist in the specified content directory and match the + /// specified mask. Unparseable files are ignored and excluded from the list + /// of artifact summaries. + /// + /// + public bool ExcludeSummariesForUnknownArtifacts + { + get { return _settings.ExcludeSummariesForUnknownArtifacts; } + set { - get { return _settings.ExcludeSummariesForUnknownArtifacts; } - set - { - _settings.ExcludeSummariesForUnknownArtifacts = value; - _summaryGenerator.ExcludeSummariesForUnknownArtifacts = value; - Refresh(); - } + _settings.ExcludeSummariesForUnknownArtifacts = value; + _summaryGenerator.ExcludeSummariesForUnknownArtifacts = value; + Refresh(); } + } - /// Gets the configuration settings that the behavior of the PoCo parser. - public ParserSettings ParserSettings => _settings.ParserSettings; + /// Gets the configuration settings that the behavior of the PoCo parser. + public ParserSettings ParserSettings => _settings.ParserSettings; - /// Gets the configuration settings that control the behavior of the XML parser. - public FhirXmlParsingSettings XmlParserSettings => _settings.XmlParserSettings; + /// Gets the configuration settings that control the behavior of the XML parser. + public FhirXmlParsingSettings XmlParserSettings => _settings.XmlParserSettings; - /// Gets the configuration settings that control the behavior of the JSON parser. - public FhirJsonParsingSettings JsonParserSettings => _settings.JsonParserSettings; + /// Gets the configuration settings that control the behavior of the JSON parser. + public FhirJsonParsingSettings JsonParserSettings => _settings.JsonParserSettings; + #region Refresh - #region Refresh + /// + /// Re-index the specified content directory. + /// + /// Clears the internal artifact summary cache. + /// Re-indexes the current and generates new summaries on demand, + /// during the next resolving call. + /// + /// + public void Refresh() + { + Refresh(false); + } - /// - /// Re-index the specified content directory. - /// - /// Clears the internal artifact summary cache. - /// Re-indexes the current and generates new summaries on demand, - /// during the next resolving call. - /// - /// - public void Refresh() + /// + /// Re-index the specified content directory. + /// + /// Clears the internal artifact summary cache. + /// Re-indexes the current and generates new summaries. + /// + /// + /// If equals true, then the source performs the re-indexing immediately. + /// Otherwise, if equals false, then re-indexing is performed on demand + /// during the next resolving request. + /// + /// + /// + /// Determines if the source should perform re-indexing immediately (true) or on demand (false). + /// + public void Refresh(bool force) + { + // Re-create lazy collection + // Assignment is atomic, no locking necessary + // Only single thread can call loadSummaries, any other threads will block + // Runtime exceptions during initialization are promoted to Value property getter + _lazyArtifactSummaries = new Lazy>(loadSummaries, LazyThreadSafetyMode.ExecutionAndPublication); + if (force) { - Refresh(false); + // [WMR 20180813] Verified: compiler does NOT remove this call in Release build + var dummy = _lazyArtifactSummaries.Value; } + } - /// - /// Re-index the specified content directory. - /// - /// Clears the internal artifact summary cache. - /// Re-indexes the current and generates new summaries. - /// - /// - /// If equals true, then the source performs the re-indexing immediately. - /// Otherwise, if equals false, then re-indexing is performed on demand - /// during the next resolving request. - /// - /// - /// - /// Determines if the source should perform re-indexing immediately (true) or on demand (false). - /// - public void Refresh(bool force) + /// + /// Re-index one or more specific artifact file(s). + /// This method is NOT thread-safe! + /// + /// Notifies the that specific files in the current + /// have been created, updated or deleted. + /// The argument should specify an array of artifact + /// file paths that (may) have been deleted, modified or created. + /// + /// + /// The source will: + /// + /// remove any existing summary information for the specified artifacts, if available; + /// try to harvest updated summary information from the specified artifacts, if they still exist. + /// + /// + /// + /// An array of artifact file path(s). + /// + /// true if any summary information was updated, or false otherwise. + /// + public bool Refresh(params string[] filePaths) + { + if (filePaths == null || filePaths.Length == 0) { - // Re-create lazy collection - // Assignment is atomic, no locking necessary - // Only single thread can call loadSummaries, any other threads will block - // Runtime exceptions during initialization are promoted to Value property getter - _lazyArtifactSummaries = new Lazy>(loadSummaries, LazyThreadSafetyMode.ExecutionAndPublication); - if (force) - { - // [WMR 20180813] Verified: compiler does NOT remove this call in Release build - var dummy = _lazyArtifactSummaries.Value; - } + // throw Error.ArgumentNullOrEmpty(nameof(filePaths)); + return true; // NOP } - /// - /// Re-index one or more specific artifact file(s). - /// This method is NOT thread-safe! - /// - /// Notifies the that specific files in the current - /// have been created, updated or deleted. - /// The argument should specify an array of artifact - /// file paths that (may) have been deleted, modified or created. - /// - /// - /// The source will: - /// - /// remove any existing summary information for the specified artifacts, if available; - /// try to harvest updated summary information from the specified artifacts, if they still exist. - /// - /// - /// - /// An array of artifact file path(s). - /// - /// true if any summary information was updated, or false otherwise. - /// - public bool Refresh(params string[] filePaths) - { - if (filePaths == null || filePaths.Length == 0) - { - // throw Error.ArgumentNullOrEmpty(nameof(filePaths)); - return true; // NOP - } + bool result = false; - bool result = false; + // [WMR 20180814] Possible protection: + // - Save current thread id in ctor + // - In this method, compare current thread id with saved id; throw if mismatch + // However this won't detect Refresh on main tread while bg threads are reading - // [WMR 20180814] Possible protection: - // - Save current thread id in ctor - // - In this method, compare current thread id with saved id; throw if mismatch - // However this won't detect Refresh on main tread while bg threads are reading + var summaries = GetSummaries(); + var factory = _navigatorFactoryDelegate; + var generator = _summaryGenerator; + var harvesters = _settings.SummaryDetailsHarvesters; - var summaries = GetSummaries(); - var factory = _navigatorFactoryDelegate; - var generator = _summaryGenerator; - var harvesters = _settings.SummaryDetailsHarvesters; + foreach (var filePath in filePaths) + { + // Always remove existing summaries + var isRemoved = summaries.RemoveAll(s => PathComparer.Equals(filePath, s.Origin)) > 0; - foreach (var filePath in filePaths) + // [WMR 20190528] Fixed: also update existing summaries! + if (File.Exists(filePath)) { - // Always remove existing summaries - var isRemoved = summaries.RemoveAll(s => PathComparer.Equals(filePath, s.Origin)) > 0; - - // [WMR 20190528] Fixed: also update existing summaries! - if (File.Exists(filePath)) - { - // File was added/changed; generate and add new summary - // [WMR 20190403] Fixed: inject navigator factory delegate - var newSummaries = generator.Generate(filePath, factory, harvesters); - summaries.AddRange(newSummaries); - result |= newSummaries.Count > 0; - } - else - { - // File was removed - result |= isRemoved; - } + // File was added/changed; generate and add new summary + // [WMR 20190403] Fixed: inject navigator factory delegate + var newSummaries = generator.Generate(filePath, factory, harvesters); + summaries.AddRange(newSummaries); + result |= newSummaries.Count > 0; + } + else + { + // File was removed + result |= isRemoved; } - return result; } + return result; + } - #endregion + #endregion - #region IArtifactSource + #region IArtifactSource - /// Returns a list of artifact filenames. - public IEnumerable ListArtifactNames() - { - return GetFileNames(); - } + /// Returns a list of artifact filenames. + public IEnumerable ListArtifactNames() + { + return GetFileNames(); + } - /// - /// Load the artifact with the specified file name. - /// Also accepts relative file paths. - /// - /// More than one file exists with the specified name. - public Stream LoadArtifactByName(string filePath) - { - if (filePath == null) throw Error.ArgumentNull(nameof(filePath)); - - var fullList = GetFilePaths(); - // [WMR 20190219] https://github.com/FirelyTeam/firely-net-sdk/issues/875 - // var fullFileName = GetFilePaths().SingleOrDefault(path => path.EndsWith(Path.DirectorySeparatorChar + filePath, PathComparison)); - //return fullFileName == null ? null : File.OpenRead(fullFileName); - - // Exact match on absolute path, or partial match on relative path - bool isMatch(ArtifactSummary summary) - => PathComparer.Equals(summary.Origin, filePath) - || summary.Origin.EndsWith(Path.DirectorySeparatorChar + filePath, PathComparison); - - // Only consider valid summaries for recognized artifacts - bool isCandidateArtifact(ArtifactSummary summary) - => !(summary is null) - // EK - // && !summary.IsFaulted - && !(summary.Origin is null) - && isMatch(summary); - - var candidates = GetSummaries().Where(isCandidateArtifact); - - // We might match multiple files; pick best/nearest fit - ArtifactSummary match = null; - foreach (var candidate in candidates) - { - if (match is null || candidate.Origin.Length < match.Origin.Length) - { - match = candidate; - } - } + /// + /// Load the artifact with the specified file name. + /// Also accepts relative file paths. + /// + /// More than one file exists with the specified name. + public Stream LoadArtifactByName(string filePath) + { + if (filePath == null) throw Error.ArgumentNull(nameof(filePath)); - return match?.Origin is null ? null : File.OpenRead(match.Origin); - } + GetFilePaths(); - #endregion + // [WMR 20190219] https://github.com/FirelyTeam/firely-net-sdk/issues/875 + // var fullFileName = GetFilePaths().SingleOrDefault(path => path.EndsWith(Path.DirectorySeparatorChar + filePath, PathComparison)); + //return fullFileName == null ? null : File.OpenRead(fullFileName); - #region IConformanceSource + // Exact match on absolute path, or partial match on relative path + bool isMatch(ArtifactSummary summary) + => PathComparer.Equals(summary.Origin, filePath) + || summary.Origin?.EndsWith(Path.DirectorySeparatorChar + filePath, PathComparison) is true; - /// List all resource uris, optionally filtered by type. - /// A enum value. - /// A sequence of uri strings. - public IEnumerable ListResourceUris(ResourceType? filter = null) - { - // [WMR 20180813] Do not return null values from non-FHIR artifacts (ResourceUri = null) - // => OfResourceType filters valid FHIR artifacts (ResourceUri != null) - return GetSummaries().OfResourceType(filter).Select(dsi => dsi.ResourceUri); - } + // Only consider valid summaries for recognized artifacts + bool isCandidateArtifact(ArtifactSummary summary) + => summary?.Origin != null && isMatch(summary); - /// - /// Find a resource by a canonical url that contains all codes from that codesystem. - /// - /// The canonical uri of a resource. - /// A resource, or null. - /// - /// It is very common for valuesets to represent all codes from a specific/smaller code system. - /// These are indicated by he CodeSystem.valueSet element, which is searched here. - /// - public CodeSystem FindCodeSystemByValueSet(string valueSetUri) - { - if (valueSetUri == null) throw Error.ArgumentNull(nameof(valueSetUri)); - var summary = GetSummaries().ResolveCodeSystem(valueSetUri); - return loadResourceInternal(summary); - } + var candidates = GetSummaries().Where(isCandidateArtifact); - /// Find resources which map from the given source to the given target. - /// An uri that is either the source uri, source ValueSet system or source StructureDefinition canonical url for the map. - /// An uri that is either the target uri, target ValueSet system or target StructureDefinition canonical url for the map. - /// A sequence of resources. - /// Either sourceUri may be null, or targetUri, but not both - public IEnumerable FindConceptMaps(string sourceUri = null, string targetUri = null) + // We might match multiple files; pick best/nearest fit + ArtifactSummary match = null; + foreach (var candidate in candidates) { - if (sourceUri == null && targetUri == null) + if (match is null || candidate.Origin?.Length < match.Origin?.Length) { - throw Error.ArgumentNull(nameof(targetUri), $"{nameof(sourceUri)} and {nameof(targetUri)} arguments cannot both be null"); + match = candidate; } - var summaries = GetSummaries().FindConceptMaps(sourceUri, targetUri); - return summaries.Select(summary => loadResourceInternal(summary)).Where(r => r != null); } - /// Finds a resource by matching any of a system's UniqueIds. - /// The unique id of a resource. - /// A resource, or null. - public NamingSystem FindNamingSystem(string uniqueId) - { - if (uniqueId == null) throw Error.ArgumentNull(nameof(uniqueId)); - var summary = GetSummaries().ResolveNamingSystem(uniqueId); - return loadResourceInternal(summary); - } + return match?.Origin is null ? null : File.OpenRead(match.Origin); + } + + #endregion - #endregion + #region IConformanceSource + + /// List all resource uris, optionally filtered by type. + /// A enum value. + /// A sequence of uri strings. + public IEnumerable ListResourceUris(ResourceType? filter = null) + { + // [WMR 20180813] Do not return null values from non-FHIR artifacts (ResourceUri = null) + // => OfResourceType filters valid FHIR artifacts (ResourceUri != null) + return GetSummaries().OfResourceType(filter).Select(dsi => dsi.ResourceUri); + } - #region ISummarySource + /// + /// Find a resource by a canonical url that contains all codes from that codesystem. + /// + /// The canonical uri of a resource. + /// A resource, or null. + /// + /// It is very common for valuesets to represent all codes from a specific/smaller code system. + /// These are indicated by he CodeSystem.valueSet element, which is searched here. + /// + public CodeSystem FindCodeSystemByValueSet(string valueSetUri) + { + if (valueSetUri == null) throw Error.ArgumentNull(nameof(valueSetUri)); + var summary = GetSummaries().ResolveCodeSystem(valueSetUri); + return loadResourceInternal(summary); + } - /// Returns a list of instances with key information about each FHIR artifact provided by the source. - public IEnumerable ListSummaries() + /// Find resources which map from the given source to the given target. + /// An uri that is either the source uri, source ValueSet system or source StructureDefinition canonical url for the map. + /// An uri that is either the target uri, target ValueSet system or target StructureDefinition canonical url for the map. + /// A sequence of resources. + /// Either sourceUri may be null, or targetUri, but not both + public IEnumerable FindConceptMaps(string sourceUri = null, string targetUri = null) + { + if (sourceUri == null && targetUri == null) { - return GetSummaries(); + throw Error.ArgumentNull(nameof(targetUri), $"{nameof(sourceUri)} and {nameof(targetUri)} arguments cannot both be null"); } + var summaries = GetSummaries().FindConceptMaps(sourceUri, targetUri); + return summaries.Select(summary => loadResourceInternal(summary)).Where(r => r != null); + } - /// - /// Load the resource from which the specified summary was generated. - /// - /// This implementation annotates returned resource instances with an - /// that captures the value of the property. - /// The extension method - /// provides access to the annotated location. - /// - /// - /// An instance generated by this source. - /// A new instance, or null. - /// - /// The and - /// summary properties allow the source to identify and resolve the artifact. - /// - public Resource LoadBySummary(ArtifactSummary summary) - { - if (summary == null) { throw Error.ArgumentNull(nameof(summary)); } - return loadResourceInternal(summary); + /// Finds a resource by matching any of a system's UniqueIds. + /// The unique id of a resource. + /// A resource, or null. + public NamingSystem FindNamingSystem(string uniqueId) + { + if (uniqueId == null) throw Error.ArgumentNull(nameof(uniqueId)); + var summary = GetSummaries().ResolveNamingSystem(uniqueId); + return loadResourceInternal(summary); + } - } + #endregion - #endregion + #region ISummarySource - #region IResourceResolver + /// Returns a list of instances with key information about each FHIR artifact provided by the source. + public IEnumerable ListSummaries() + { + return GetSummaries(); + } - /// Find a resource based on its relative or absolute uri. - /// A resource uri. - public Resource ResolveByUri(string uri) - { - return TryResolveByUri(uri).Value; - } + /// + /// Load the resource from which the specified summary was generated. + /// + /// This implementation annotates returned resource instances with an + /// that captures the value of the property. + /// The extension method + /// provides access to the annotated location. + /// + /// + /// An instance generated by this source. + /// A new instance, or null. + /// + /// The and + /// summary properties allow the source to identify and resolve the artifact. + /// + public Resource LoadBySummary(ArtifactSummary summary) + { + if (summary == null) { throw Error.ArgumentNull(nameof(summary)); } + return loadResourceInternal(summary); - /// Find a (conformance) resource based on its canonical uri. - /// The canonical url of a (conformance) resource. - public Resource ResolveByCanonicalUri(string uri) - { - return TryResolveByCanonicalUri(uri).Value; - } + } - /// - public ResolverResult TryResolveByUri(string uri) - { - if (uri == null) throw Error.ArgumentNull(nameof(uri)); - var summary = GetSummaries().ResolveByUri(uri); - - if(summary is null) - return ResolverException.ArtifactSummaryNoMatch(uri); - - var resource = loadResourceInternal(summary); - - if (resource is null) - return ResolverException.NotFound(); - - return resource; - } + #endregion - /// - public ResolverResult TryResolveByCanonicalUri(string uri) - { - if (uri == null) throw Error.ArgumentNull(nameof(uri)); - var summary = GetSummaries().ResolveByCanonicalUri(uri); - - if(summary is null) - return ResolverException.ArtifactSummaryNoMatch(uri); - - var resource = loadResourceInternal(summary); - - if (resource is null) - return ResolverException.NotFound(); - - return resource; - } + #region IResourceResolver - #endregion + /// Find a resource based on its relative or absolute uri. + /// A resource uri. + public Resource ResolveByUri(string uri) + { + return TryResolveByUri(uri).Value; + } - #region Private members + /// Find a (conformance) resource based on its canonical uri. + /// The canonical url of a (conformance) resource. + public Resource ResolveByCanonicalUri(string uri) + { + return TryResolveByCanonicalUri(uri).Value; + } - /// - /// List all files present in the directory (matching the mask, if given) - /// - private List discoverFiles() - { - var masks = _settings.Masks ?? DirectorySourceSettings.DefaultMasks; // (new[] { "*.*" }); + /// + public ResolverResult TryResolveByUri(string uri) + { + if (uri == null) throw Error.ArgumentNull(nameof(uri)); + var summary = GetSummaries().ResolveByUri(uri); - var contentDirectory = ContentDirectory; + if(summary is null) + return ResolverException.ArtifactSummaryNoMatch(uri); - // Add files present in the content directory - var filePaths = new List(); + var resource = loadResourceInternal(summary); - // [WMR 20170817] NEW - // Safely enumerate files in specified path and subfolders, recursively - filePaths.AddRange(safeGetFiles(contentDirectory, masks, _settings.IncludeSubDirectories)); + if (resource is null) + return ResolverException.NotFound(); - var includes = Includes; - if (includes?.Length > 0) - { - var includeFilter = new FilePatternFilter(includes); - filePaths = includeFilter.Filter(contentDirectory, filePaths).ToList(); - } + return resource; + } - var excludes = Excludes; - if (excludes?.Length > 0) - { - var excludeFilter = new FilePatternFilter(excludes, negate: true); - filePaths = excludeFilter.Filter(contentDirectory, filePaths).ToList(); - } + /// + public ResolverResult TryResolveByCanonicalUri(string uri) + { + if (uri == null) throw Error.ArgumentNull(nameof(uri)); + var summary = GetSummaries().ResolveByCanonicalUri(uri); - return filePaths; - } + if(summary is null) + return ResolverException.ArtifactSummaryNoMatch(uri); + + var resource = loadResourceInternal(summary); + + if (resource is null) + return ResolverException.NotFound(); + + return resource; + } - // [WMR 20170817] + #endregion + + #region Private members + + /// + /// List all files present in the directory (matching the mask, if given) + /// + private List discoverFiles() + { + var masks = _settings.Masks ?? DirectorySourceSettings.DefaultMasks; // (new[] { "*.*" }); + + var contentDirectory = ContentDirectory; + + // Add files present in the content directory + var filePaths = new List(); + + // [WMR 20170817] NEW // Safely enumerate files in specified path and subfolders, recursively - // Ignore files & folders with Hidden and/or System attributes - // Ignore subfolders with insufficient access permissions - // https://stackoverflow.com/a/38959208 - // https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-enumerate-directories-and-files + filePaths.AddRange(safeGetFiles(contentDirectory, masks, _settings.IncludeSubDirectories)); - private static IEnumerable safeGetFiles(string path, IEnumerable masks, bool searchSubfolders) + var includes = Includes; + if (includes?.Length > 0) { - if (File.Exists(path)) - { - return new string[] { path }; - } + var includeFilter = new FilePatternFilter(includes); + filePaths = includeFilter.Filter(contentDirectory, filePaths).ToList(); + } - if (!Directory.Exists(path)) - { - return Enumerable.Empty(); - } + var excludes = Excludes; + if (excludes?.Length > 0) + { + var excludeFilter = new FilePatternFilter(excludes, negate: true); + filePaths = excludeFilter.Filter(contentDirectory, filePaths).ToList(); + } + + return filePaths; + } - // Not necessary; caller prepareFiles() validates the mask - //if (!masks.Any()) - //{ - // return Enumerable.Empty(); - //} + // [WMR 20170817] + // Safely enumerate files in specified path and subfolders, recursively + // Ignore files & folders with Hidden and/or System attributes + // Ignore subfolders with insufficient access permissions + // https://stackoverflow.com/a/38959208 + // https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-enumerate-directories-and-files - Queue folders = new Queue(); - // Use HashSet to remove duplicates; different masks could match same file(s) - HashSet files = new HashSet(); - folders.Enqueue(path); + private static IEnumerable safeGetFiles(string path, IEnumerable masks, bool searchSubfolders) + { + if (File.Exists(path)) + { + return [path]; + } - while (folders.Count != 0) - { - string currentFolder = folders.Dequeue(); - var currentDirInfo = new DirectoryInfo(currentFolder); + if (!Directory.Exists(path)) + { + return []; + } - // local helper function to validate file/folder attributes, exclude system and/or hidden - bool isValid(FileAttributes attr) => (attr & (FileAttributes.System | FileAttributes.Hidden)) == 0; + // Not necessary; caller prepareFiles() validates the mask + //if (!masks.Any()) + //{ + // return Enumerable.Empty(); + //} - // local helper function to filter executables (*.exe, *.dll) - bool isExtensionSafe(string extension) => !ExecutableExtensions.Contains(extension, PathComparer); + var folders = new Queue(); + // Use HashSet to remove duplicates; different masks could match same file(s) + var files = new HashSet(); + folders.Enqueue(path); - foreach (var mask in masks) + while (folders.Count != 0) + { + string currentFolder = folders.Dequeue(); + var currentDirInfo = new DirectoryInfo(currentFolder); + + // local helper function to validate file/folder attributes, exclude system and/or hidden + bool isValid(FileAttributes attr) => (attr & (FileAttributes.System | FileAttributes.Hidden)) == 0; + + // local helper function to filter executables (*.exe, *.dll) + bool isExtensionSafe(string extension) => !ExecutableExtensions.Contains(extension, PathComparer); + + foreach (var mask in masks) + { + try { - try + // https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-enumerate-directories-and-files + // "Although you can immediately enumerate all the files in the subdirectories of a + // parent directory by using the AllDirectories search option provided by the SearchOption + // enumeration, unauthorized access exceptions (UnauthorizedAccessException) may cause the + // enumeration to be incomplete. If these exceptions are possible, you can catch them and + // continue by first enumerating directories and then enumerating files." + + // Explicitly ignore system & hidden files + var curFiles = currentDirInfo.EnumerateFiles(mask, SearchOption.TopDirectoryOnly); + foreach (var file in curFiles) { - // https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-enumerate-directories-and-files - // "Although you can immediately enumerate all the files in the subdirectories of a - // parent directory by using the AllDirectories search option provided by the SearchOption - // enumeration, unauthorized access exceptions (UnauthorizedAccessException) may cause the - // enumeration to be incomplete. If these exceptions are possible, you can catch them and - // continue by first enumerating directories and then enumerating files." - - // Explicitly ignore system & hidden files - var curFiles = currentDirInfo.EnumerateFiles(mask, SearchOption.TopDirectoryOnly); - foreach (var file in curFiles) + // Skip system & hidden files + // Exclude executables (*.exe, *.dll) + if (isValid(file.Attributes) && isExtensionSafe(file.Extension)) { - // Skip system & hidden files - // Exclude executables (*.exe, *.dll) - if (isValid(file.Attributes) && isExtensionSafe(file.Extension)) - { - files.Add(file.FullName); - } + files.Add(file.FullName); } } + } #if DEBUG - catch (Exception ex) - { - // Do Nothing - Debug.WriteLine($"[{nameof(DirectorySource)}.{nameof(harvestSummaries)}] {ex.GetType().Name} while enumerating files in '{currentFolder}':\r\n{ex.Message}"); - } + catch (Exception ex) + { + // Do Nothing + Debug.WriteLine($"[{nameof(DirectorySource)}.{nameof(harvestSummaries)}] {ex.GetType().Name} while enumerating files in '{currentFolder}':\r\n{ex.Message}"); + } #else catch { } #endif - } + } - if (searchSubfolders) + if (searchSubfolders) + { + try { - try + var subFolders = currentDirInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly); + foreach (var subFolder in subFolders) { - var subFolders = currentDirInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly); - foreach (var subFolder in subFolders) + // Skip system & hidden folders + if (isValid(subFolder.Attributes)) { - // Skip system & hidden folders - if (isValid(subFolder.Attributes)) - { - folders.Enqueue(subFolder.FullName); - } + folders.Enqueue(subFolder.FullName); } } + } #if DEBUG - catch (Exception ex) - { - // Do Nothing - Debug.WriteLine($"Error enumerating subfolders of '{currentFolder}': {ex.Message}"); - } + catch (Exception ex) + { + // Do Nothing + Debug.WriteLine($"Error enumerating subfolders of '{currentFolder}': {ex.Message}"); + } #else catch { } #endif - } } - - return files.AsEnumerable(); } - // Internal for unit testing purposes - internal static List ResolveDuplicateFilenames(List allFilenames, DuplicateFilenameResolution preference) - { - var result = new List(); - var xmlOrJson = new List(); + return files.AsEnumerable(); + } - foreach (var filename in allFilenames.Distinct()) - { - if (FhirFileFormats.HasXmlOrJsonExtension(filename)) - xmlOrJson.Add(filename); - else - result.Add(filename); - } + // Internal for unit testing purposes + internal static List ResolveDuplicateFilenames(List allFilenames, DuplicateFilenameResolution preference) + { + var result = new List(); + var xmlOrJson = new List(); + + foreach (var filename in allFilenames.Distinct()) + { + if (FhirFileFormats.HasXmlOrJsonExtension(filename)) + xmlOrJson.Add(filename); + else + result.Add(filename); + } - var groups = xmlOrJson.GroupBy(path => fullPathWithoutExtension(path)); + var groups = xmlOrJson.GroupBy(path => fullPathWithoutExtension(path)); - foreach (var group in groups) + foreach (var group in groups) + { + if (group.Count() == 1 || preference == DuplicateFilenameResolution.KeepBoth) + result.AddRange(group); + else { - if (group.Count() == 1 || preference == DuplicateFilenameResolution.KeepBoth) - result.AddRange(group); + // count must be 2 + var first = group.First(); + if (preference == DuplicateFilenameResolution.PreferXml && FhirFileFormats.HasXmlExtension(first)) + result.Add(first); + else if (preference == DuplicateFilenameResolution.PreferJson && FhirFileFormats.HasJsonExtension(first)) + result.Add(first); else - { - // count must be 2 - var first = group.First(); - if (preference == DuplicateFilenameResolution.PreferXml && FhirFileFormats.HasXmlExtension(first)) - result.Add(first); - else if (preference == DuplicateFilenameResolution.PreferJson && FhirFileFormats.HasJsonExtension(first)) - result.Add(first); - else - result.Add(group.Skip(1).First()); - } + result.Add(group.Skip(1).First()); } + } - return result; + return result; - } + } - private static string fullPathWithoutExtension(string fullPath) => Path.ChangeExtension(fullPath, null); + private static string fullPathWithoutExtension(string fullPath) => Path.ChangeExtension(fullPath, null); - /// Scan all xml files found by prepareFiles and find conformance resources and their id. - private List loadSummaries() - { - var files = discoverFiles(); + /// Scan all xml files found by prepareFiles and find conformance resources and their id. + private List loadSummaries() + { + var files = discoverFiles(); - var settings = _settings; - var uniqueArtifacts = ResolveDuplicateFilenames(files, settings.FormatPreference); - var summaries = harvestSummaries(uniqueArtifacts); + var settings = _settings; + var uniqueArtifacts = ResolveDuplicateFilenames(files, settings.FormatPreference); + var summaries = harvestSummaries(uniqueArtifacts); #if false // [WMR 20180914] OBSOLETE @@ -870,187 +862,178 @@ where g.Count() > 1 // g.Skip(1).Any() } #endif - return summaries; - } + return summaries; + } - List harvestSummaries(List paths) - { - // [WMR 20171023] Note: some files may no longer exist + private List harvestSummaries(List paths) + { + // [WMR 20171023] Note: some files may no longer exist - var cnt = paths.Count; - var scanResult = new List(cnt); - var harvesters = _settings.SummaryDetailsHarvesters; - var factory = _navigatorFactoryDelegate; + var cnt = paths.Count; + var scanResult = new List(cnt); + var harvesters = _settings.SummaryDetailsHarvesters; + var factory = _navigatorFactoryDelegate; - if (!_settings.MultiThreaded) + if (!_settings.MultiThreaded) + { + foreach (var filePath in paths) { - foreach (var filePath in paths) - { - // [WMR 20190403] Fixed: inject navigator factory delegate - var summaries = _summaryGenerator.Generate(filePath, factory, harvesters); + // [WMR 20190403] Fixed: inject navigator factory delegate + var summaries = _summaryGenerator.Generate(filePath, factory, harvesters); - // [WMR 20180423] Generate may return null, e.g. if specified file has unknown extension - if (summaries != null) - { - scanResult.AddRange(summaries); - } + // [WMR 20180423] Generate may return null, e.g. if specified file has unknown extension + if (summaries != null) + { + scanResult.AddRange(summaries); } } - else + } + else + { + // Optimization: use Task.Parallel.ForEach to process files in parallel + // More efficient then creating task per file (esp. if many files) + // + // For netstandard13, add NuGet package System.Threading.Tasks.Parallel + // + // + // + // + // + // TODO: + // - Support TimeOut + // - Support CancellationToken (how to inject?) + + // Pre-allocate results array, one entry per file + // Each entry receives a list with summaries harvested from a single file (Bundles return 0..*) + var summaries = new List[cnt]; + try { - // Optimization: use Task.Parallel.ForEach to process files in parallel - // More efficient then creating task per file (esp. if many files) - // - // For netstandard13, add NuGet package System.Threading.Tasks.Parallel - // - // - // - // - // - // TODO: - // - Support TimeOut - // - Support CancellationToken (how to inject?) - - // Pre-allocate results array, one entry per file - // Each entry receives a list with summaries harvested from a single file (Bundles return 0..*) - var summaries = new List[cnt]; - try - { - // Process files in parallel - var loopResult = Tasks.Parallel.For(0, cnt, - // new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount }, - i => - { - // Harvest summaries from single file - // Save each result to a separate array entry (no locking required) - // [WMR 20190403] Fixed: inject navigator factory delegate - summaries[i] = _summaryGenerator.Generate(paths[i], factory, harvesters); - }); - } - catch (AggregateException aex) - { - // ArtifactSummaryHarvester.HarvestAll catches and returns exceptions using ArtifactSummary.FromException - // However Parallel.For may still throw, e.g. due to time out or cancel + // Process files in parallel + Tasks.Parallel.For(0, cnt, + // new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount }, + i => + { + // Harvest summaries from single file + // Save each result to a separate array entry (no locking required) + // [WMR 20190403] Fixed: inject navigator factory delegate + summaries[i] = _summaryGenerator.Generate(paths[i], factory, harvesters); + }); + } + catch (AggregateException aex) + { + // ArtifactSummaryHarvester.HarvestAll catches and returns exceptions using ArtifactSummary.FromException + // However Parallel.For may still throw, e.g. due to time out or cancel - // var isCanceled = ex.InnerExceptions.OfType().Any(); - Debug.WriteLine($"[{nameof(DirectorySource)}.{nameof(harvestSummaries)}] {aex.GetType().Name}: {aex.Message}" - + aex.InnerExceptions?.Select(ix => $"\r\n\t{ix.GetType().Name}: {ix.Message}")); + // var isCanceled = ex.InnerExceptions.OfType().Any(); + Debug.WriteLine($"[{nameof(DirectorySource)}.{nameof(harvestSummaries)}] {aex.GetType().Name}: {aex.Message}" + + aex.InnerExceptions?.Select(ix => $"\r\n\t{ix.GetType().Name}: {ix.Message}")); - // [WMR 20171023] Return exceptions via ArtifactSummary.FromException - // Or unwrap all inner exceptions? - // scanResult.Add(ArtifactSummary.FromException(aex)); - scanResult.AddRange(aex.InnerExceptions.Select(ArtifactSummary.FromException)); - } - // Aggregate completed results into single list - scanResult.AddRange(summaries.SelectMany(r => r ?? Enumerable.Empty())); + // [WMR 20171023] Return exceptions via ArtifactSummary.FromException + // Or unwrap all inner exceptions? + // scanResult.Add(ArtifactSummary.FromException(aex)); + scanResult.AddRange(aex.InnerExceptions.Select(ArtifactSummary.FromException)); } + // Aggregate completed results into single list + scanResult.AddRange(summaries.SelectMany(r => r ?? Enumerable.Empty())); + } + + return scanResult; + } - return scanResult; + /// Returns null if the specified equals null. + private T loadResourceInternal(ArtifactSummary summary) where T : Resource + { + if (summary == null) { return null; } + + // File path of the containing resource file (could be a Bundle) + var origin = summary.Origin; + if (string.IsNullOrEmpty(origin)) + { + throw Error.Argument($"Unable to load resource from summary. The '{nameof(ArtifactSummary.Origin)}' information is unavailable."); } - /// Returns null if the specified equals null. - T loadResourceInternal(ArtifactSummary summary) where T : Resource + var pos = summary.Position; + if (string.IsNullOrEmpty(pos)) { - if (summary == null) { return null; } + throw Error.Argument($"Unable to load resource from summary. The '{nameof(ArtifactSummary.Position)}' information is unavailable."); + } - // File path of the containing resource file (could be a Bundle) - var origin = summary.Origin; - if (string.IsNullOrEmpty(origin)) - { - throw Error.Argument($"Unable to load resource from summary. The '{nameof(ArtifactSummary.Origin)}' information is unavailable."); - } + // Always use the current Xml/Json parser settings + var factory = getNavigatorStreamFactory(); - var pos = summary.Position; - if (string.IsNullOrEmpty(pos)) - { - throw Error.Argument($"Unable to load resource from summary. The '{nameof(ArtifactSummary.Position)}' information is unavailable."); - } + // Also use the current PoCo parser settings + var pocoSettings = PocoBuilderSettings.CreateDefault(); + if (_settings.ParserSettings is { } ps) + pocoSettings.CopyFrom(ps); - // Always use the current Xml/Json parser settings - var factory = GetNavigatorStreamFactory(); + using var navStream = factory.Create(origin); - // Also use the current PoCo parser settings - var pocoSettings = PocoBuilderSettings.CreateDefault(); - if (_settings.ParserSettings is { } ps) - pocoSettings.CopyFrom(ps); + // Handle exceptions & null return values? + // e.g. file may have been deleted/renamed since last scan - T result = null; + // Advance stream to the target resource (e.g. specific Bundle entry) + if (navStream == null || !navStream.Seek(pos)) return null; - using (var navStream = factory.Create(origin)) - { - // Handle exceptions & null return values? - // e.g. file may have been deleted/renamed since last scan - - // Advance stream to the target resource (e.g. specific Bundle entry) - if (navStream != null && navStream.Seek(pos)) - { - // Create navigator for the target resource - // Current property uses the specified Xml/JsonParsingSettings for parsing - var nav = navStream.Current; - if (nav != null) - { - // Parse target resource from navigator - result = nav.ToPoco(pocoSettings); + // Create navigator for the target resource + // Current property uses the specified Xml/JsonParsingSettings for parsing + var nav = navStream.Current; + if (nav == null) return null; - // Add origin annotation - result?.SetOrigin(origin); - } - } - } + // Parse target resource from navigator + T result = nav.ToPoco(pocoSettings); - return result; - } + // Add origin annotation + result.SetOrigin(origin); - /// Return instance, updated with current Xml/Json parser settings. - ConfigurableNavigatorStreamFactory GetNavigatorStreamFactory() - { - var settings = _settings; - var factory = _navigatorFactory; - settings.XmlParserSettings.CopyTo(factory.XmlParsingSettings); - settings.JsonParserSettings.CopyTo(factory.JsonParsingSettings); - return factory; - } + return result; + } - #endregion + /// Return instance, updated with current Xml/Json parser settings. + private ConfigurableNavigatorStreamFactory getNavigatorStreamFactory() + { + _settings.XmlParserSettings?.CopyTo(_navigatorFactory.XmlParsingSettings); + _settings.JsonParserSettings?.CopyTo(_navigatorFactory.JsonParsingSettings); + return _navigatorFactory; + } - #region Protected members + #endregion - /// - /// Gets a list of instances for files in the specified . - /// The artifact summaries are loaded on demand. - /// - protected List GetSummaries() => _lazyArtifactSummaries.Value; + #region Protected members - // Note: Need distinct for bundled resources + /// + /// Gets a list of instances for files in the specified . + /// The artifact summaries are loaded on demand. + /// + protected List GetSummaries() => _lazyArtifactSummaries.Value; - /// - /// Enumerate distinct file paths in the specified . - /// The underlying artifact summaries are loaded on demand. - /// - protected IEnumerable GetFilePaths() => GetSummaries().Select(s => s.Origin).Distinct(); + // Note: Need distinct for bundled resources - /// - /// Enumerate distinct file names in the specified . - /// The underlying artifact summaries are loaded on demand. - /// - protected IEnumerable GetFileNames() => GetSummaries().Select(s => Path.GetFileName(s.Origin)).Distinct(); + /// + /// Enumerate distinct file paths in the specified . + /// The underlying artifact summaries are loaded on demand. + /// + protected IEnumerable GetFilePaths() => GetSummaries().Select(s => s.Origin).Distinct(); - public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); + /// + /// Enumerate distinct file names in the specified . + /// The underlying artifact summaries are loaded on demand. + /// + protected IEnumerable GetFileNames() => GetSummaries().Select(s => Path.GetFileName(s.Origin)).Distinct(); - public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); - public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); + public Tasks.Task ResolveByUriAsync(string uri) => Tasks.Task.FromResult(ResolveByUri(uri)); - public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); + public Tasks.Task ResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(ResolveByCanonicalUri(uri)); + public Tasks.Task TryResolveByUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByUri(uri)); - #endregion + public Tasks.Task TryResolveByCanonicalUriAsync(string uri) => Tasks.Task.FromResult(TryResolveByCanonicalUri(uri)); - // Allow derived classes to override - // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - internal protected virtual string DebuggerDisplay - => $"{GetType().Name} for '{ContentDirectory}' : '{Mask}'" - + (IncludeSubDirectories ? " (with subdirs)" : null) - + (_lazyArtifactSummaries.IsValueCreated ? $" {_lazyArtifactSummaries.Value.Count} resources" : " (summaries not yet loaded)"); - } + #endregion + // Allow derived classes to override + // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + internal protected virtual string DebuggerDisplay + => $"{GetType().Name} for '{ContentDirectory}' : '{Mask}'" + + (IncludeSubDirectories ? " (with subdirs)" : null) + + (_lazyArtifactSummaries.IsValueCreated ? $" {_lazyArtifactSummaries.Value.Count} resources" : " (summaries not yet loaded)"); } \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs index ff84bc79e2..46ae6d79f2 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/DirectorySourceSettings.cs @@ -6,9 +6,10 @@ * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE */ +#nullable enable + using Hl7.Fhir.Serialization; using Hl7.Fhir.Specification.Summary; -using Hl7.Fhir.Support; using Hl7.Fhir.Utility; using System; using System.IO; @@ -23,7 +24,7 @@ public sealed class DirectorySourceSettings public const DirectorySource.DuplicateFilenameResolution DefaultFormatPreference = DirectorySource.DuplicateFilenameResolution.PreferXml; /// Default value of the configuration setting (*.*) - public readonly static string[] DefaultMasks = new[] { "*.*" }; + public readonly static string[] DefaultMasks = ["*.*"]; /// Creates a new instance with default property values. public static DirectorySourceSettings CreateDefault() => new DirectorySourceSettings(); @@ -60,20 +61,24 @@ public void CopyTo(DirectorySourceSettings other) // [WMR 20181025] Clone state other.IncludeSubDirectories = this.IncludeSubDirectories; - other.Masks = (string[])this.Masks.Clone(); - other.Includes = (string[])this.Includes?.Clone(); - other.Excludes = (string[])this.Excludes?.Clone(); + other.Masks = (string[]?)this.Masks?.Clone(); + other.Includes = (string[]?)this.Includes?.Clone(); + other.Excludes = (string[]?)this.Excludes?.Clone(); other.FormatPreference = this.FormatPreference; other.MultiThreaded = this.MultiThreaded; - other.SummaryDetailsHarvesters = (ArtifactSummaryHarvester[])this.SummaryDetailsHarvesters?.Clone(); + other.SummaryDetailsHarvesters = (ArtifactSummaryHarvester[]?)this.SummaryDetailsHarvesters?.Clone(); other.ExcludeSummariesForUnknownArtifacts = this.ExcludeSummariesForUnknownArtifacts; - other.ParserSettings = this.ParserSettings with { }; - other.XmlParserSettings = new FhirXmlParsingSettings(this.XmlParserSettings); - other.JsonParserSettings = new FhirJsonParsingSettings(this.JsonParserSettings); + other.ParserSettings = this.ParserSettings is not null ? this.ParserSettings with { } : null; + other.XmlParserSettings = this.XmlParserSettings is not null + ? new FhirXmlParsingSettings(this.XmlParserSettings) + : null; + other.JsonParserSettings = this.JsonParserSettings is not null + ? new FhirJsonParsingSettings(this.JsonParserSettings) + : null; } /// Creates a new object that is a copy of the current instance. - public DirectorySourceSettings Clone() => new DirectorySourceSettings(this); + public DirectorySourceSettings Clone() => new(this); /// Returns the default content directory of the . public static string SpecificationDirectory @@ -142,11 +147,15 @@ public static string SpecificationDirectory /// public string Mask { - get => String.Join("|", Masks); - set { Masks = SplitMask(value); } + get => String.Join("|", Masks ?? []); + set { Masks = splitMask(value); } } - static string[] SplitMask(string mask) => mask?.Split('|').Select(s => s.Trim()).Where(s => !String.IsNullOrEmpty(s)).ToArray(); + private static string[]? splitMask(string? mask) => mask? + .Split('|') + .Select(s => s.Trim()) + .Where(s => !String.IsNullOrEmpty(s)) + .ToArray(); /// /// Gets or sets an array of search strings to match against the names of files in the content directory. @@ -175,7 +184,7 @@ public string Mask /// /// Masks = new string[] { "v2*.*", "*.StructureDefinition.*" }; /// - public string[] Masks { get; set; } = DefaultMasks; + public string[]? Masks { get; set; } = DefaultMasks; /// /// Gets or sets an array of search strings to match against the names of subdirectories of the content directory. @@ -204,7 +213,7 @@ public string Mask /// /// Includes = new string[] { "profiles/**/*", "**/valuesets" }; /// - public string[] Includes { get; set; } + public string[]? Includes { get; set; } /// /// Gets or sets an array of search strings to match against the names of subdirectories of the content directory. @@ -233,7 +242,7 @@ public string Mask /// /// Excludes = new string[] { "profiles/**/old", "temp/**/*" }; /// - public string[] Excludes { get; set; } + public string[]? Excludes { get; set; } /// Gets or sets a value that determines how to process duplicate files with multiple serialization formats. /// The default value is . @@ -268,7 +277,7 @@ public string Mask /// A custom delegate array may include one or more of the default harvesters. /// /// - public ArtifactSummaryHarvester[] SummaryDetailsHarvesters { get; set; } + public ArtifactSummaryHarvester[]? SummaryDetailsHarvesters { get; set; } // [WMR 20180813] NEW @@ -293,10 +302,10 @@ public string Mask /// Never returns null. Assigning null reverts back to default settings. /// /// A instance. - public ParserSettings ParserSettings + public ParserSettings? ParserSettings { get => _parserSettings; - set => _parserSettings = value ?? new ParserSettings(); + set => _parserSettings = value ?? new ParserSettings().UsingMode(DeserializationMode.Recoverable); } /// @@ -304,7 +313,7 @@ public ParserSettings ParserSettings /// Never returns null. Assigning null reverts back to default settings. /// /// A instance. - public FhirXmlParsingSettings XmlParserSettings + public FhirXmlParsingSettings? XmlParserSettings { get => _xmlParserSettings; set => _xmlParserSettings = value?.Clone() ?? FhirXmlParsingSettings.CreateDefault(); @@ -316,13 +325,11 @@ public FhirXmlParsingSettings XmlParserSettings /// Never returns null. Assigning null reverts back to default settings. /// /// A instance. - public FhirJsonParsingSettings JsonParserSettings + public FhirJsonParsingSettings? JsonParserSettings { get => _jsonParserSettings; set => _jsonParserSettings = value?.Clone() ?? FhirJsonParsingSettings.CreateDefault(); } - - } } \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummary.cs b/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummary.cs index 143e3a2486..69640e5ee8 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummary.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummary.cs @@ -18,169 +18,168 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; -namespace Hl7.Fhir.Specification.Source +namespace Hl7.Fhir.Specification.Source; + +// Note: +// 1. ArtifactSummaryGenerator creates new (mutable) ArtifactSummaryPropertyBag +// 2. ArtifactSummaryGenerator calls the available ArtifactSummaryHarvester delegates +// to harvest artifact summary information and add it to the property bag +// 3. ArtifactSummaryGenerator creates a new (read-only) ArtifactSummary instance +// from the initialized property bag and returns it to the caller. +// This way, harvested property values are protected against modification by callers. + +/// Represents summary information harvested from a FHIR artifact. +/// +/// Created by the class from +/// a property bag containing the harvested summary information. +/// +/// Implements the interface +/// to support extension methods for accessing specific harvested properties. +/// +/// +[DebuggerDisplay(@"\{{DebuggerDisplay,nq}}")] +public class ArtifactSummary : IArtifactSummaryPropertyBag { - // Note: - // 1. ArtifactSummaryGenerator creates new (mutable) ArtifactSummaryPropertyBag - // 2. ArtifactSummaryGenerator calls the available ArtifactSummaryHarvester delegates - // to harvest artifact summary information and add it to the property bag - // 3. ArtifactSummaryGenerator creates a new (read-only) ArtifactSummary instance - // from the initialized property bag and returns it to the caller. - // This way, harvested property values are protected against modification by callers. - - /// Represents summary information harvested from a FHIR artifact. - /// - /// Created by the class from - /// a property bag containing the harvested summary information. - /// - /// Implements the interface - /// to support extension methods for accessing specific harvested properties. - /// - /// - [DebuggerDisplay(@"\{{DebuggerDisplay,nq}}")] - public class ArtifactSummary : IArtifactSummaryPropertyBag - { - /// Returns an empty instance. - public static ArtifactSummary Empty => new ArtifactSummary(ArtifactSummaryPropertyBag.Empty); + /// Returns an empty instance. + public static ArtifactSummary Empty => new ArtifactSummary(ArtifactSummaryPropertyBag.Empty); - // Note: omit leading underscore to be CLS compliant - protected readonly IArtifactSummaryPropertyBag properties; + // Note: omit leading underscore to be CLS compliant + protected readonly IArtifactSummaryPropertyBag properties; - #region Factory + #region Factory - /// Create a new instance from the specified exception. - /// An exception that occured while harvesting artifact summary information. - public static ArtifactSummary FromException(Exception error) => FromException(error, null); + /// Create a new instance from the specified exception. + /// An exception that occured while harvesting artifact summary information. + public static ArtifactSummary FromException(Exception error) => FromException(error, null); - /// Create a new instance from the specified exception. - /// An exception that occured while harvesting artifact summary information. - /// The original location of the target artifact. - public static ArtifactSummary FromException(Exception error, string? origin) + /// Create a new instance from the specified exception. + /// An exception that occured while harvesting artifact summary information. + /// The original location of the target artifact. + public static ArtifactSummary FromException(Exception error, string? origin) + { + if (error == null) { throw Errors.ArgumentNull(nameof(error)); } + // Create a new (default) ArtifactSummaryPropertyBag to store the artifact origin + var properties = new ArtifactSummaryPropertyBag(); + if (!string.IsNullOrEmpty(origin)) { - if (error == null) { throw Errors.ArgumentNull(nameof(error)); } - // Create a new (default) ArtifactSummaryPropertyBag to store the artifact origin - var properties = new ArtifactSummaryPropertyBag(); - if (!string.IsNullOrEmpty(origin)) - { - properties[ArtifactSummaryProperties.OriginKey] = origin; - } - return new ArtifactSummary(properties, error); + properties[ArtifactSummaryProperties.OriginKey] = origin; } + return new ArtifactSummary(properties, error); + } - #endregion + #endregion - #region ctor + #region ctor - /// Create a new instance from a set of harvested artifact summary properties. - /// A property bag with harvested artifact summary information. - public ArtifactSummary(IArtifactSummaryPropertyBag properties) : this(properties, null) { } + /// Create a new instance from a set of harvested artifact summary properties. + /// A property bag with harvested artifact summary information. + public ArtifactSummary(IArtifactSummaryPropertyBag properties) : this(properties, null) { } - /// - /// Create a new instance from a set of harvested artifact summary properties - /// and a runtime exception that occured during harvesting. - /// - public ArtifactSummary(IArtifactSummaryPropertyBag properties, Exception? error) + /// + /// Create a new instance from a set of harvested artifact summary properties + /// and a runtime exception that occured during harvesting. + /// + public ArtifactSummary(IArtifactSummaryPropertyBag properties, Exception? error) + { + this.properties = properties ?? throw Errors.ArgumentNull(nameof(properties)); + this.Error = error; + + if (ResourceTypeName != null) { - this.properties = properties ?? throw Errors.ArgumentNull(nameof(properties)); - this.Error = error; - - if (ResourceTypeName != null) - { - ResourceType = ModelInfo.FhirTypeNameToResourceType(ResourceTypeName); - } + ResourceType = ModelInfo.FhirTypeNameToResourceType(ResourceTypeName); } + } - #endregion + #endregion - #region Properties + #region Properties - /// Returns information about errors that occured while generating the artifact summary. - public Exception? Error { get; } + /// Returns information about errors that occured while generating the artifact summary. + public Exception? Error { get; } - /// Indicates if any errors occured while generating the artifact summary. - /// If true, then the property returns detailed error information. - public bool IsFaulted => Error != null; // cf. Task + /// Indicates if any errors occured while generating the artifact summary. + /// If true, then the property returns detailed error information. + public bool IsFaulted => Error != null; // cf. Task - /// Indicates if the summary describes a valid FHIR resource. - /// Returns true if is known (not null), or false otherwise. - public bool IsFhirResource => ResourceType != null; + /// Indicates if the summary describes a valid FHIR resource. + /// Returns true if is known (not null), or false otherwise. + public bool IsFhirResource => ResourceType != null; - /// Gets the original location of the associated artifact. - public string Origin => properties.GetOrigin(); + /// Gets the original location of the associated artifact. + public string? Origin => properties.GetOrigin(); - /// Gets the size of the original artifact file. - public long? FileSize => properties.GetFileSize(); + /// Gets the size of the original artifact file. + public long? FileSize => properties.GetFileSize(); - /// Gets the last modified date of the original artifact file. - public DateTimeOffset? LastModified => properties.GetLastModified(); + /// Gets the last modified date of the original artifact file. + public DateTimeOffset? LastModified => properties.GetLastModified(); - /// - /// Get a string value that represents the artifact serialization format, - /// as defined by the class, if available. - /// - public string SerializationFormat => properties.GetSerializationFormat(); + /// + /// Get a string value that represents the artifact serialization format, + /// as defined by the class, if available. + /// + public string? SerializationFormat => properties.GetSerializationFormat(); - /// - /// Gets an opaque value that represents the position of the artifact within the container. - /// Allows the to retrieve and deserialize the associated artifact. - /// - public string Position => properties.GetPosition(); + /// + /// Gets an opaque value that represents the position of the artifact within the container. + /// Allows the to retrieve and deserialize the associated artifact. + /// + public string? Position => properties.GetPosition(); - /// Gets the type name of the resource. - public string? ResourceTypeName => properties.GetTypeName(); + /// Gets the type name of the resource. + public string? ResourceTypeName => properties.GetTypeName(); - /// Gets the type of the resource, parsed from the original value, or null. - public ResourceType? ResourceType { get; } + /// Gets the type of the resource, parsed from the original value, or null. + public ResourceType? ResourceType { get; } - /// Gets the resource uri. - /// The generates virtual uri values for resources that are not bundle entries. - public string ResourceUri => properties.GetResourceUri(); + /// Gets the resource uri. + /// The generates virtual uri values for resources that are not bundle entries. + public string? ResourceUri => properties.GetResourceUri(); - /// Returns true if the summary describes a Bundle entry resource. - public bool IsBundleEntry => properties.IsBundleEntry(); + /// Returns true if the summary describes a Bundle entry resource. + public bool IsBundleEntry => properties.IsBundleEntry(); - #endregion + #endregion - #region IEnumerable + #region IEnumerable - /// Returns an enumerator that iterates through the summary properties. - public IEnumerator> GetEnumerator() => properties.GetEnumerator(); + /// Returns an enumerator that iterates through the summary properties. + public IEnumerator> GetEnumerator() => properties.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => properties.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => properties.GetEnumerator(); - #endregion + #endregion - #region IReadOnlyCollection + #region IReadOnlyCollection - /// Gets the number of harvested property values. - public int Count => properties.Count; + /// Gets the number of harvested property values. + public int Count => properties.Count; - #endregion + #endregion - #region IReadOnlyDictionary + #region IReadOnlyDictionary - /// Gets the property value associated with the specified property key. - public object this[string key] => properties[key]; + /// Gets the property value associated with the specified property key. + public object this[string key] => properties[key]; - /// Gets a collection of property keys. - public IEnumerable Keys => properties.Keys; + /// Gets a collection of property keys. + public IEnumerable Keys => properties.Keys; - /// Gets a collection of property values. - public IEnumerable Values => properties.Values; + /// Gets a collection of property values. + public IEnumerable Values => properties.Values; - /// Determines wether the summary contains a property value for the specified property key. - public bool ContainsKey(string key) => properties.ContainsKey(key); + /// Determines wether the summary contains a property value for the specified property key. + public bool ContainsKey(string key) => properties.ContainsKey(key); - /// Gets the property value associated with the specified property key. - public bool TryGetValue(string key, [NotNullWhen(true)] out object? value) => properties.TryGetValue(key, out value); + /// Gets the property value associated with the specified property key. + public bool TryGetValue(string key, [NotNullWhen(true)] out object? value) => properties.TryGetValue(key, out value); - #endregion + #endregion - // Allow derived classes to override - // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - protected virtual string DebuggerDisplay - => $"{GetType().Name} for {ResourceTypeName} | Origin: {Origin}" - + (IsFaulted ? " | Error: " + Error!.Message : string.Empty); - } + // Allow derived classes to override + // http://blogs.msdn.com/b/jaredpar/archive/2011/03/18/debuggerdisplay-attribute-best-practices.aspx + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + protected virtual string DebuggerDisplay + => $"{GetType().Name} for {ResourceTypeName} | Origin: {Origin}" + + (IsFaulted ? " | Error: " + Error!.Message : string.Empty); } \ No newline at end of file diff --git a/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummaryHarvesters.cs b/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummaryHarvesters.cs index 394edd8c27..d11ee8b29f 100644 --- a/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummaryHarvesters.cs +++ b/src/Hl7.Fhir.STU3/Specification/Source/Summary/ArtifactSummaryHarvesters.cs @@ -6,6 +6,8 @@ * available at https://github.com/FirelyTeam/firely-net-sdk/blob/master/LICENSE */ +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics; @@ -17,448 +19,448 @@ using Hl7.Fhir.Utility; // Expose low-level interfaces from a separate child namespace, to prevent pollution -namespace Hl7.Fhir.Specification.Source -{ - // Define a set of default ArtifactSummaryHarvester delegate implementations, - // with property keys and helper extension methods for accessing the harvested properties. +namespace Hl7.Fhir.Specification.Source; - // DSTU2 Specific! - /// For accessing common artifact summary properties stored in an . - /// - /// The creates an - /// for each artifact and adds a set of common summary properties, independent of the resource type. - /// This class provides property keys and extension methods to access the common properties. - /// - public static class ArtifactSummaryProperties - { - public static readonly string OriginKey = "Origin"; - public static readonly string FileSizeKey = "Size"; - public static readonly string LastModifiedKey = "LastModified"; - public static readonly string SerializationFormatKey = "Format"; - public static readonly string PositionKey = "Position"; - public static readonly string TypeNameKey = "TypeName"; - public static readonly string ResourceUriKey = "Uri"; - public static readonly string IsBundleEntryKey = "IsBundleEntry"; - - /// Try to retrieve the property value for the specified key. - /// An artifact summary property bag. - /// A property key. - /// An object value, or null. - public static object GetValueOrDefault(this IArtifactSummaryPropertyBag properties, string key) - => properties.TryGetValue(key, out object result) ? result : null; - - /// Try to retrieve the property value for the specified key. - /// An artifact summary property bag. - /// A property key. - /// The type of the property value. - /// A value of type , or null. - public static T GetValueOrDefault(this IArtifactSummaryPropertyBag properties, string key) - where T : class - => properties.GetValueOrDefault(key) as T; - - /// Get the Origin property value from the specified artifact summary property bag, if available. - public static string GetOrigin(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(OriginKey); - - internal static void SetOrigin(this ArtifactSummaryPropertyBag properties, string value) - { - properties[OriginKey] = value; - } +// Define a set of default ArtifactSummaryHarvester delegate implementations, +// with property keys and helper extension methods for accessing the harvested properties. - /// Get the Size property value from the specified artifact summary property bag, if available. - public static long? GetFileSize(this IArtifactSummaryPropertyBag properties) - => (long?)properties.GetValueOrDefault(FileSizeKey); +// DSTU2 Specific! - internal static void SetFileSize(this ArtifactSummaryPropertyBag properties, long value) - { - properties[FileSizeKey] = value; - } +/// For accessing common artifact summary properties stored in an . +/// +/// The creates an +/// for each artifact and adds a set of common summary properties, independent of the resource type. +/// This class provides property keys and extension methods to access the common properties. +/// +public static class ArtifactSummaryProperties +{ + public static readonly string OriginKey = "Origin"; + public static readonly string FileSizeKey = "Size"; + public static readonly string LastModifiedKey = "LastModified"; + public static readonly string SerializationFormatKey = "Format"; + public static readonly string PositionKey = "Position"; + public static readonly string TypeNameKey = "TypeName"; + public static readonly string ResourceUriKey = "Uri"; + public static readonly string IsBundleEntryKey = "IsBundleEntry"; + + /// Try to retrieve the property value for the specified key. + /// An artifact summary property bag. + /// A property key. + /// An object value, or null. + public static object? GetValueOrDefault(this IArtifactSummaryPropertyBag properties, string key) + => CollectionExtensions.GetValueOrDefault(properties, key); + + /// Try to retrieve the property value for the specified key. + /// An artifact summary property bag. + /// A property key. + /// The type of the property value. + /// A value of type , or null. + public static T? GetValueOrDefault(this IArtifactSummaryPropertyBag properties, string key) + where T : class + => properties.GetValueOrDefault(key) as T; + + /// Get the Origin property value from the specified artifact summary property bag, if available. + public static string? GetOrigin(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(OriginKey); + + internal static void SetOrigin(this ArtifactSummaryPropertyBag properties, string value) + { + properties[OriginKey] = value; + } - /// Get the LastModified property value from the specified artifact summary property bag, if available. - public static DateTimeOffset? GetLastModified(this IArtifactSummaryPropertyBag properties) - => (DateTimeOffset?)properties.GetValueOrDefault(LastModifiedKey); + /// Get the Size property value from the specified artifact summary property bag, if available. + public static long? GetFileSize(this IArtifactSummaryPropertyBag properties) + => (long?)properties.GetValueOrDefault(FileSizeKey); - internal static void SetLastModified(this ArtifactSummaryPropertyBag properties, DateTimeOffset value) - { - properties[LastModifiedKey] = value; - } + internal static void SetFileSize(this ArtifactSummaryPropertyBag properties, long value) + { + properties[FileSizeKey] = value; + } - /// - /// Get a string value from the specified artifact summary property bag that represents the artifact - /// serialization format, as defined by the class, if available. - /// - public static string GetSerializationFormat(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(SerializationFormatKey); + /// Get the LastModified property value from the specified artifact summary property bag, if available. + public static DateTimeOffset? GetLastModified(this IArtifactSummaryPropertyBag properties) + => (DateTimeOffset?)properties.GetValueOrDefault(LastModifiedKey); - internal static void SetSerializationFormat(this ArtifactSummaryPropertyBag properties, string value) - { - Debug.Assert(Array.IndexOf(FhirSerializationFormats.All, value) >= 0); - properties[SerializationFormatKey] = value; - } + internal static void SetLastModified(this ArtifactSummaryPropertyBag properties, DateTimeOffset value) + { + properties[LastModifiedKey] = value; + } - /// Get the Position property value from the specified artifact summary property bag, if available. - public static string GetPosition(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(PositionKey); + /// + /// Get a string value from the specified artifact summary property bag that represents the artifact + /// serialization format, as defined by the class, if available. + /// + public static string? GetSerializationFormat(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(SerializationFormatKey); - internal static void SetPosition(this ArtifactSummaryPropertyBag properties, string value) - { - properties[PositionKey] = value; - } + internal static void SetSerializationFormat(this ArtifactSummaryPropertyBag properties, string value) + { + Debug.Assert(Array.IndexOf(FhirSerializationFormats.All, value) >= 0); + properties[SerializationFormatKey] = value; + } - /// Get the TypeName property value from the specified artifact summary property bag, if available. - public static string GetTypeName(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(TypeNameKey); + /// Get the Position property value from the specified artifact summary property bag, if available. + public static string? GetPosition(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(PositionKey); - internal static void SetTypeName(this ArtifactSummaryPropertyBag properties, string value) - { - properties[TypeNameKey] = value; - } + internal static void SetPosition(this ArtifactSummaryPropertyBag properties, string value) + { + properties[PositionKey] = value; + } - /// Get the ResourceUri property value from the specified artifact summary property bag, if available. - public static string GetResourceUri(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(ResourceUriKey); + /// Get the TypeName property value from the specified artifact summary property bag, if available. + public static string? GetTypeName(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(TypeNameKey); - internal static void SetResourceUri(this ArtifactSummaryPropertyBag properties, string value) - { - properties[ResourceUriKey] = value; - } + internal static void SetTypeName(this ArtifactSummaryPropertyBag properties, string value) + { + properties[TypeNameKey] = value; + } - /// Returns true if the summary describes a Bundle entry. - public static bool IsBundleEntry(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(IsBundleEntryKey) is bool b ? b : false; + /// Get the ResourceUri property value from the specified artifact summary property bag, if available. + public static string? GetResourceUri(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(ResourceUriKey); - internal static void SetIsBundleEntry(this ArtifactSummaryPropertyBag properties, bool value) - { - properties[IsBundleEntryKey] = value; - } + internal static void SetResourceUri(this ArtifactSummaryPropertyBag properties, string value) + { + properties[ResourceUriKey] = value; } - /// For harvesting specific summary information from a resource. - public static class NamingSystemSummaryProperties + /// Returns true if the summary describes a Bundle entry. + public static bool IsBundleEntry(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(IsBundleEntryKey) is true; + + internal static void SetIsBundleEntry(this ArtifactSummaryPropertyBag properties, bool value) { - static readonly string NamingSystemTypeName = ResourceType.NamingSystem.GetLiteral(); + properties[IsBundleEntryKey] = value; + } +} + +/// For harvesting specific summary information from a resource. +public static class NamingSystemSummaryProperties +{ + private static readonly string NamingSystemTypeName = ResourceType.NamingSystem.GetLiteral(); - public static readonly string UniqueIdKey = "NamingSystem.uniqueId"; + public static readonly string UniqueIdKey = "NamingSystem.uniqueId"; - /// Determines if the specified instance represents summary information about a resource. - public static bool IsNamingSystemSummary(this IArtifactSummaryPropertyBag properties) - => properties.GetTypeName() == NamingSystemTypeName; + /// Determines if the specified instance represents summary information about a resource. + public static bool IsNamingSystemSummary(this IArtifactSummaryPropertyBag properties) + => properties.GetTypeName() == NamingSystemTypeName; - /// Harvest specific summary information from a resource. - /// true if the current target represents a resource, or false otherwise. - /// The calls this method through a delegate. - public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + /// Harvest specific summary information from a resource. + /// true if the current target represents a resource, or false otherwise. + /// The calls this method through a delegate. + public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + { + if (IsNamingSystemSummary(properties)) { - if (IsNamingSystemSummary(properties)) - { - nav.HarvestValues(properties, UniqueIdKey, "uniqueId", "value"); - return true; - } - return false; + nav.HarvestValues(properties, UniqueIdKey, "uniqueId", "value"); + return true; } + return false; + } - /// Get the NamingSystem.uniqueId property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string[] GetNamingSystemUniqueId(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(UniqueIdKey); + /// Get the NamingSystem.uniqueId property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string[]? GetNamingSystemUniqueId(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(UniqueIdKey); - /// - /// Determines if the current summary properties represent a - /// resource with the specified uniqueId value. - /// - public static bool HasNamingSystemUniqueId(this IArtifactSummaryPropertyBag properties, string uniqueId) + /// + /// Determines if the current summary properties represent a + /// resource with the specified uniqueId value. + /// + public static bool HasNamingSystemUniqueId(this IArtifactSummaryPropertyBag properties, string? uniqueId) + { + if (uniqueId != null) { - if (uniqueId != null) - { - var ids = GetNamingSystemUniqueId(properties); - return ids != null && Array.IndexOf(ids, uniqueId) > -1; - } - return false; + var ids = GetNamingSystemUniqueId(properties); + return ids != null && Array.IndexOf(ids, uniqueId) > -1; } + return false; } +} + +/// For harvesting common summary information from a conformance resource. +public static class ConformanceSummaryProperties +{ + public static readonly string CanonicalUrlKey = "Conformance.url"; + public static readonly string NameKey = "Conformance.name"; + public static readonly string VersionKey = "Conformance.version"; + public static readonly string StatusKey = "Conformance.status"; + + /// Determines if the specified instance represents summary information about a conformance resource. + public static bool IsConformanceSummary(this IArtifactSummaryPropertyBag properties) + => properties.GetTypeName() is { } typeName && ModelInfo.IsConformanceResource(typeName); - /// For harvesting common summary information from a conformance resource. - public static class ConformanceSummaryProperties + /// Harvest common summary information from a conformance resource. + /// true if the current target represents a conformance resource, or false otherwise. + /// + /// The calls this method through a delegate. + /// Also called directly by other delegates to harvest summary + /// information common to all conformance resources, before harvesting any additional type specific + /// information. + /// + /// + /// + public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) { - public static readonly string CanonicalUrlKey = "Conformance.url"; - public static readonly string NameKey = "Conformance.name"; - public static readonly string VersionKey = "Conformance.version"; - public static readonly string StatusKey = "Conformance.status"; - - /// Determines if the specified instance represents summary information about a conformance resource. - public static bool IsConformanceSummary(this IArtifactSummaryPropertyBag properties) - => properties.GetTypeName() is { } typeName && ModelInfo.IsConformanceResource(typeName); - - /// Harvest common summary information from a conformance resource. - /// true if the current target represents a conformance resource, or false otherwise. - /// - /// The calls this method through a delegate. - /// Also called directly by other delegates to harvest summary - /// information common to all conformance resources, before harvesting any additional type specific - /// information. - /// - /// - /// - public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + if (IsConformanceSummary(properties)) { - if (IsConformanceSummary(properties)) - { - nav.HarvestValue(properties, CanonicalUrlKey, "url"); - nav.HarvestValue(properties, VersionKey, "version"); - nav.HarvestValue(properties, NameKey, "name"); - nav.HarvestValue(properties, StatusKey, "status"); - return true; - } - return false; + nav.HarvestValue(properties, CanonicalUrlKey, "url"); + nav.HarvestValue(properties, VersionKey, "version"); + nav.HarvestValue(properties, NameKey, "name"); + nav.HarvestValue(properties, StatusKey, "status"); + return true; } - - /// Get the canonical url property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of conformance resources. - public static string GetConformanceCanonicalUrl(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(CanonicalUrlKey); - - /// Get the version property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of conformance resources. - public static string GetConformanceVersion(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(VersionKey); - - /// Get the name property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of conformance resources. - public static string GetConformanceName(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(NameKey); - - /// Get the status property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of conformance resources. - public static string GetConformanceStatus(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(StatusKey); + return false; } - /// For harvesting specific summary information from a resource. - public static class StructureDefinitionSummaryProperties - { - static readonly string StructureDefinitionTypeName = ResourceType.StructureDefinition.GetLiteral(); + /// Get the canonical url property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of conformance resources. + public static string? GetConformanceCanonicalUrl(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(CanonicalUrlKey); + + /// Get the version property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of conformance resources. + public static string? GetConformanceVersion(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(VersionKey); + + /// Get the name property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of conformance resources. + public static string? GetConformanceName(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(NameKey); + + /// Get the status property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of conformance resources. + public static string? GetConformanceStatus(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(StatusKey); +} + +/// For harvesting specific summary information from a resource. +public static class StructureDefinitionSummaryProperties +{ + private static readonly string StructureDefinitionTypeName = ResourceType.StructureDefinition.GetLiteral(); - public static readonly string FhirVersionKey = "StructureDefinition.fhirVersion"; - public static readonly string KindKey = "StructureDefinition.kind"; - public static readonly string TypeKey = "StructureDefinition.type"; - public static readonly string ContextTypeKey = "StructureDefinition.contextType"; - public static readonly string ContextKey = "StructureDefinition.context"; - public static readonly string BaseDefinitionKey = "StructureDefinition.baseDefinition"; - public static readonly string DerivationKey = "StructureDefinition.derivation"; + public static readonly string FhirVersionKey = "StructureDefinition.fhirVersion"; + public static readonly string KindKey = "StructureDefinition.kind"; + public static readonly string TypeKey = "StructureDefinition.type"; + public static readonly string ContextTypeKey = "StructureDefinition.contextType"; + public static readonly string ContextKey = "StructureDefinition.context"; + public static readonly string BaseDefinitionKey = "StructureDefinition.baseDefinition"; + public static readonly string DerivationKey = "StructureDefinition.derivation"; - public static readonly string FmmExtensionUrl = @"http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm"; - public static readonly string MaturityLevelKey = "StructureDefinition.maturityLevel"; + public static readonly string FmmExtensionUrl = @"http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm"; + public static readonly string MaturityLevelKey = "StructureDefinition.maturityLevel"; - public static readonly string WgExtensionUrl = @"http://hl7.org/fhir/StructureDefinition/structuredefinition-wg"; - public static readonly string WorkingGroupKey = "StructureDefinition.workingGroup"; + public static readonly string WgExtensionUrl = @"http://hl7.org/fhir/StructureDefinition/structuredefinition-wg"; + public static readonly string WorkingGroupKey = "StructureDefinition.workingGroup"; - public static readonly string RootDefinitionKey = "StructureDefinition.rootDefinition"; + public static readonly string RootDefinitionKey = "StructureDefinition.rootDefinition"; - /// Determines if the specified instance represents summary information about a resource. - public static bool IsStructureDefinitionSummary(this IArtifactSummaryPropertyBag properties) - => properties.GetTypeName() == StructureDefinitionTypeName; + /// Determines if the specified instance represents summary information about a resource. + public static bool IsStructureDefinitionSummary(this IArtifactSummaryPropertyBag properties) + => properties.GetTypeName() == StructureDefinitionTypeName; - /// Harvest specific summary information from a resource. - /// true if the current target represents a resource, or false otherwise. - /// The calls this method from a delegate. - public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + /// Harvest specific summary information from a resource. + /// true if the current target represents a resource, or false otherwise. + /// The calls this method from a delegate. + public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + { + if (IsStructureDefinitionSummary(properties)) { - if (IsStructureDefinitionSummary(properties)) - { - // [WMR 20171218] Harvest global core extensions, e.g. maturity level & working group - nav.HarvestExtensions(properties, harvestExtension); + // [WMR 20171218] Harvest global core extensions, e.g. maturity level & working group + nav.HarvestExtensions(properties, harvestExtension); - // Explicit extractor chaining - if (ConformanceSummaryProperties.Harvest(nav, properties)) + // Explicit extractor chaining + if (ConformanceSummaryProperties.Harvest(nav, properties)) + { + nav.HarvestValue(properties, FhirVersionKey, "fhirVersion"); + nav.HarvestValue(properties, KindKey, "kind"); + nav.HarvestValue(properties, ContextTypeKey, "contextType"); + // [WMR 20180919] NEW: Extension context + nav.HarvestValues(properties, ContextKey, "context"); + nav.HarvestValue(properties, TypeKey, "type"); + nav.HarvestValue(properties, BaseDefinitionKey, "baseDefinition"); + nav.HarvestValue(properties, DerivationKey, "derivation"); + + // [WMR 20180725] Also harvest definition property from (first) root element in snapshot/differential + // HL7 FHIR website displays this text as introduction on top of each resource/datatype page + var elementNode = nav.Children("snapshot").FirstOrDefault() ?? nav.Children("differential").FirstOrDefault(); + if (elementNode != null) { - nav.HarvestValue(properties, FhirVersionKey, "fhirVersion"); - nav.HarvestValue(properties, KindKey, "kind"); - nav.HarvestValue(properties, ContextTypeKey, "contextType"); - // [WMR 20180919] NEW: Extension context - nav.HarvestValues(properties, ContextKey, "context"); - nav.HarvestValue(properties, TypeKey, "type"); - nav.HarvestValue(properties, BaseDefinitionKey, "baseDefinition"); - nav.HarvestValue(properties, DerivationKey, "derivation"); - - // [WMR 20180725] Also harvest definition property from (first) root element in snapshot/differential - // HL7 FHIR website displays this text as introduction on top of each resource/datatype page - var elementNode = nav.Children("snapshot").FirstOrDefault() ?? nav.Children("differential").FirstOrDefault(); - if (elementNode != null) + var childNode = elementNode.Children("element").FirstOrDefault(); + if(childNode != null && Navigation.ElementDefinitionNavigator.IsRootPath(childNode.Name)) { - var childNode = elementNode.Children("element").FirstOrDefault(); - if(childNode != null && Navigation.ElementDefinitionNavigator.IsRootPath(childNode.Name)) - { - childNode.HarvestValue(properties, RootDefinitionKey, "definition"); - } + childNode.HarvestValue(properties, RootDefinitionKey, "definition"); } } - return true; } - return false; + return true; } + return false; + } - // Callback for HarvestExtensions, called for each individual extension entry - static void harvestExtension(ISourceNode nav, IDictionary properties, string url) + // Callback for HarvestExtensions, called for each individual extension entry + private static void harvestExtension(ISourceNode nav, IDictionary properties, string url) + { + if (StringComparer.Ordinal.Equals(FmmExtensionUrl, url)) { - if (StringComparer.Ordinal.Equals(FmmExtensionUrl, url)) + var child = nav.Children("valueInteger").FirstOrDefault(); + if (child != null) { - var child = nav.Children("valueInteger").FirstOrDefault(); - if (child != null) - { - properties[MaturityLevelKey] = child.Text; - } + properties[MaturityLevelKey] = child.Text; } - else if (StringComparer.Ordinal.Equals(WgExtensionUrl, url)) + } + else if (StringComparer.Ordinal.Equals(WgExtensionUrl, url)) + { + var child = nav.Children("valueCode").FirstOrDefault(); + if (child != null) { - var child = nav.Children("valueCode").FirstOrDefault(); - if (child != null) - { - properties[WorkingGroupKey] = child.Text; - } + properties[WorkingGroupKey] = child.Text; } } + } - /// Get the StructureDefinition.fhirVersion property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetStructureDefinitionFhirVersion(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(FhirVersionKey); - - /// Get the StructureDefinition.kind property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetStructureDefinitionKind(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(KindKey); - - /// Get the StructureDefinition.contextType property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetStructureDefinitionContextType(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(ContextTypeKey); - - /// Get the StructureDefinition.context property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string[] GetStructureDefinitionContext(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(ContextKey); - - /// Get the StructureDefinition.type property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetStructureDefinitionType(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(TypeKey); - - /// Get the StructureDefinition.baseDefinition property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetStructureDefinitionBaseDefinition(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(BaseDefinitionKey); - - /// Get the StructureDefinition.derivation property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetStructureDefinitionDerivation(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(DerivationKey); - - /// Get the value of the maturity level extension from the specified artifact summary property bag, if available. - /// - /// Returns the resource maturity level, as defined by the official FHIR extension "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm". - /// Only applies to summaries of resources that define FHIR core resources. - /// - public static string GetStructureDefinitionMaturityLevel(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(MaturityLevelKey); - - /// Get the value of the working group extension from the specified artifact summary property bag, if available. - /// - /// Returns the associated working group, as defined by the official FHIR extension "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg". - /// Only applies to summaries of resources that define FHIR core resources. - /// - public static string GetStructureDefinitionWorkingGroup(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(WorkingGroupKey); - - /// Get the value of the root element definition from the specified artifact summary property bag, if available. - /// - /// Returns the definition text of the root element. - /// Only applies to summaries of resources. - /// - public static string GetStructureDefinitionRootDefinition(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(RootDefinitionKey); + /// Get the StructureDefinition.fhirVersion property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetStructureDefinitionFhirVersion(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(FhirVersionKey); + + /// Get the StructureDefinition.kind property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetStructureDefinitionKind(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(KindKey); + + /// Get the StructureDefinition.contextType property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetStructureDefinitionContextType(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(ContextTypeKey); + + /// Get the StructureDefinition.context property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string[]? GetStructureDefinitionContext(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(ContextKey); + + /// Get the StructureDefinition.type property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetStructureDefinitionType(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(TypeKey); + + /// Get the StructureDefinition.baseDefinition property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetStructureDefinitionBaseDefinition(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(BaseDefinitionKey); + + /// Get the StructureDefinition.derivation property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetStructureDefinitionDerivation(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(DerivationKey); + + /// Get the value of the maturity level extension from the specified artifact summary property bag, if available. + /// + /// Returns the resource maturity level, as defined by the official FHIR extension "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm". + /// Only applies to summaries of resources that define FHIR core resources. + /// + public static string? GetStructureDefinitionMaturityLevel(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(MaturityLevelKey); - } + /// Get the value of the working group extension from the specified artifact summary property bag, if available. + /// + /// Returns the associated working group, as defined by the official FHIR extension "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg". + /// Only applies to summaries of resources that define FHIR core resources. + /// + public static string? GetStructureDefinitionWorkingGroup(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(WorkingGroupKey); + + /// Get the value of the root element definition from the specified artifact summary property bag, if available. + /// + /// Returns the definition text of the root element. + /// Only applies to summaries of resources. + /// + public static string? GetStructureDefinitionRootDefinition(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(RootDefinitionKey); + +} - /// For harvesting specific summary information from a resource. - public static class CodeSystemSummaryProperties - { - static readonly string CodeSystemTypeName = ResourceType.CodeSystem.GetLiteral(); +/// For harvesting specific summary information from a resource. +public static class CodeSystemSummaryProperties +{ + private static readonly string CodeSystemTypeName = ResourceType.CodeSystem.GetLiteral(); - public static readonly string ValueSetKey = "CodeSystem.valueSet"; + public static readonly string ValueSetKey = "CodeSystem.valueSet"; - /// Determines if the specified instance represents summary information about a resource. - public static bool IsCodeSystemSummary(this IArtifactSummaryPropertyBag properties) - => properties.GetTypeName() == CodeSystemTypeName; + /// Determines if the specified instance represents summary information about a resource. + public static bool IsCodeSystemSummary(this IArtifactSummaryPropertyBag properties) + => properties.GetTypeName() == CodeSystemTypeName; - /// Harvest specific summary information from a resource. - /// true if the current target represents a resource, or false otherwise. - /// The calls this method from a delegate. - public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + /// Harvest specific summary information from a resource. + /// true if the current target represents a resource, or false otherwise. + /// The calls this method from a delegate. + public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + { + if (properties.IsCodeSystemSummary()) { - if (properties.IsCodeSystemSummary()) + // Explicit harvester chaining + if (ConformanceSummaryProperties.Harvest(nav, properties)) { - // Explicit harvester chaining - if (ConformanceSummaryProperties.Harvest(nav, properties)) - { - nav.HarvestValue(properties, ValueSetKey, "valueSet", "value"); - } - return true; + nav.HarvestValue(properties, ValueSetKey, "valueSet", "value"); } - return false; + return true; } - - /// Get the CodeSystem.valueSet property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetCodeSystemValueSet(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(ValueSetKey); + return false; } - /// For harvesting specific summary information from a resource. - public static class ConceptMapSummaryProperties - { - static readonly string ConceptMapTypeName = ResourceType.ConceptMap.GetLiteral(); + /// Get the CodeSystem.valueSet property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetCodeSystemValueSet(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(ValueSetKey); +} - public static readonly string SourceKey = "ConceptMap.source"; - public static readonly string TargetKey = "ConceptMap.target"; +/// For harvesting specific summary information from a resource. +public static class ConceptMapSummaryProperties +{ + private static readonly string ConceptMapTypeName = ResourceType.ConceptMap.GetLiteral(); + + public static readonly string SourceKey = "ConceptMap.source"; + public static readonly string TargetKey = "ConceptMap.target"; - /// Determines if the specified instance represents summary information about a resource. - public static bool IsConceptMapSummary(this IArtifactSummaryPropertyBag properties) - => properties.GetTypeName() == ConceptMapTypeName; + /// Determines if the specified instance represents summary information about a resource. + public static bool IsConceptMapSummary(this IArtifactSummaryPropertyBag properties) + => properties.GetTypeName() == ConceptMapTypeName; - /// Harvest specific summary information from a resource. - /// true if the current target represents a resource, or false otherwise. - /// The calls this method from a delegate. - public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + /// Harvest specific summary information from a resource. + /// true if the current target represents a resource, or false otherwise. + /// The calls this method from a delegate. + public static bool Harvest(ISourceNode nav, ArtifactSummaryPropertyBag properties) + { + if (IsConceptMapSummary(properties)) { - if (IsConceptMapSummary(properties)) + // Explicit harvester chaining + if (ConformanceSummaryProperties.Harvest(nav, properties)) { - // Explicit harvester chaining - if (ConformanceSummaryProperties.Harvest(nav, properties)) + if (!nav.HarvestValue(properties, SourceKey, "sourceUri")) { - if (!nav.HarvestValue(properties, SourceKey, "sourceUri")) - { - nav.HarvestValue(properties, SourceKey, "sourceReference", "reference"); - } + nav.HarvestValue(properties, SourceKey, "sourceReference", "reference"); + } - if (!nav.HarvestValue(properties, TargetKey, "targetUri")) - { - nav.HarvestValue(properties, TargetKey, "targetReference", "reference"); - } + if (!nav.HarvestValue(properties, TargetKey, "targetUri")) + { + nav.HarvestValue(properties, TargetKey, "targetReference", "reference"); } - return true; } - return false; + return true; } + return false; + } - /// Get the ConceptMap.source[x] property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetConceptMapSource(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(SourceKey); + /// Get the ConceptMap.source[x] property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetConceptMapSource(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(SourceKey); - /// Get the ConceptMap.target[x] property value from the specified artifact summary property bag, if available. - /// Only applies to summaries of resources. - public static string GetConceptMapTarget(this IArtifactSummaryPropertyBag properties) - => properties.GetValueOrDefault(TargetKey); - } + /// Get the ConceptMap.target[x] property value from the specified artifact summary property bag, if available. + /// Only applies to summaries of resources. + public static string? GetConceptMapTarget(this IArtifactSummaryPropertyBag properties) + => properties.GetValueOrDefault(TargetKey); } \ No newline at end of file