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

Turn Element.ElementId and Extension.Url into FHIR Primitives #2978

Merged
merged 6 commits into from
Nov 26, 2024
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
7 changes: 7 additions & 0 deletions src/Hl7.Fhir.Base/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@
<Right>lib/net8.0/Hl7.Fhir.Base.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Hl7.Fhir.Serialization.FhirJsonException.INCOMPATIBLE_SIMPLE_VALUE_CODE</Target>
<Left>lib/net8.0/Hl7.Fhir.Base.dll</Left>
<Right>lib/net8.0/Hl7.Fhir.Base.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Hl7.FhirPath.Functions.EqualityOperators.TypedElementEqualityComparer</Target>
Expand Down
11 changes: 9 additions & 2 deletions src/Hl7.Fhir.Base/ElementModel/NewPocoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,16 @@ private Base readFromElement(ITypedElement node, ClassMapping classMapping)
var objectValue = newInstance is DynamicPrimitive ?
value :
convertTypedElementValue(value, node.InstanceType);
newInstance["value"] = objectValue;

if (settings?.AllowUnrecognizedEnums == false && classMapping.EnumType is not null && objectValue is string enumLiteral)
if(newInstance is PrimitiveType pt)
pt.ObjectValue = objectValue;
else
raiseFormatError($"{node.Name} is a primitive of type {value.GetType()}, but the target POCO is a {newInstance.GetType()}, " +
$"which is not FHIR primitive.", node.Location);

if (settings?.AllowUnrecognizedEnums == false &&
classMapping.EnumType is not null &&
objectValue is string enumLiteral)
{
// Backwards-compatible check for enums. Although our POCOs accept strings rather
// than enum values, this check is still useful for catching typos in the data and may
Expand Down
22 changes: 5 additions & 17 deletions src/Hl7.Fhir.Base/ElementModel/PocoElementNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal PocoElementNode(ModelInspector inspector, Base root, string rootName =
InstanceType = ((IStructureDefinitionSummary)_myClassMapping).TypeName;
Definition = ElementDefinitionSummary.ForRoot(_myClassMapping, rootName ?? root.TypeName);

Location = InstanceType;
Location = InstanceType!;
ShortPath = InstanceType;
}

Expand All @@ -49,9 +49,9 @@ private PocoElementNode(ModelInspector inspector, Base instance, PocoElementNode
var instanceType = definition.Choice != ChoiceType.None
? instance.GetType()
: determineInstanceType(definition);
_myClassMapping = _inspector.FindOrImportClassMapping(instanceType);
_myClassMapping = _inspector.FindOrImportClassMapping(instanceType)!;
InstanceType = ((IStructureDefinitionSummary)_myClassMapping).TypeName;
Definition = definition ?? throw Error.ArgumentNull(nameof(definition));
Definition = definition;

ExceptionHandler = parent.ExceptionHandler;
Location = location;
Expand All @@ -61,18 +61,8 @@ private PocoElementNode(ModelInspector inspector, Base instance, PocoElementNode
private Type determineInstanceType(PropertyMapping definition)
{
if (!definition.IsPrimitive) return definition.PropertyTypeMapping.NativeType;

// Backwards compat hack: the primitives (since .value is never queried, this
// means Element.id, Narrative.div and Extension.url) should be returned as FHIR types, not
// system (CQL) type.
return definition.Name switch
{
"url" => typeof(FhirUri),
"id" => typeof(FhirString),
//"div" => typeof(XHtml),
_ => throw new NotSupportedException(
$"Encountered unexpected primitive type {Name} in backward compat behaviour for PocoElementNode.InstanceType.")
};
throw new NotSupportedException(
$"Encountered unexpected primitive type {Name} for PocoElementNode.InstanceType.");
}

public IElementDefinitionSummary Definition { get; }
Expand All @@ -92,8 +82,6 @@ private Type determineInstanceType(PropertyMapping definition)
{
(Base @base, _, _) => new[] { (@base, 0) },
(IEnumerable<Base> bases, _, _) => bases.Select((e, i) => (e, i)),
(string s, Extension, "url") => new[]{ ( (Base)new FhirUri(s), 0)},
(string s, Element, "id") => new[]{ ((Base)new FhirString(s), 0)},
_ => Enumerable.Empty<(Base, int)>(),
};
}
Expand Down
20 changes: 5 additions & 15 deletions src/Hl7.Fhir.Base/Introspection/PropertyMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,26 +339,16 @@ private ITypeSerializationInfo[] buildTypes()
if (elementTypeMapping!.IsBackboneType)
{
var info = elementTypeMapping;
return new ITypeSerializationInfo[] { info };
return [info];
}
else if (IsPrimitive)
{
// Backwards compat hack: the primitives (since .value is never queried, this
// means Element.id, Narrative.div and Extension.url) should be returned as FHIR type names, not
// system (CQL) type names.
var bwcompatType = Name switch
{
"url" => "uri",
"id" => "string",
"div" => "xhtml",
_ => throw new NotSupportedException($"Encountered unexpected primitive type {Name} in backward compat behaviour for ITypedElement.InstanceType.")
};

return new[] { (ITypeSerializationInfo)new PocoTypeReferenceInfo(bwcompatType) };
throw new NotSupportedException(
$"Encountered unexpected primitive type {Name} for ITypedElement.InstanceType.");
}
else
{
var names = FhirType.Select(ft => getFhirTypeName(ft));
var names = FhirType.Select(getFhirTypeName);
return names.Select(n => (ITypeSerializationInfo)new PocoTypeReferenceInfo(n)).ToArray();
}

Expand All @@ -381,7 +371,7 @@ public PocoTypeReferenceInfo(string canonical)
ReferredType = canonical;
}

public string ReferredType { get; private set; }
public string ReferredType { get; }
}

