Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VONK-5093: introducing IScopedNode #2582

Merged
merged 16 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/Benchmarks/EnumUtilityBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Hl7.Fhir.Utility;
using System;

#nullable enable

namespace Firely.Sdk.Benchmarks
{
[MemoryDiagnoser]
Expand All @@ -16,7 +18,7 @@ public string EnumToString()
=> SearchParamType.String.ToString();

[Benchmark]
public string EnumGetName()
public string? EnumGetName()
=> Enum.GetName(StringSearchParam);

[Benchmark]
Expand All @@ -37,26 +39,27 @@ public SearchParamType EnumParseIgnoreCase()

[Benchmark]
public SearchParamType EnumUtilityParseLiteral()
=> EnumUtility.ParseLiteral<SearchParamType>("string").Value;
=> EnumUtility.ParseLiteral<SearchParamType>("string")!.Value;

[Benchmark]
public Enum EnumUtilityParseLiteralNonGeneric()
public Enum? EnumUtilityParseLiteralNonGeneric()
=> EnumUtility.ParseLiteral("string", typeof(SearchParamType));

[Benchmark]
public SearchParamType EnumUtilityParseLiteralIgnoreCase()
=> EnumUtility.ParseLiteral<SearchParamType>("string", true).Value;
=> EnumUtility.ParseLiteral<SearchParamType>("string", true)!.Value;

[Benchmark]
public Enum EnumUtilityParseLiteralIgnoreCaseNonGeneric()
public Enum? EnumUtilityParseLiteralIgnoreCaseNonGeneric()
=> EnumUtility.ParseLiteral("string", typeof(SearchParamType), true);

[Benchmark]
public string? EnumUtilityGetSystem()
=> EnumUtility.GetSystem(StringSearchParam);

[Benchmark]
public string EnumUtilityGetSystemNonGeneric()
public string? EnumUtilityGetSystemNonGeneric()
=> EnumUtility.GetSystem(StringSearchParamEnum);
}
}
#nullable restore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2023, Firely ([email protected]) 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
{
/// <summary>
/// An adapter from <see cref="IScopedNode"/> to <see cref="ITypedElement"/>.
/// </summary>
/// <remarks>Be careful, this adapter does not implement the <see cref="ITypedElement.Location"/> and
/// <see cref="ITypedElement.Definition"/> property.
/// </remarks>
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<ITypedElement> Children(string? name = null) =>
_adaptee.Children(name).Select(n => new ScopedNodeToTypedElementAdapter(n));
}
}

#nullable restore
9 changes: 9 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/ElementNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,14 @@ public static IReadOnlyCollection<IElementDefinitionSummary> ChildDefinitions(th

public static ScopedNode ToScopedNode(this ITypedElement node) =>
node as ScopedNode ?? new ScopedNode(node);

/// <summary>
/// Convert a <see cref="ITypedElement"/> to a <see cref="IScopedNode"/>.
/// </summary>
/// <param name="node">An <see cref="ITypedElement"/></param>
/// <returns></returns>
internal static IScopedNode AsScopedNode(this ITypedElement node) => ToScopedNode(node);
}
}

#nullable restore
70 changes: 70 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/IBaseElementNavigator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2023, Firely ([email protected]) 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;
using System.Collections.Generic;

#nullable enable

namespace Hl7.Fhir.ElementModel
{
/// <summary>
/// The base interface for <see cref="ITypedElement"/> and <see cref="IScopedNode"/>."/>
/// </summary>
/// <typeparam name="TDerived"></typeparam>
[Obsolete("WARNING! Intended for internal API usage exclusively, this interface ideally should be kept internal. " +
"However, due to its derivation by the public interface ITypedElement, maintaining its internal status is impossible.")]
public interface IBaseElementNavigator<TDerived> where TDerived : IBaseElementNavigator<TDerived>
{
/// <summary>
/// Enumerate the child nodes present in the source representation (if any)
/// </summary>
/// <param name="name">Return only the children with the given name.</param>
/// <returns></returns>
IEnumerable<TDerived> Children(string? name = null);

/// <summary>
/// Name of the node, e.g. "active", "value".
/// </summary>
string Name { get; }

/// <summary>
/// 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.
/// </summary>
string InstanceType { get; }

/// <summary>
/// The value of the node (if it represents a primitive FHIR value)
/// </summary>
/// <remarks>
/// 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
/// </remarks>
object Value { get; }
}
}

#nullable restore
31 changes: 31 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/IScopedNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2023, Firely ([email protected]) 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
{
/// <summary>
/// An element within a tree of typed FHIR data with also a parent element.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
#pragma warning disable CS0618 // Type or member is obsolete
internal interface IScopedNode : IBaseElementNavigator<IScopedNode>
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// The parent node of this node, or null if this is the root node.
/// </summary>
IScopedNode? Parent { get; }
}
}

