diff --git a/src/Benchmarks/EnumUtilityBenchmarks.cs b/src/Benchmarks/EnumUtilityBenchmarks.cs index 527acc7653..1ea5258531 100644 --- a/src/Benchmarks/EnumUtilityBenchmarks.cs +++ b/src/Benchmarks/EnumUtilityBenchmarks.cs @@ -3,6 +3,8 @@ using Hl7.Fhir.Utility; using System; +#nullable enable + namespace Firely.Sdk.Benchmarks { [MemoryDiagnoser] @@ -16,7 +18,7 @@ public string EnumToString() => SearchParamType.String.ToString(); [Benchmark] - public string EnumGetName() + public string? EnumGetName() => Enum.GetName(StringSearchParam); [Benchmark] @@ -37,18 +39,18 @@ public SearchParamType EnumParseIgnoreCase() [Benchmark] public SearchParamType EnumUtilityParseLiteral() - => EnumUtility.ParseLiteral("string").Value; + => EnumUtility.ParseLiteral("string")!.Value; [Benchmark] - public Enum EnumUtilityParseLiteralNonGeneric() + public Enum? EnumUtilityParseLiteralNonGeneric() => EnumUtility.ParseLiteral("string", typeof(SearchParamType)); [Benchmark] public SearchParamType EnumUtilityParseLiteralIgnoreCase() - => EnumUtility.ParseLiteral("string", true).Value; + => EnumUtility.ParseLiteral("string", true)!.Value; [Benchmark] - public Enum EnumUtilityParseLiteralIgnoreCaseNonGeneric() + public Enum? EnumUtilityParseLiteralIgnoreCaseNonGeneric() => EnumUtility.ParseLiteral("string", typeof(SearchParamType), true); [Benchmark] @@ -56,7 +58,8 @@ public Enum EnumUtilityParseLiteralIgnoreCaseNonGeneric() => EnumUtility.GetSystem(StringSearchParam); [Benchmark] - public string EnumUtilityGetSystemNonGeneric() + public string? EnumUtilityGetSystemNonGeneric() => EnumUtility.GetSystem(StringSearchParamEnum); } } +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs new file mode 100644 index 0000000000..3d0814c177 --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/Adapters/ScopedNodeToTypedElementAdapter.cs @@ -0,0 +1,47 @@ +/* + * 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.Specification; +using System.Collections.Generic; +using System.Linq; + +namespace Hl7.Fhir.ElementModel +{ + /// + /// An adapter from to . + /// + /// Be careful, this adapter does not implement the and + /// property. + /// + internal class ScopedNodeToTypedElementAdapter : ITypedElement + { + private readonly IScopedNode _adaptee; + + public ScopedNodeToTypedElementAdapter(IScopedNode adaptee) + { + _adaptee = adaptee; + } + + public string Location => throw new System.NotImplementedException(); + + public IElementDefinitionSummary Definition => throw new System.NotImplementedException(); + + public string Name => _adaptee.Name; + + public string InstanceType => _adaptee.InstanceType; + + public object Value => _adaptee.Value; + + public IEnumerable Children(string? name = null) => + _adaptee.Children(name).Select(n => new ScopedNodeToTypedElementAdapter(n)); + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs index 75f3ce82d0..6df5f749d2 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs @@ -108,5 +108,14 @@ public static IReadOnlyCollection ChildDefinitions(th public static ScopedNode ToScopedNode(this ITypedElement node) => node as ScopedNode ?? new ScopedNode(node); + + /// + /// Convert a to a . + /// + /// An + /// + public static IScopedNode AsScopedNode(this ITypedElement node) => ToScopedNode(node); } } + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs b/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs new file mode 100644 index 0000000000..df03548715 --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs @@ -0,0 +1,67 @@ +/* + * 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 + */ + +using System.Collections.Generic; + +#nullable enable + +namespace Hl7.Fhir.ElementModel +{ + /// + /// The base interface for and ."/> + /// + /// + public interface IBaseElementNavigator where TDerived : IBaseElementNavigator + { + /// + /// Enumerate the child nodes present in the source representation (if any) + /// + /// Return only the children with the given name. + /// + IEnumerable Children(string? name = null); + + /// + /// Name of the node, e.g. "active", "value". + /// + string Name { get; } + + /// + /// Type of the node. If a FHIR type, this is just a simple string, otherwise a StructureDefinition url for a type defined as a logical model. + /// + string InstanceType { get; } + + /// + /// The value of the node (if it represents a primitive FHIR value) + /// + /// + /// FHIR primitives are mapped to underlying C# types as follows: + /// + /// instant Hl7.Fhir.ElementModel.Types.DateTime + /// time Hl7.Fhir.ElementModel.Types.Time + /// date Hl7.Fhir.ElementModel.Types.Date + /// dateTime Hl7.Fhir.ElementModel.Types.DateTime + /// decimal decimal + /// boolean bool + /// integer int + /// unsignedInt int + /// positiveInt int + /// long/integer64 long (name will be finalized in R5) + /// string string + /// code string + /// id string + /// uri, oid, uuid, + /// canonical, url string + /// markdown string + /// base64Binary string (uuencoded) + /// xhtml string + /// + object Value { get; } + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs new file mode 100644 index 0000000000..968dc12268 --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs @@ -0,0 +1,29 @@ +/* + * 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 + +namespace Hl7.Fhir.ElementModel +{ + /// + /// An element within a tree of typed FHIR data with also a parent element. + /// + /// + /// This interface represents FHIR data as a tree of elements, including type information either present in + /// the instance or derived from fully aware of the FHIR definitions and types + /// + public interface IScopedNode : IBaseElementNavigator + { + /// + /// The parent node of this node, or null if this is the root node. + /// + IScopedNode? Parent { get; } + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs new file mode 100644 index 0000000000..30bbca5b85 --- /dev/null +++ b/src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs @@ -0,0 +1,42 @@ +/* + * 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 System.Collections.Generic; +using System.Linq; + +namespace Hl7.Fhir.ElementModel +{ + public static class IScopedNodeExtensions + { + /// + /// Converts a to a . + /// + /// An node + /// An implementation of + /// Be careful when using this method, the returned does not implement + /// the methods and . + /// + public static ITypedElement AsTypedElement(this IScopedNode node) => + node is ITypedElement ite ? ite : new ScopedNodeToTypedElementAdapter(node); + + /// + /// Returns the parent resource of this node, or null if this node is not part of a resource. + /// + /// + /// + /// + public static IEnumerable Children(this IEnumerable nodes, string? name = null) => + nodes.SelectMany(n => n.Children(name)); + + } +} + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs b/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs index bb7e8403a8..6e0a5a88ee 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs @@ -7,7 +7,6 @@ */ using Hl7.Fhir.Specification; -using System.Collections.Generic; namespace Hl7.Fhir.ElementModel { @@ -19,51 +18,9 @@ namespace Hl7.Fhir.ElementModel /// the instance or derived from fully aware of the FHIR definitions and types /// - public interface ITypedElement + public interface ITypedElement : IBaseElementNavigator { - /// - /// Enumerate the child nodes present in the source representation (if any) - /// - /// Return only the children with the given name. - /// - IEnumerable Children(string name = null); - - /// - /// Name of the node, e.g. "active", "value". - /// - string Name { get; } - /// - /// Type of the node. If a FHIR type, this is just a simple string, otherwise a StructureDefinition url for a type defined as a logical model. - /// - string InstanceType { get; } - - /// - /// The value of the node (if it represents a primitive FHIR value) - /// - /// - /// FHIR primitives are mapped to underlying C# types as follows: - /// - /// instant Hl7.Fhir.ElementModel.Types.DateTime - /// time Hl7.Fhir.ElementModel.Types.Time - /// date Hl7.Fhir.ElementModel.Types.Date - /// dateTime Hl7.Fhir.ElementModel.Types.DateTime - /// decimal decimal - /// boolean bool - /// integer int - /// unsignedInt int - /// positiveInt int - /// long/integer64 long (name will be finalized in R5) - /// string string - /// code string - /// id string - /// uri, oid, uuid, - /// canonical, url string - /// markdown string - /// base64Binary string (uuencoded) - /// xhtml string - /// - object Value { get; } /// /// An indication of the location of this node within the data represented by the ITypedElement. diff --git a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs index 72001de883..dabf05cff3 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs @@ -16,7 +16,7 @@ namespace Hl7.Fhir.ElementModel { - public class ScopedNode : ITypedElement, IAnnotated, IExceptionSource + public class ScopedNode : ITypedElement, IScopedNode, IAnnotated, IExceptionSource { private class Cache { @@ -45,6 +45,7 @@ public ScopedNode(ITypedElement wrapped, string? instanceUri = null) private ScopedNode(ScopedNode parentNode, ScopedNode? parentResource, ITypedElement wrapped, string? fullUrl) { Current = wrapped; + Parent = parentNode; ExceptionHandler = parentNode.ExceptionHandler; ParentResource = parentNode.AtResource ? parentNode : parentResource; @@ -217,8 +218,7 @@ public string? InstanceUri { // Scan up until the first parent that knowns the instance uri (at the last the // root, if it has been supplied). - if (_cache.InstanceUri is null) - _cache.InstanceUri = ParentResources().SkipWhile(p => p.InstanceUri is null).FirstOrDefault()?.InstanceUri; + _cache.InstanceUri ??= ParentResources().SkipWhile(p => p.InstanceUri is null).FirstOrDefault()?.InstanceUri; return _cache.InstanceUri; } @@ -229,11 +229,22 @@ private set } } + /// + public IScopedNode? Parent { get; private set; } + /// public IEnumerable Annotations(Type type) => type == typeof(ScopedNode) ? (new[] { this }) : Current.Annotations(type); + private IEnumerable childrenInternal(string? name = null) => + Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl)); + /// public IEnumerable Children(string? name = null) => - Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl)); + childrenInternal(name); + + IEnumerable IBaseElementNavigator.Children(string? name) => + childrenInternal(name); } } + +#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs index de357cd544..d4f2359777 100644 --- a/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/ScopedNodeExtensions.cs @@ -136,7 +136,7 @@ public static string MakeAbsolute(this ScopedNode node, string reference) => string? url = element switch { { Value: string s } => s, - { InstanceType: FhirTypeConstants.REFERENCE } => element.ParseResourceReference()?.Reference, + { InstanceType: FhirTypeConstants.REFERENCE } => element.ParseResourceReference()?.Reference, _ => null }; diff --git a/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs index 986e54c602..e703bdc706 100644 --- a/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/TypedElementExtensions.cs @@ -37,7 +37,7 @@ public static ITypedElement ToTypedElement(this Base @base, ModelInspector model /// When true the order of the children is discarded. When false the order of children is part /// of the equation. /// true when the ITypedElements are equal, false otherwise. - public static bool IsExactlyEqualTo(this ITypedElement left, ITypedElement right, bool ignoreOrder = false) + public static bool IsExactlyEqualTo(this T left, T right, bool ignoreOrder = false) where T : IBaseElementNavigator { if (left == null && right == null) return true; if (left == null || right == null) return false; @@ -95,7 +95,7 @@ public static bool ValueEquality(T1 val1, T2 val2) /// /// /// true when matches the , false otherwise. - public static bool Matches(this ITypedElement value, ITypedElement pattern) + public static bool Matches(this T value, T pattern) where T : IBaseElementNavigator { if (value == null && pattern == null) return true; if (value == null || pattern == null) return false; diff --git a/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs b/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs index 08b0f6d57a..f18cc4eeba 100644 --- a/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs +++ b/src/Hl7.Fhir.Base/ElementModel/TypedElementParseExtensions.cs @@ -31,14 +31,14 @@ public static class TypedElementParseExtensions /// 'string' => code /// 'uri' => code /// - public static Element ParseBindable(this ITypedElement instance) + public static Element ParseBindable(this T instance) where T : IBaseElementNavigator { return instance.InstanceType switch { - FhirTypeConstants.CODE => instance.ParsePrimitive(), - FhirTypeConstants.STRING => new Code(instance.ParsePrimitive()?.Value), - FhirTypeConstants.URI => new Code(instance.ParsePrimitive()?.Value), + FhirTypeConstants.CODE => instance.ParsePrimitive(), + FhirTypeConstants.STRING => new Code(instance.ParsePrimitive()?.Value), + FhirTypeConstants.URI => new Code(instance.ParsePrimitive()?.Value), FhirTypeConstants.CODING => instance.ParseCoding(), FhirTypeConstants.CODEABLE_CONCEPT => instance.ParseCodeableConcept(), FhirTypeConstants.QUANTITY => parseQuantity(), @@ -62,7 +62,7 @@ Element parseExtension() } } - public static Model.Quantity ParseQuantity(this ITypedElement instance) + public static Model.Quantity ParseQuantity(this T instance) where T : IBaseElementNavigator { var newQuantity = new Quantity { @@ -79,11 +79,11 @@ public static Model.Quantity ParseQuantity(this ITypedElement instance) return newQuantity; } - public static T ParsePrimitive(this ITypedElement instance) where T : PrimitiveType, new() + public static T ParsePrimitive(this U instance) where T : PrimitiveType, new() where U : IBaseElementNavigator => new T() { ObjectValue = instance.Value }; - public static Coding ParseCoding(this ITypedElement instance) + public static Coding ParseCoding(this T instance) where T : IBaseElementNavigator { return new Coding() { @@ -95,7 +95,7 @@ public static Coding ParseCoding(this ITypedElement instance) }; } - public static ResourceReference ParseResourceReference(this ITypedElement instance) + public static ResourceReference ParseResourceReference(this T instance) where T : IBaseElementNavigator { return new ResourceReference() { @@ -104,7 +104,7 @@ public static ResourceReference ParseResourceReference(this ITypedElement instan }; } - public static CodeableConcept ParseCodeableConcept(this ITypedElement instance) + public static CodeableConcept ParseCodeableConcept(this T instance) where T : IBaseElementNavigator { return new CodeableConcept() { @@ -114,6 +114,7 @@ public static CodeableConcept ParseCodeableConcept(this ITypedElement instance) }; } - public static string GetString(this IEnumerable instance) => instance.SingleOrDefault()?.Value as string; + public static string GetString(this IEnumerable instance) where T : IBaseElementNavigator + => instance.SingleOrDefault()?.Value as string; } } diff --git a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs index 5a8ae6a4b4..54072a9c50 100644 --- a/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs +++ b/src/Hl7.Fhir.Base/FhirPath/CompiledExpression.cs @@ -50,6 +50,7 @@ public static bool IsTrue(this CompiledExpression evaluator, ITypedElement input return result is not null && result.Value; } + /// /// Evaluates if the result of an expression is equal to a given boolean. /// diff --git a/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs b/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs index 8700f8db37..f9680ccc95 100644 --- a/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs +++ b/src/Hl7.Fhir.Conformance/Specification/Source/SnapshotSource.cs @@ -3,7 +3,7 @@ using Hl7.Fhir.Utility; using System; using System.Diagnostics; -using T=System.Threading.Tasks; +using T = System.Threading.Tasks; namespace Hl7.Fhir.Specification.Source { @@ -34,7 +34,9 @@ public SnapshotSource(SnapshotGenerator generator) /// Creates a new instance of the for the specified internal resolver. /// An internal instance. The implementation should be idempotent (i.e. cached), so the generated snapshots are persisted in memory. /// Configuration settings for the snapshot generator. - public SnapshotSource(IResourceResolver source, SnapshotGeneratorSettings settings) +#pragma warning disable CS0618 // Type or member is obsolete + public SnapshotSource(ISyncOrAsyncResourceResolver source, SnapshotGeneratorSettings settings) +#pragma warning restore CS0618 // Type or member is obsolete { // SnapshotGenerator ctor will throw if source or settings are null Generator = new SnapshotGenerator(source, settings); @@ -43,11 +45,13 @@ public SnapshotSource(IResourceResolver source, SnapshotGeneratorSettings settin /// Creates a new instance of the for the specified internal resolver. /// An internal instance. The implementation should be idempotent (i.e. cached), so the generated snapshots are persisted in memory. /// Determines if the source should always discard any existing snapshot components provided by the internal source and force re-generation. - public SnapshotSource(IResourceResolver source, bool regenerate) +#pragma warning disable CS0618 // Type or member is obsolete + public SnapshotSource(ISyncOrAsyncResourceResolver source, bool regenerate) +#pragma warning restore CS0618 // Type or member is obsolete : this(source, createSettings(regenerate)) { } // Create default SnapshotGeneratorSettings, apply the specified regenerate flag - static SnapshotGeneratorSettings createSettings(bool regenerate) + private static SnapshotGeneratorSettings createSettings(bool regenerate) { var settings = SnapshotGeneratorSettings.CreateDefault(); settings.ForceRegenerateSnapshots = regenerate; @@ -56,7 +60,9 @@ static SnapshotGeneratorSettings createSettings(bool regenerate) /// Creates a new instance of the for the specified internal resolver. /// An internal instance. The implementation should be idempotent (i.e. cached), so the generated snapshots are persisted in memory. - public SnapshotSource(IResourceResolver source) +#pragma warning disable CS0618 // Type or member is obsolete + public SnapshotSource(ISyncOrAsyncResourceResolver source) +#pragma warning restore CS0618 // Type or member is obsolete : this(source, SnapshotGeneratorSettings.CreateDefault()) { } /// Returns the internal instance used by the source. diff --git a/src/firely-net-sdk.props b/src/firely-net-sdk.props index e0da87fe14..7d59a3a0ca 100644 --- a/src/firely-net-sdk.props +++ b/src/firely-net-sdk.props @@ -6,8 +6,8 @@ - 5.4.1 - + 5.4.2 + alpha3 Firely (info@fire.ly) and contributors Firely (https://fire.ly) Copyright 2013-2023 Firely. Contains materials (C) HL7 International