#endregion
Expand Down
6 changes: 5 additions & 1 deletion src/Hl7.Fhir.Base/Model/Base.Dictionary.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#if NOT_USED

using System;
using System.Collections;
using System.Collections.Generic;
Expand Down Expand Up @@ -46,4 +48,6 @@ IEnumerator IEnumerable.GetEnumerator() =>

#endregion

}
}

#endif
7 changes: 2 additions & 5 deletions src/Hl7.Fhir.Base/Model/Base.Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ public static IEnumerable<Base> Children(this Base instance)
case ("div", XHtml xhtml):
yield return new FhirString(xhtml.Value);
break;
case ("id", string id):
yield return new FhirString(id);
break;
case ("url", string url):
yield return new FhirUri(url);
case ("id", FhirUri id):
yield return new FhirString(id.Value);
break;
case (_, IEnumerable<Base> list):
foreach (var item in list)
Expand Down
26 changes: 10 additions & 16 deletions src/Hl7.Fhir.Base/Model/Base.TypedElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,13 @@ IEnumerable<ITypedElement> ITypedElement.Children(string? name) =>
this.GetElementPairs()
.Where(ep => (name == null || name == ep.Key))
.SelectMany<KeyValuePair<string, object>, Base>(ep =>
(ep.Key, ep.Value) switch
ep.Value switch
{
(_, Base b) => (IEnumerable<Base>) [b.WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
(_, IEnumerable<Base> list) => list.Select((item, idx) =>
Base b => (IEnumerable<Base>) [b.WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
IEnumerable<Base> list => list.Select((item, idx) =>
item.WithScopeInfo(new ScopeInformation(this, ep.Key, idx))),
("url", string s) when this is Extension =>
[new FhirUri(s).WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
("id", string s) when this is Element =>
[new FhirString(s).WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
("value", _) => [],
_ => throw new InvalidOperationException("Unexpected system primitive in child list")
_ => throw new InvalidOperationException($"Key '{ep.Key}' has a value with " +
$"unexpected type '{ep.Value.GetType()}'.")
}
);

Expand Down Expand Up @@ -218,14 +214,12 @@ bool IScopedNode.TryResolveContainedEntry(string id, [NotNullWhen(true)] out ISc
IEnumerable<IScopedNode> IScopedNode.Children(string? name) => this.GetElementPairs()
.Where(ep => (name == null || name == ep.Key))
.SelectMany<KeyValuePair<string, object>, Base>(ep =>
(ep.Key, ep.Value) switch
ep.Value switch
{
(_, Base b) => (IEnumerable<Base>)[b.WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
(_, IEnumerable<Base> list) => list.Select((item, idx) => item.WithScopeInfo(new ScopeInformation(this, ep.Key, idx))),
("url", string s) when this is Extension => [new FhirUri(s).WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
("id", string s) when this is Element => [new FhirString(s).WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
("value", _) => [],
_ => throw new InvalidOperationException("Unexpected system primitive in child list")
Base b => (IEnumerable<Base>)[b.WithScopeInfo(new ScopeInformation(this, ep.Key, null))],
IEnumerable<Base> list => list.Select((item, idx) => item.WithScopeInfo(new ScopeInformation(this, ep.Key, idx))),
_ => throw new InvalidOperationException($"Key '{ep.Key}' has a value with " +
$"unexpected type '{ep.Value.GetType()}'.")
}
);

Expand Down
16 changes: 7 additions & 9 deletions src/Hl7.Fhir.Base/Model/Base.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ POSSIBILITY OF SUCH DAMAGE.
#nullable enable

using Hl7.Fhir.ElementModel;
using Hl7.Fhir.Introspection;
using Hl7.Fhir.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;

namespace Hl7.Fhir.Model;
Expand All @@ -51,7 +48,6 @@ public abstract partial class Base : IDeepCopyable, IDeepComparable,
/// </summary>
public virtual string TypeName => GetType().Name;


private Dictionary<string, object>? _overflow = null;

/// <summary>
Expand All @@ -71,11 +67,11 @@ public abstract partial class Base : IDeepCopyable, IDeepComparable,
public IEnumerable<object> Annotations(Type type)
{
if (type == typeof(ITypedElement) || type == typeof(IShortPathGenerator) || type == typeof(IScopedNode))
return new[] { this };
return [this];
else if (type == typeof(IFhirValueProvider))
return new[] { this };
return [this];
else if (type == typeof(IResourceTypeSupplier))
return new[] { this };
return [this];
else
return annotations.OfType(type);
}
Expand All @@ -100,7 +96,11 @@ internal protected virtual Base SetValue(string key, object? value)
if (value is null)
Overflow.Remove(key);
else
{
if (value is not Base && value is not IReadOnlyList<Base>)
throw new InvalidCastException($"Value for key '{key}' must be of type Base or a list of type Base.");
Overflow[key] = value;
}

return this;
}
Expand All @@ -117,6 +117,4 @@ internal protected virtual bool TryGetValue(string key, [NotNullWhen(true)] out
Overflow.TryGetValue(key, out value);

internal protected virtual IEnumerable<KeyValuePair<string, object>> GetElementPairs() => Overflow;

// TODO bring Children + NamedChildren over as well.
}
41 changes: 29 additions & 12 deletions src/Hl7.Fhir.Base/Model/Generated/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,32 @@ public abstract partial class Element : Hl7.Fhir.Model.Base
/// Unique id for inter-element referencing
/// </summary>
[FhirElement("id", XmlSerialization = XmlRepresentation.XmlAttr, Order=10)]
[DeclaredType(Type = typeof(SystemPrimitive.String))]
[DataMember]
public string ElementId
public Hl7.Fhir.Model.FhirString ElementIdElement
{
get { return _ElementId; }
set { _ElementId = value; OnPropertyChanged("ElementId"); }
get { return _ElementIdElement; }
set { _ElementIdElement = value; OnPropertyChanged("ElementIdElement"); }
}

private string _ElementId;
private Hl7.Fhir.Model.FhirString _ElementIdElement;

/// <summary>
/// Unique id for inter-element referencing
/// </summary>
/// <remarks>This uses the native .NET datatype, rather than the FHIR equivalent</remarks>
[IgnoreDataMember]
public string ElementId
{
get { return ElementIdElement != null ? ElementIdElement.Value : null; }
set
{
if (value == null)
ElementIdElement = null;
else
ElementIdElement = new Hl7.Fhir.Model.FhirString(value);
OnPropertyChanged("ElementId");
}
}

/// <summary>
/// Additional content defined by implementations
Expand All @@ -92,7 +109,7 @@ public override IDeepCopyable CopyTo(IDeepCopyable other)
}

base.CopyTo(dest);
if(ElementId != null) dest.ElementId = ElementId;
if(ElementIdElement != null) dest.ElementIdElement = (Hl7.Fhir.Model.FhirString)ElementIdElement.DeepCopy();
if(Extension.Any()) dest.Extension = new List<Hl7.Fhir.Model.Extension>(Extension.DeepCopy());
return dest;
}
Expand All @@ -104,7 +121,7 @@ public override bool Matches(IDeepComparable other)
if(otherT == null) return false;

if(!base.Matches(otherT)) return false;
if( ElementId != otherT.ElementId ) return false;
if( !DeepComparable.Matches(ElementIdElement, otherT.ElementIdElement)) return false;
if( !DeepComparable.Matches(Extension, otherT.Extension)) return false;

return true;
Expand All @@ -116,7 +133,7 @@ public override bool IsExactly(IDeepComparable other)
if(otherT == null) return false;

if(!base.IsExactly(otherT)) return false;
if(ElementId != otherT.ElementId) return false;
if( !DeepComparable.IsExactly(ElementIdElement, otherT.ElementIdElement)) return false;
if( !DeepComparable.IsExactly(Extension, otherT.Extension)) return false;

return true;
Expand All @@ -127,8 +144,8 @@ internal protected override bool TryGetValue(string key, out object value)
switch (key)
{
case "id":
value = ElementId;
return ElementId is not null;
value = ElementIdElement;
return ElementIdElement is not null;
case "extension":
value = Extension;
return Extension?.Any() == true;
Expand All @@ -143,7 +160,7 @@ internal protected override Base SetValue(string key, object value)
switch (key)
{
case "id":
ElementId = (string)value;
ElementIdElement = (Hl7.Fhir.Model.FhirString)value;
return this;
case "extension":
Extension = (List<Hl7.Fhir.Model.Extension>)value;
Expand All @@ -157,7 +174,7 @@ internal protected override Base SetValue(string key, object value)
internal protected override IEnumerable<KeyValuePair<string, object>> GetElementPairs()
{
foreach (var kvp in base.GetElementPairs()) yield return kvp;
if (ElementId is not null) yield return new KeyValuePair<string,object>("id",ElementId);
if (ElementIdElement is not null) yield return new KeyValuePair<string,object>("id",ElementIdElement);
if (Extension?.Any() == true) yield return new KeyValuePair<string,object>("extension",Extension);
}

Expand Down
Loading