#nullable restore
45 changes: 45 additions & 0 deletions src/Hl7.Fhir.Base/ElementModel/IScopedNodeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2023, Firely ([email protected]) 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;
using System.Collections.Generic;
using System.Linq;

namespace Hl7.Fhir.ElementModel
{
internal static class IScopedNodeExtensions
{
/// <summary>
/// Converts a <see cref="IScopedNode"/> to a <see cref="ITypedElement"/>.
/// </summary>
/// <param name="node">An <see cref="IScopedNode"/> node</param>
/// <returns>An implementation of <see cref="ITypedElement"/></returns>
/// <remarks>Be careful when using this method, the returned <see cref="ITypedElement"/> does not implement
/// the methods <see cref="ITypedElement.Location"/> and <see cref="ITypedElement.Definition"/>.
/// </remarks>
[Obsolete("WARNING! For internal API use only. Turning an IScopedNode into an ITypedElement will cause problems for" +
"Location and Definitions. Those properties are not implemented using this method and can cause problems " +
"elsewhere. Please don't use this method unless you know what you are doing.")]
public static ITypedElement AsTypedElement(this IScopedNode node) =>
marcovisserFurore marked this conversation as resolved.
Show resolved Hide resolved
node is ITypedElement ite ? ite : new ScopedNodeToTypedElementAdapter(node);

/// <summary>
/// Returns the parent resource of this node, or null if this node is not part of a resource.
/// </summary>
/// <param name="nodes"></param>
/// <param name="name"></param>
/// <returns></returns>
public static IEnumerable<IScopedNode> Children(this IEnumerable<IScopedNode> nodes, string? name = null) =>
nodes.SelectMany(n => n.Children(name));
}
}

#nullable restore
47 changes: 3 additions & 44 deletions src/Hl7.Fhir.Base/ElementModel/ITypedElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
*/

using Hl7.Fhir.Specification;
using System.Collections.Generic;

namespace Hl7.Fhir.ElementModel
{
Expand All @@ -19,51 +18,11 @@ namespace Hl7.Fhir.ElementModel
/// the instance or derived from fully aware of the FHIR definitions and types
/// </remarks>

public interface ITypedElement
#pragma warning disable CS0618 // Type or member is obsolete
public interface ITypedElement : IBaseElementNavigator<ITypedElement>
#pragma warning restore CS0618 // Type or member is obsolete
{
/// <summary>
/// Enumerate the child nodes present in the source representation (if any)
/// </summary>
/// <param name="name">Return only the children with the given name.</param>
/// <returns></returns>
IEnumerable<ITypedElement> Children(string name = null);

/// <summary>
/// Name of the node, e.g. "active", "value".
/// </summary>
string Name { get; }

/// <summary>
/// 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.
/// </summary>
string InstanceType { get; }

/// <summary>
/// The value of the node (if it represents a primitive FHIR value)
/// </summary>
/// <remarks>
/// 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
/// </remarks>
object Value { get; }

/// <summary>
/// An indication of the location of this node within the data represented by the <c>ITypedElement</c>.
Expand Down
22 changes: 18 additions & 4 deletions src/Hl7.Fhir.Base/ElementModel/ScopedNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace Hl7.Fhir.ElementModel
{
public class ScopedNode : ITypedElement, IAnnotated, IExceptionSource
public class ScopedNode : ITypedElement, IScopedNode, IAnnotated, IExceptionSource
{
private class Cache
{
Expand All @@ -32,6 +32,7 @@ private class Cache
private readonly Cache _cache = new();

public readonly ITypedElement Current;
private readonly ScopedNode? _parent;

public ScopedNode(ITypedElement wrapped, string? instanceUri = null)
{
Expand All @@ -45,6 +46,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;

Expand Down Expand Up @@ -217,8 +219,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;
}
Expand All @@ -229,11 +230,24 @@ private set
}
}

/// <inheritdoc />


IScopedNode? IScopedNode.Parent => _parent;

/// <inheritdoc />
public IEnumerable<object> Annotations(Type type) => type == typeof(ScopedNode) ? (new[] { this }) : Current.Annotations(type);

private IEnumerable<ScopedNode> childrenInternal(string? name = null) =>
Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl));

/// <inheritdoc />
public IEnumerable<ITypedElement> Children(string? name = null) =>
Current.Children(name).Select(c => new ScopedNode(this, ParentResource, c, _fullUrl));
childrenInternal(name);

IEnumerable<IScopedNode> IBaseElementNavigator<IScopedNode>.Children(string? name) =>
childrenInternal(name);
}
}

#nullable restore
Loading