Skip to content

Commit

Permalink
Refactored so we now have a function to generate AllowedType attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
ewoutkramer committed Nov 27, 2024
1 parent ca8c52d commit fc30f6f
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 88 deletions.
120 changes: 35 additions & 85 deletions src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirely2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// </copyright>

using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Hl7.Fhir.Model;
using Hl7.Fhir.Utility;
using Microsoft.Health.Fhir.CodeGen.FhirExtensions;
Expand Down Expand Up @@ -2968,7 +2966,7 @@ private void WriteElements(
orderOffset);
}
}
private void BuildFhirElementAttribute(string name, string summary, string? isModifier, ElementDefinition element, int orderOffset, string choice, string fiveWs, string? since = null, (string, string)? until = null, string? xmlSerialization = null)
private void WriteFhirElementAttribute(string name, string summary, string? isModifier, ElementDefinition element, int orderOffset, string choice, string fiveWs, string? since = null, (string, string)? until = null, string? xmlSerialization = null)
{
var xmlser = xmlSerialization is null ? null : $", XmlSerialization = XmlRepresentation.{xmlSerialization}";
string attributeText = $"[FhirElement(\"{name}\"{xmlser}{summary}{isModifier}, Order={GetOrder(element)}{choice}{fiveWs}";
Expand Down Expand Up @@ -3046,18 +3044,18 @@ private void WriteElement(

if (path == "OperationOutcome.issue.severity")
{
BuildFhirElementAttribute(name, summary, ", IsModifier=true", element, orderOffset, choice, fiveWs);
BuildFhirElementAttribute(name, summary, null, element, orderOffset, choice, fiveWs, since: "R4");
WriteFhirElementAttribute(name, summary, ", IsModifier=true", element, orderOffset, choice, fiveWs);
WriteFhirElementAttribute(name, summary, null, element, orderOffset, choice, fiveWs, since: "R4");
}
else if (path is "Signature.who" or "Signature.onBehalfOf")
{
BuildFhirElementAttribute(name, summary, isModifier, element, orderOffset, ", Choice = ChoiceType.DatatypeChoice", fiveWs);
BuildFhirElementAttribute(name, summary, isModifier, element, orderOffset, "", fiveWs, since: since);
WriteFhirElementAttribute(name, summary, isModifier, element, orderOffset, ", Choice = ChoiceType.DatatypeChoice", fiveWs);
WriteFhirElementAttribute(name, summary, isModifier, element, orderOffset, "", fiveWs, since: since);
_writer.WriteLineIndented($"[DeclaredType(Type = typeof(ResourceReference), Since = FhirRelease.R4)]");
}
else
{
BuildFhirElementAttribute(name, summary, isModifier, element, orderOffset, choice, fiveWs, since, until, xmlSerialization);
WriteFhirElementAttribute(name, summary, isModifier, element, orderOffset, choice, fiveWs, since, until, xmlSerialization);
}

if (ei.PropertyType is CqlTypeReference ctr)
Expand All @@ -3077,6 +3075,22 @@ private void WriteElement(

if (_elementTypeChanges.TryGetValue(path, out ElementTypeChange[]? changes))
{
IEnumerable<TypeReference>? ats = changes.Select(c => c.DeclaredTypeReference);
_writer.WriteLineIndented("[CLSCompliant(false)]");
_writer.WriteLineIndented(BuildAllowedTypesAttribute(ats, null));

// Write comments for future improved AllowedTypesAttribute, with a Since
_writer.WriteIndentedComment(
"Attribute validation is not sensitive to FHIR version, so the next, more precise validations, will not work yet.",
isSummary: false, singleLine: true);
foreach(ElementTypeChange change in changes)
{
string allowedType = BuildAllowedTypesAttribute([change.DeclaredTypeReference], change.Since);
_writer.WriteIndentedComment(allowedType, isSummary: false, singleLine: true);
}

// Write the DeclaredTypes with the since, that will at least make sure
// the metadata for the property is correct for each version.
foreach(ElementTypeChange change in changes)
{
_writer.WriteIndented($"[DeclaredType(Type = typeof({change.DeclaredTypeReference.PropertyTypeString})");
Expand All @@ -3085,9 +3099,6 @@ private void WriteElement(
}
}

//if (element.cgIsSimple() && element.Type.Count == 1 && element.Type.Single().cgName() == "uri")
// _writer.WriteLineIndented("[UriPattern]");

bool notClsCompliant = !string.IsNullOrEmpty(allowedTypes) ||
!string.IsNullOrEmpty(resourceReferences);

Expand Down Expand Up @@ -3160,23 +3171,23 @@ private void WriteElement(
return $"{baseDescription}. {changedDescription}";
}

private static PrimitiveTypeReference BuildTypeReferenceForCode(DefinitionCollection info, ElementDefinition element, Dictionary<string, WrittenValueSetInfo> writtenValueSets)
private static (string? enumName, string? enumClass) GetVsInfoForCodedElement(DefinitionCollection info, ElementDefinition element, Dictionary<string, WrittenValueSetInfo> writtenValueSets)
{
if ((element.Binding?.Strength != Hl7.Fhir.Model.BindingStrength.Required) ||
(!info.TryExpandVs(element.Binding.ValueSet, out ValueSet? vs)) ||
_exclusionSet.Contains(vs.Url) ||
(_codedElementOverrides.Contains(element.Path) && info.FhirSequence >= FhirReleases.FhirSequenceCodes.R4) ||
!writtenValueSets.TryGetValue(vs.Url, out WrittenValueSetInfo vsInfo))
{
return PrimitiveTypeReference.GetTypeReference("code");
return (null, null);
}

string vsClass = vsInfo.ClassName;
string vsName = vsInfo.ValueSetName;

if (string.IsNullOrEmpty(vsClass))
{
return new CodedTypeReference(vsName, null);
return (vsName, null);
}

string pascal = element.cgName().ToPascalCase();
Expand All @@ -3187,7 +3198,7 @@ private static PrimitiveTypeReference BuildTypeReferenceForCode(DefinitionCollec
$"Change the name of the valueset '{vs.Url}' by adapting the _enumNamesOverride variable in the generator and rerun.");
}

return new CodedTypeReference(vsName, vsClass);
return (vsName, vsClass);
}

private static TypeReference DetermineTypeReferenceForFhirElement(
Expand Down Expand Up @@ -3216,21 +3227,12 @@ TypeReference determineTypeReferenceForFhirElementName()

string initialTypeName = getTypeNameFromElement();

// Elements that use multiple datatypes are of type DataType
// TODO: Probably need the list of types later to be able to render the
// AllowedTypes.
if (initialTypeName == "DataType")
return new ChoiceTypeReference();

// Elements of type Code or Code<T> have their own naming/types, so handle those separately.
if (initialTypeName == "code")
return BuildTypeReferenceForCode(info, element, writtenValueSets);
var (vsName,vsClass) = initialTypeName == "code"
? GetVsInfoForCodedElement(info, element, writtenValueSets)
: (null,null);

if (PrimitiveTypeReference.IsFhirPrimitiveType(initialTypeName))
return PrimitiveTypeReference.GetTypeReference(initialTypeName);

// Otherwise, this is a "normal" name for a complex type.
return new ComplexTypeReference(initialTypeName, getPocoNameForComplexTypeReference(initialTypeName));
return TypeReference.BuildFromFhirTypeName(initialTypeName, vsName, vsClass);

string getTypeNameFromElement()
{
Expand All @@ -3239,7 +3241,7 @@ string getTypeNameFromElement()
{
// TODO(ginoc): this should move into cgBaseTypeName();
// check to see if the referenced element has an explicit name
if (info.TryFindElementByPath(btn, out StructureDefinition? targetSd, out ElementDefinition? targetEd))
if (info.TryFindElementByPath(btn, out StructureDefinition? _, out ElementDefinition? targetEd))
{
return BuildTypeNameForNestedComplexType(targetEd, btn);
}
Expand All @@ -3251,13 +3253,6 @@ string getTypeNameFromElement()
? element.Type.First().cgName()
: "DataType";
}

string getPocoNameForComplexTypeReference(string name)
{
return name.Contains('.')
? BuildTypeNameForNestedComplexType(element, name)
: TypeReference.MapTypeName(name);
}
}
}

Expand Down Expand Up @@ -3463,22 +3458,6 @@ private static string MostGeneralValueAccessorType(PrimitiveTypeReference ptr)
/// <returns>A string.</returns>
private static string BuildTypeNameForNestedComplexType(ElementDefinition ed, string type)
{
// ginoc 2024.03.12: Release has happened and these are no longer needed - leaving here but commented out until confirmed
/*
// TODO: the following renames (repairs) should be removed when release 4B is official and there is an
// explicit name in the definition for attributes:
// - Statistic.attributeEstimate.attributeEstimate
// - Citation.contributorship.summary
if (type.StartsWith("Citation") || type.StartsWith("Statistic") || type.StartsWith("DeviceDefinition"))
{
string parentName = type.Substring(0, type.IndexOf('.'));
var sillyBackboneName = type.Substring(parentName.Length);
type = parentName + "." + capitalizeThoseSillyBackboneNames(sillyBackboneName) + "Component";
}
// end of repair
*/

string explicitTypeName = ed.cgExplicitName();

if (!string.IsNullOrEmpty(explicitTypeName))
Expand Down Expand Up @@ -3602,7 +3581,7 @@ internal static void BuildElementOptionalFlags(
// present in the current version of the standard. So, in principle, we don't generate
// this attribute in the base subset, unless all types mentioned are present in the
// exception list above.
bool isPrimitive(string name) => char.IsLower(name[0]);
static bool isPrimitive(string name) => char.IsLower(name[0]);
bool allTypesAvailable =
elementTypes.Keys.All(en =>
isPrimitive(en) // primitives are available everywhere
Expand All @@ -3613,44 +3592,15 @@ internal static void BuildElementOptionalFlags(

if (allTypesAvailable)
{
StringBuilder sb = new();
sb.Append("[AllowedTypes(");

bool needsSep = false;
foreach ((string etName, ElementDefinition.TypeRefComponent _) in elementTypes)
{
if (needsSep)
{
sb.Append(',');
}

needsSep = true;

sb.Append("typeof(");
sb.Append(Namespace);
sb.Append('.');

if (TypeNameMappings.TryGetValue(etName, out string? tmValue))
{
sb.Append(tmValue);
}
else
{
sb.Append(FhirSanitizationUtils.SanitizedToConvention(etName, NamingConvention.PascalCase));
}

sb.Append(')');
}

sb.Append(")]");
allowedTypes = sb.ToString();
IEnumerable<TypeReference> typeRefs = elementTypes.Values.Select(v => TypeReference.BuildFromFhirTypeName(v.Code));
allowedTypes = BuildAllowedTypesAttribute(typeRefs, null);
}
}
}

if (elementTypes.Any())
{
foreach ((string etName, ElementDefinition.TypeRefComponent elementType) in elementTypes.Where(kvp => (kvp.Key == "Reference") && kvp.Value.TargetProfile.Any()))
foreach ((string _, ElementDefinition.TypeRefComponent elementType) in elementTypes.Where(kvp => (kvp.Key == "Reference") && kvp.Value.TargetProfile.Any()))
{
resourceReferences = "[References(" +
string.Join(",", elementType.cgTargetProfiles().Keys.Select(name => "\"" + name + "\"")) +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
// </copyright>

using System.ComponentModel;
using System.Text;
using Hl7.Fhir.Model;
using Microsoft.Health.Fhir.CodeGen.FhirExtensions;
using Microsoft.Health.Fhir.CodeGenCommon.Extensions;
using Microsoft.Health.Fhir.CodeGenCommon.Packaging;
using Microsoft.Health.Fhir.CodeGenCommon.Utils;

#if NETSTANDARD2_0
using Microsoft.Health.Fhir.CodeGenCommon.Polyfill;
Expand Down Expand Up @@ -251,6 +255,21 @@ public static int GetOrder(int relativeOrder)
{
return (relativeOrder * 10) + 10;
}

public static string BuildAllowedTypesAttribute(IEnumerable<TypeReference> types, FhirReleases.FhirSequenceCodes? since)
{
StringBuilder sb = new();
sb.Append("[AllowedTypes(");

string typesList = string.Join(",",
types.Select(t => $"typeof({t.PropertyTypeString})"));

sb.Append(typesList);
if (since is not null)
sb.Append($", Since = FhirRelease.{since}");
sb.Append(")]");
return sb.ToString();
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3567,7 +3567,7 @@ private ExtensionData GetExtensionData(
Expression = "DataType",
},
ContextTarget = null,
ContextElementInfo = new("", "", "", new ChoiceTypeReference(), null),
ContextElementInfo = new("", "", "", ComplexTypeReference.DataTypeReference, null),
//ContextElementInfo = new CSharpFirely2.WrittenElementInfo()
//{
// ElementType = "Hl7.Fhir.Model.DataType",
Expand Down
15 changes: 13 additions & 2 deletions src/Microsoft.Health.Fhir.CodeGen/Language/Firely/TypeReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ namespace Microsoft.Health.Fhir.CodeGen.Language.Firely;

public abstract record TypeReference(string Name)
{
public static TypeReference BuildFromFhirTypeName(string name, string? vsName=null, string? vsClass=null)
{
// Elements of type Code or Code<T> have their own naming/types, so handle those separately.
if (name == "code" && vsName is not null)
return new CodedTypeReference(vsName, vsClass);

if (PrimitiveTypeReference.IsFhirPrimitiveType(name))
return PrimitiveTypeReference.GetTypeReference(name);

// Otherwise, this is a "normal" name for a complex type.
return new ComplexTypeReference(name, MapTypeName(name));
}

public abstract string PropertyTypeString { get; }

internal static string MapTypeName(string name)
Expand Down Expand Up @@ -103,8 +116,6 @@ public ComplexTypeReference(string name) : this(name, name) { }
public static readonly ComplexTypeReference DataTypeReference = new("DataType");
}

public record ChoiceTypeReference() : ComplexTypeReference("DataType");

public record CodedTypeReference(string EnumName, string? EnumClassName)
: PrimitiveTypeReference("code", EnumName, typeof(Enum))
{
Expand Down

0 comments on commit fc30f6f

Please sign in to comment.