diff --git a/.run/firely-all.run.xml b/.run/firely-all.run.xml new file mode 100644 index 000000000..7f737b051 --- /dev/null +++ b/.run/firely-all.run.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Health.Fhir.CodeGen/FhirExtensions/ElementDefinitionExtensions.cs b/src/Microsoft.Health.Fhir.CodeGen/FhirExtensions/ElementDefinitionExtensions.cs index 2bba61f4e..aba2ec179 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/FhirExtensions/ElementDefinitionExtensions.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/FhirExtensions/ElementDefinitionExtensions.cs @@ -78,7 +78,7 @@ public static int cgComponentFieldOrder(this ElementDefinition ed) public static string cgBasePath(this ElementDefinition ed) => ed.Base?.Path ?? string.Empty; /// Gets the explicit name of this element if set, or returns an empty string. - public static string cgExplicitName(this ElementDefinition ed) => ed.GetExtensionValue(CommonDefinitions.ExtUrlExplicitTypeName)?.ToString() ?? string.Empty; + public static string? cgExplicitName(this ElementDefinition ed) => ed.GetExtensionValue(CommonDefinitions.ExtUrlExplicitTypeName)?.ToString(); /// Gets the short name of this element, or explicit name if there is one. /// The ed to act on. @@ -89,12 +89,10 @@ public static string cgName(this ElementDefinition ed, bool allowExplicitName = { if (allowExplicitName) { - string en = ed.cgExplicitName(); + string? en = ed.cgExplicitName(); - if (!string.IsNullOrEmpty(en)) - { + if (en is not null) return en; - } } if (removeChoiceMarker && ed.Path.EndsWith("[x]", StringComparison.Ordinal)) diff --git a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirely2.cs b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirely2.cs index dd4d280db..6bdca6cae 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirely2.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirely2.cs @@ -4,16 +4,16 @@ // using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text; using Hl7.Fhir.Model; using Hl7.Fhir.Utility; using Microsoft.Health.Fhir.CodeGen.FhirExtensions; using Microsoft.Health.Fhir.CodeGen.Models; using Microsoft.Health.Fhir.CodeGen.Utils; using Microsoft.Health.Fhir.CodeGenCommon.FhirExtensions; +using Microsoft.Health.Fhir.CodeGenCommon.Models; using Microsoft.Health.Fhir.CodeGenCommon.Packaging; using Microsoft.Health.Fhir.CodeGenCommon.Utils; +using Ncqa.Cql.Model; using static Microsoft.Health.Fhir.CodeGen.Language.Firely.CSharpFirelyCommon; using static Microsoft.Health.Fhir.CodeGenCommon.Extensions.FhirNameConventionExtensions; @@ -32,7 +32,7 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput set => _generateHashesInsteadOfOutput = value; } - private Dictionary _fileHashes = []; + private readonly Dictionary _fileHashes = []; Dictionary IFileHashTestable.FileHashes => _fileHashes; /// (Immutable) Name of the language. @@ -43,9 +43,6 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput public Type ConfigType => typeof(FirelyGenOptions); - /// Gets the FHIR primitive type map. - public Dictionary FhirPrimitiveTypeMap => CSharpFirelyCommon.PrimitiveTypeMap; - /// Gets a value indicating whether this language is idempotent. public bool IsIdempotent => false; @@ -74,7 +71,7 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput private string _exportDirectory = string.Empty; /// Structures to skip generating. - internal static readonly HashSet _exclusionSet = + internal static readonly HashSet ExclusionSet = [ /* These two types are generated as interfaces, so require special handling and * are not to be treated as normal resources. */ @@ -100,15 +97,30 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput "http://hl7.org/fhir/ValueSet/mimetypes", ]; + private static readonly Dictionary<(string,string), VersionIndependentResourceTypesAll> _searchParamDefsTargetRemovals = new() + { + [("DiagnosticReport", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("RiskAssessment", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("List", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("VisionPrescription", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("ServiceRequest", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("Flag", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("Observation", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("NutritionOrder", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("Composition", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("DeviceRequest", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + [("Procedure", "encounter")] = VersionIndependentResourceTypesAll.EpisodeOfCare, + }; + /// /// List of types introduced in R5 that are retrospectively introduced in R3 and R4. /// - internal static readonly List _sharedR5DataTypes = + internal static readonly List SharedR5DataTypes = [ - new WrittenModelInfo { CsName = "BackboneType", FhirName = "BackboneType", IsAbstract = true }, - new WrittenModelInfo { CsName = "Base", FhirName = "Base", IsAbstract = true }, - new WrittenModelInfo { CsName = "DataType", FhirName = "DataType", IsAbstract = true }, - new WrittenModelInfo { CsName = "PrimitiveType", FhirName = "PrimitiveType", IsAbstract = true }, + new("BackboneType", "BackboneType", true), + new("Base", "Base",true), + new("DataType", "DataType", true), + new("PrimitiveType", "PrimitiveType", true), ]; /// @@ -116,6 +128,7 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput /// private static readonly List _baseSubsetComplexTypes = [ + "Address", "Attachment", "BackboneElement", "BackboneType", @@ -125,8 +138,10 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput "ContactPoint", "ContactDetail", "DataType", + "Duration", "Element", "Extension", + "HumanName", "Identifier", "Meta", "Narrative", @@ -134,6 +149,7 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput "PrimitiveType", "Quantity", "Range", + "Ratio", "Reference", "Signature", "UsageContext", @@ -182,10 +198,9 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput [ "http://hl7.org/fhir/ValueSet/publication-status", "http://hl7.org/fhir/ValueSet/FHIR-version", - - // Doesn't strictly need to be in base (but in conformance), - // but we used to generate it for base, so I am keeping it that way. + "http://hl7.org/fhir/ValueSet/search-param-type", "http://hl7.org/fhir/ValueSet/filter-operator", + "http://hl7.org/fhir/ValueSet/version-independent-all-resource-types" ]; /// @@ -195,7 +210,6 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput [ "http://hl7.org/fhir/ValueSet/capability-statement-kind", "http://hl7.org/fhir/ValueSet/binding-strength", - "http://hl7.org/fhir/ValueSet/search-param-type", // These are necessary for CapabilityStatement/CapabilityStatement2 // CapStat2 has disappeared in ballot, if that becomes final, @@ -212,38 +226,10 @@ bool IFileHashTestable.GenerateHashesInsteadOfOutput "http://hl7.org/fhir/ValueSet/codesystem-content-mode" ]; - /// - /// Information about special handling for specific value sets (for backwards compatibility of - /// generated code) - /// - internal record class ValueSetBehaviorOverrides - { - public required bool AllowShared { get; init; } - public required bool AllowInClasses { get; init; } - public HashSet ForceInClasses { get; init; } = []; - } - - /// - /// (Immutable) ValueSets that need special handling for backwards compatibility - /// - /// - /// Plan to remove in SDK v6.0 - /// - private static readonly Dictionary _valueSetBehaviorOverrides = new() - { - { "http://hl7.org/fhir/ValueSet/consent-data-meaning", new ValueSetBehaviorOverrides() { AllowShared = true, AllowInClasses = true, } }, - { "http://hl7.org/fhir/ValueSet/consent-provision-type", new ValueSetBehaviorOverrides() { AllowShared = true, AllowInClasses = true, }}, - { "http://hl7.org/fhir/ValueSet/encounter-status", new ValueSetBehaviorOverrides() { AllowShared = true, AllowInClasses = true, }}, - { "http://hl7.org/fhir/ValueSet/list-mode", new ValueSetBehaviorOverrides() { AllowShared = true, AllowInClasses = true, }}, - { "http://hl7.org/fhir/ValueSet/color-codes", new ValueSetBehaviorOverrides() { AllowShared = false, AllowInClasses = false, }}, - //{ "http://hl7.org/fhir/ValueSet/timezones", new ValueSetBehaviorOverrides() { AllowShared = true, ForceInClasses = ["Appointment"] }}, - //{ "http://hl7.org/fhir/ValueSet/timezones", new ValueSetBehaviorOverrides() { AllowShared = true, ForceInClasses = ["Appointment"] }}, - }; - /// /// List of all valuesets that we should publish as a shared Enum although there is only 1 reference to it. /// - internal static readonly List<(string, string)> _explicitSharedValueSets = + internal static readonly List<(string, string)> ExplicitSharedValueSets = [ // This enum should go to Template-binding.cs because otherwise it will introduce a breaking change. ("R4", "http://hl7.org/fhir/ValueSet/messageheader-response-request"), @@ -253,14 +239,13 @@ internal record class ValueSetBehaviorOverrides ("R5", "http://hl7.org/fhir/ValueSet/constraint-severity"), ]; - /// Gets the reserved words. /// The reserved words. private static readonly HashSet _reservedWords = []; - private static readonly Func SupportedResourcesFilter = wmi => !wmi.IsAbstract; - private static readonly Func FhirToCsFilter = wmi => !_excludeFromCsToFhir!.Contains(wmi.FhirName); - private static readonly Func CsToStringFilter = FhirToCsFilter; + private static readonly Func _supportedResourcesFilter = wmi => !wmi.IsAbstract; + private static readonly Func _fhirToCsFilter = wmi => !_excludeFromCsToFhir!.Contains(wmi.FhirName); + private static readonly Func _csToStringFilter = _fhirToCsFilter; private static readonly string[] _excludeFromCsToFhir = [ @@ -281,7 +266,7 @@ internal record class ValueSetBehaviorOverrides /// Some valuesets have names that are the same as element names or are just not nice - use this collection /// to change the name of the generated enum as required. /// - internal static readonly Dictionary _enumNamesOverride = new() + internal static readonly Dictionary EnumNamesOverride = new() { ["http://hl7.org/fhir/ValueSet/characteristic-combination"] = "CharacteristicCombinationCode", ["http://hl7.org/fhir/ValueSet/claim-use"] = "ClaimUseCode", @@ -295,7 +280,75 @@ internal record class ValueSetBehaviorOverrides ["http://hl7.org/fhir/ValueSet/fhir-types"] = "FHIRAllTypes" }; - private record SinceVersion(FhirReleases.FhirSequenceCodes Since); + private record ElementTypeChange(FhirReleases.FhirSequenceCodes Since, + TypeReference DeclaredTypeReference); + + private static readonly ElementTypeChange[] _stringToMarkdown = + [ + new(FhirReleases.FhirSequenceCodes.STU3, PrimitiveTypeReference.String), + new(FhirReleases.FhirSequenceCodes.R5, PrimitiveTypeReference.Markdown) + ]; + + /// + /// Given one of the versions, returns a string that describes from which version until which + /// version that change was in effect. + /// + private static string VersionChangeMessage(ElementTypeChange[] changeSet, ElementTypeChange thisChange, bool capitalize) + { + int index = Array.IndexOf(changeSet, thisChange); + if (index == -1) throw new ArgumentException("Change needs to be part of the set", nameof(thisChange)); + + if (index + 1 >= changeSet.Length) + { + // This is the last change in the set + return $"{(capitalize ? "S" : "s")}tarting from {thisChange.Since}"; + } + + int now = (int)thisChange.Since; + int next = (int)changeSet[index + 1].Since; + IEnumerable versionOrdinals = + Enumerable.Range(now, next - now).Cast(); + string versions = string.Join(", ", versionOrdinals.Select(v => v.ToString())); + versions = replaceLastOccurrence(versions, ", ", " and "); + return $"{(capitalize ? "I" : "i")}n {versions}"; + + static string replaceLastOccurrence(string source, string find, string replace) + { + int place = source.LastIndexOf(find, StringComparison.Ordinal); + + if (place == -1) + return source; + + return source.Remove(place, find.Length).Insert(place, replace); + } + } + + // ReSharper disable ArrangeObjectCreationWhenTypeNotEvident + private static readonly Dictionary _elementTypeChanges = new() + { + ["Attachment.size"] = [ + new(FhirReleases.FhirSequenceCodes.STU3, PrimitiveTypeReference.UnsignedInt), + new (FhirReleases.FhirSequenceCodes.R5, PrimitiveTypeReference.Integer64), + ], + ["Attachment.url"] = [ + new(FhirReleases.FhirSequenceCodes.STU3, PrimitiveTypeReference.Uri), + new(FhirReleases.FhirSequenceCodes.R4, PrimitiveTypeReference.Url), + ], + ["Meta.profile"] = [ + new(FhirReleases.FhirSequenceCodes.STU3, PrimitiveTypeReference.Uri), + new(FhirReleases.FhirSequenceCodes.R4, PrimitiveTypeReference.Canonical), + ], + ["Bundle.link.relation"] = [ + new(FhirReleases.FhirSequenceCodes.STU3, PrimitiveTypeReference.String), + new(FhirReleases.FhirSequenceCodes.R5, PrimitiveTypeReference.Code) + ], + + ["ElementDefinition.constraint.requirements"] = _stringToMarkdown, + ["ElementDefinition.binding.description"] = _stringToMarkdown, + ["ElementDefinition.mapping.comment"] = _stringToMarkdown, + ["CapabilityStatement.implementation.description"] = _stringToMarkdown, + }; + // ReSharper restore ArrangeObjectCreationWhenTypeNotEvident private readonly Dictionary _sinceAttributes = new() { @@ -388,22 +441,46 @@ private record SinceVersion(FhirReleases.FhirSequenceCodes Since); ["Signature.contentType"] = ("R4", "") }; + private static string? GetExplicitName(ElementDefinition ed, FhirReleases.FhirSequenceCodes sequence) => + (ed.Path, sequence) switch + { + ("Evidence.statistic.attributeEstimate.attributeEstimate", _) => "AttributeEstimate", + ("Citation.citedArtifact.contributorship.summary", _) => "CitedArtifactContributorshipSummary", + ("Measure.group", FhirReleases.FhirSequenceCodes.R6) => "GroupBackboneComponent", + ("Measure.group.component", FhirReleases.FhirSequenceCodes.R6) => "GroupComponent", + ("Measure.group.stratifier.component", FhirReleases.FhirSequenceCodes.R6) => "GroupStratifierComponent", + ("MolecularDefinition.location.sequenceLocation.coordinateInterval", FhirReleases.FhirSequenceCodes.R6) => + "LocationSequenceLocationCoordinateIntervalComponent", + ("MolecularDefinition.location.sequenceLocation.coordinateInterval.coordinateSystem", FhirReleases.FhirSequenceCodes.R6) => + "LocationSequenceLocationCoordinateIntervalCoordinateSystemComponent", + ("MolecularDefinition.representation.extracted.coordinateInterval", FhirReleases.FhirSequenceCodes.R6) => + "RepresentationExtractedCoordinateIntervalComponent", + ("MolecularDefinition.representation.extracted.coordinateInterval.coordinateSystem", FhirReleases.FhirSequenceCodes.R6) => + "RepresentationExtractedCoordinateIntervalCoordinateSystemComponent", + ("MolecularDefinition.representation.relative.edit.coordinateInterval", FhirReleases.FhirSequenceCodes.R6) => + "RepresentationRelativeEditCoordinateIntervalComponent", + ("MolecularDefinition.representation.relative.edit.coordinateInterval.coordinateSystem", FhirReleases.FhirSequenceCodes.R6) => + "RepresentationRelativeEditCoordinateIntervalCoordinateSystemComponent", + + ("TestPlan.scope", FhirReleases.FhirSequenceCodes.R6) => "ScopeComponent", + ("TestPlan.testCase.scope", FhirReleases.FhirSequenceCodes.R6) => "TestCaseScopeComponent", + _ => ed.cgExplicitName() + }; + /// True to export five ws. private bool _exportFiveWs = true; /// Gets the FHIR primitive type map. /// The FHIR primitive type map. - Dictionary ILanguage.FhirPrimitiveTypeMap => CSharpFirelyCommon.PrimitiveTypeMap; + Dictionary ILanguage.FhirPrimitiveTypeMap => PrimitiveTypeMap; /// If a Cql ModelInfo is available, this will be the parsed XML model file. private Ncqa.Cql.Model.ModelInfo? _cqlModelInfo = null; private IDictionary? _cqlModelClassInfo = null; /// Export the passed FHIR version into the specified directory. + /// /// The information. - /// Information describing the server. - /// Options for controlling the operation. - /// Directory to write files. public void Export(object untypedOptions, DefinitionCollection info) { if (untypedOptions is not FirelyGenOptions options) @@ -415,24 +492,31 @@ public void Export(object untypedOptions, DefinitionCollection info) // STU3 satellite is a combination of satellite and conformance if ((info.FhirSequence == FhirReleases.FhirSequenceCodes.STU3) && - (subset == CSharpFirelyCommon.GenSubset.Satellite)) + (subset == GenSubset.Satellite)) { - subset = CSharpFirelyCommon.GenSubset.Satellite | CSharpFirelyCommon.GenSubset.Conformance; + subset = GenSubset.Satellite | GenSubset.Conformance; } + // By definition, we should not have any element type changes for sattelites, they + // should only have their own, defined types from the spec. + if (subset.HasFlag(GenSubset.Satellite)) + _elementTypeChanges.Clear(); + // only generate base definitions for R5 - if (subset.HasFlag(GenSubset.Base) && info.FhirSequence != FhirReleases.FhirSequenceCodes.R5) + if (subset.HasFlag(GenSubset.Base) && + info.FhirSequence is not (FhirReleases.FhirSequenceCodes.R6 or FhirReleases.FhirSequenceCodes.R5)) { - Console.WriteLine($"Aborting {LanguageName} for {info.FhirSequence}: code generation for the 'base' subset should be run on R5 only."); + Console.WriteLine($"Aborting {LanguageName} for {info.FhirSequence}: code generation for the 'base' subset should be run on R5/R6 only."); return; } // conformance subset is only valid for STU3 and R5 if (subset.HasFlag(GenSubset.Conformance) && - (info.FhirSequence != FhirReleases.FhirSequenceCodes.STU3 && - info.FhirSequence != FhirReleases.FhirSequenceCodes.R5)) + info.FhirSequence is not (FhirReleases.FhirSequenceCodes.STU3 or + FhirReleases.FhirSequenceCodes.R5 or FhirReleases.FhirSequenceCodes.R6)) + { - Console.WriteLine($"Aborting {LanguageName} for {info.FhirSequence}: code generation for the 'conformance' subset should be run on STU3 or R5 only."); + Console.WriteLine($"Aborting {LanguageName} for {info.FhirSequence}: code generation for the 'conformance' subset should be run on STU3 or R5/R6 only."); return; } @@ -457,8 +541,8 @@ public void Export(object untypedOptions, DefinitionCollection info) string cqlModelResourceKey = options.CqlModel; if (!string.IsNullOrEmpty(cqlModelResourceKey)) { - _cqlModelInfo = Ncqa.Cql.Model.CqlModels.LoadEmbeddedResource(cqlModelResourceKey); - _cqlModelClassInfo = Ncqa.Cql.Model.CqlModels.ClassesByName(_cqlModelInfo); + _cqlModelInfo = CqlModels.LoadEmbeddedResource(cqlModelResourceKey); + _cqlModelClassInfo = CqlModels.ClassesByName(_cqlModelInfo); } var allPrimitives = new Dictionary(); @@ -485,36 +569,36 @@ public void Export(object untypedOptions, DefinitionCollection info) WriteGenerationComment(infoWriter); - if (options.ExportStructures.Contains(CodeGenCommon.Models.FhirArtifactClassEnum.ValueSet)) + if (options.ExportStructures.Contains(FhirArtifactClassEnum.ValueSet)) { WriteSharedValueSets(subset); } _modelWriter.WriteLineIndented("// Generated items"); - if (options.ExportStructures.Contains(CodeGenCommon.Models.FhirArtifactClassEnum.PrimitiveType)) + if (options.ExportStructures.Contains(FhirArtifactClassEnum.PrimitiveType)) { WritePrimitiveTypes(_info.PrimitiveTypesByName.Values, ref dummy, subset); } AddModels(allPrimitives, _info.PrimitiveTypesByName.Values); - if (options.ExportStructures.Contains(CodeGenCommon.Models.FhirArtifactClassEnum.ComplexType)) + if (options.ExportStructures.Contains(FhirArtifactClassEnum.ComplexType)) { WriteComplexDataTypes(_info.ComplexTypesByName.Values, ref dummy, subset); } AddModels(allComplexTypes, _info.ComplexTypesByName.Values); - AddModels(allComplexTypes, _sharedR5DataTypes); + AddModels(allComplexTypes, SharedR5DataTypes); - if (options.ExportStructures.Contains(CodeGenCommon.Models.FhirArtifactClassEnum.Resource)) + if (options.ExportStructures.Contains(FhirArtifactClassEnum.Resource)) { WriteResources(_info.ResourcesByName.Values, ref dummy, subset); } AddModels(allResources, _info.ResourcesByName.Values); - if (options.ExportStructures.Contains(CodeGenCommon.Models.FhirArtifactClassEnum.Interface)) + if (options.ExportStructures.Contains(FhirArtifactClassEnum.Interface)) { WriteInterfaces(_info.InterfacesByName.Values, ref dummy, subset); } @@ -559,16 +643,10 @@ private void ModifyDefinitionsForConsistency() // update the copied element to be the content element edContent.ElementId = "Binary.content"; edContent.Path = "Binary.content"; - edContent.Base = new() { Path = "Binary.content", Min = 0, Max = "1" }; + edContent.Base = new ElementDefinition.BaseComponent { Path = "Binary.content", Min = 0, Max = "1" }; edContent.cgSetFieldOrder(edData.cgFieldOrder(), edData.cgComponentFieldOrder()); - //edContent.RemoveExtension(CommonDefinitions.ExtUrlEdFieldOrder); - //edContent.RemoveExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder); - - //edContent.AddExtension(CommonDefinitions.ExtUrlEdFieldOrder, new Integer(edData.cgFieldOrder())); - //edContent.AddExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder, new Integer(edData.cgComponentFieldOrder())); - // add our element and track info, note that we are not increasing // the orders since they are duplicate elements from different versions _ = _info.TryInsertElement(sdBinary, edContent, false); @@ -641,12 +719,6 @@ private void ModifyDefinitionsForConsistency() edContentType.cgSetFieldOrder(7, 6); - //edContentType.RemoveExtension(CommonDefinitions.ExtUrlEdFieldOrder); - //edContentType.RemoveExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder); - - //edContentType.AddExtension(CommonDefinitions.ExtUrlEdFieldOrder, new Integer(6), true); - //edContentType.AddExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder, new Integer(6), true); - // add our element and track info _ = _info.TryInsertElement(sdSignature, edContentType, false); } @@ -675,13 +747,8 @@ private void ModifyDefinitionsForConsistency() edOnBehalfOf.Base.Path = "Signature.onBehalfOf[x]"; edOnBehalfOf.Type.Add(new() { Code = "uri" }); - int prevFO = edOnBehalfOf.cgFieldOrder(); - int prevCFO = edOnBehalfOf.cgComponentFieldOrder(); - // TODO: fix the order (should be 6th total, 5th in component) edOnBehalfOf.cgSetFieldOrder(6, 5); - - //_ = _info.TryUpdateElement(sdSignature, edOnBehalfOf, prevFO, prevCFO); } } @@ -719,12 +786,6 @@ private void ModifyDefinitionsForConsistency() edFocus.cgSetFieldOrder(123, 3); - //edFocus.RemoveExtension(CommonDefinitions.ExtUrlEdFieldOrder); - //edFocus.RemoveExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder); - - //edFocus.AddExtension(CommonDefinitions.ExtUrlEdFieldOrder, new Integer(123), true); - //edFocus.AddExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder, new Integer(3), true); - // TODO(ginoc): This insertion is currently pushing exclusionCriteria to Order=60 in file (componentOrder 5) - it should not // add our element and track info _ = _info.TryInsertElement(sdValueSet, edFocus, true); @@ -770,12 +831,6 @@ private void ModifyDefinitionsForConsistency() edXPath.cgSetFieldOrder(66, 8); } - //edXPath.RemoveExtension(CommonDefinitions.ExtUrlEdFieldOrder); - //edXPath.RemoveExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder); - - //edXPath.AddExtension(CommonDefinitions.ExtUrlEdFieldOrder, new Integer(65), true); - //edXPath.AddExtension(CommonDefinitions.ExtUrlEdComponentFieldOrder, new Integer(7), true); - // add our element and track info _ = _info.TryInsertElement(sdElementDefinition, edXPath, true); } @@ -806,20 +861,6 @@ private void ModifyDefinitionsForConsistency() _ = _info.TryInsertElement(sdRelatedArtifact, edUrl, true); } - // need to modify the summary status of narrative elements - remove this for SDK 6.0 - if (_info.ComplexTypesByName.TryGetValue("Narrative", out StructureDefinition? sdNarrative)) - { - if (sdNarrative.cgTryGetElementById("Narrative.status", out ElementDefinition? edStatus)) - { - edStatus.IsSummary = true; - } - - if (sdNarrative.cgTryGetElementById("Narrative.div", out ElementDefinition? edDiv)) - { - edDiv.IsSummary = true; - } - } - // correct issues in FHIR 6.0.0-ballot2 if ((_info.MainPackageId == "hl7.fhir.r6.core") && (_info.MainPackageVersion == "6.0.0-ballot2")) { @@ -870,6 +911,7 @@ private void WriteModelInfo( WriteGenerationComment(); _writer.WriteLineIndented("using System;"); + _writer.WriteLineIndented("using System.Collections;"); _writer.WriteLineIndented("using System.Collections.Generic;"); _writer.WriteLineIndented("using Hl7.Fhir.Introspection;"); _writer.WriteLineIndented("using Hl7.Fhir.Validation;"); @@ -890,14 +932,16 @@ private void WriteModelInfo( // open class OpenScope(); - WriteSupportedResources(writtenResources.Values.Where(SupportedResourcesFilter)); + WriteSupportedResources(writtenResources.Values.Where(_supportedResourcesFilter)); WriteFhirVersion(); - WriteFhirToCs(writtenPrimitives.Values.Where(FhirToCsFilter), writtenComplexTypes.Values.Where(FhirToCsFilter), writtenResources.Values.Where(FhirToCsFilter)); - WriteCsToString(writtenPrimitives.Values.Where(CsToStringFilter), writtenComplexTypes.Values.Where(CsToStringFilter), writtenResources.Values.Where(CsToStringFilter)); + WriteFhirToCs(writtenPrimitives.Values.Where(_fhirToCsFilter), writtenComplexTypes.Values.Where(_fhirToCsFilter), writtenResources.Values.Where(_fhirToCsFilter)); + WriteCsToString(writtenPrimitives.Values.Where(_csToStringFilter), writtenComplexTypes.Values.Where(_csToStringFilter), writtenResources.Values.Where(_csToStringFilter)); WriteSearchParameters(); + var dataTypes = writtenPrimitives.Concat(writtenComplexTypes).ToDictionary(we => we.Key, we => we.Value); + WriteOpenTypes(dataTypes); // close class CloseScope(); @@ -921,6 +965,30 @@ private void WriteModelInfo( } } + + private void WriteOpenTypes(Dictionary types) + { + _writer.WriteIndentedComment("The open types that are used in the FHIR model. These are types that can be used in the 'value' field of an Extension.", isSummary: true); + _writer.WriteIndentedComment("This list differs from the one in the written documentation (https://www.hl7.org/fhir/datatypes.html),\n" + + "but we assume the list in Extension.value[x] is the more authorative.", + isRemarks: true, isSummary: false); + _writer.WriteLineIndented("public static readonly Type[] OpenTypes ="); + OpenScope(); + + var extensionValue = _info.TryFindElementByPath("Extension.value[x]", + out StructureDefinition? _, + out ElementDefinition? edExtensionValue) + ? edExtensionValue.Type + : throw new InvalidOperationException("Could not find Extension.value[x] element in definitions"); + + foreach (var typeName in extensionValue.Select(ev => ev.Code).OrderBy(c => c)) + { + _writer.WriteLineIndented($"typeof({types[typeName].CsName}),"); + } + + CloseScope(includeSemicolon: true); + } + /// Writes the search parameters. private void WriteSearchParameters() { @@ -937,6 +1005,13 @@ private void WriteSearchParameters() foreach (SearchParameter sp in resourceSearchParams.Values.OrderBy(s => s.Name)) { + // TODO:R6: Add support for the 'resource' search parameter type + if ((sp.TypeElement.ObjectValue is "resource")) + { + Console.WriteLine($"Skipping SearchParameter {sp.Id} ({sp.Url}) because it is target type of 'resource'!!!"); + continue; + } + if (sp.Experimental == true) { continue; @@ -971,7 +1046,7 @@ private void WriteSearchParameters() .Split(_splitChars, StringSplitOptions.RemoveEmptyEntries) .Where(s => s.StartsWith(complex.Name + ".", StringComparison.Ordinal)); - path = "\"" + string.Join("\", \"", split) + "\", "; + path = "\"" + string.Join("\", \"", split) + "\""; } string target; @@ -982,12 +1057,8 @@ private void WriteSearchParameters() } else { - SortedSet sc = []; - - foreach (string t in sp.Target.Select(t => t.GetLiteral()!)) - { - sc.Add("ResourceType." + t); - } + List sc = + [..sp.Target.Where(t => t != null).Cast()]; // HACK: for http://hl7.org/fhir/SearchParameter/clinical-encounter, // none of the base resources have EpisodeOfCare as target, except @@ -1003,19 +1074,29 @@ private void WriteSearchParameters() { if (complex.Name != "Procedure" && complex.Name != "DeviceRequest") { - sc.Remove("ResourceType.EpisodeOfCare"); + sc.Remove(VersionIndependentResourceTypesAll.EpisodeOfCare); } } else { if (complex.Name != "DocumentReference") { - sc.Remove("ResourceType.EpisodeOfCare"); + sc.Remove(VersionIndependentResourceTypesAll.EpisodeOfCare); } } } - target = ", Target = new ResourceType[] { " + string.Join(", ", sc) + ", }"; + if (_info.FhirSequence != FhirReleases.FhirSequenceCodes.STU3) + { + if(_searchParamDefsTargetRemovals.TryGetValue((complex.Name,sp.Name), out var targetRemoval)) + { + sc.Remove(targetRemoval); + } + } + + var targetStrings = sc.Select(s => $"VersionIndependentResourceTypesAll.{s.GetLiteral()}") + .Order(); + target = $", Target = [{string.Join(", ", targetStrings)}]"; } string xpath = string.IsNullOrEmpty(sp.cgXPath()) ? string.Empty : ", XPath = \"" + sp.cgXPath() + "\""; @@ -1035,7 +1116,7 @@ private void WriteSearchParameters() $" Description = @\"{SanitizeForMarkdown(description)}\"," : $" Description = new Markdown(@\"{SanitizeForMarkdown(description)}\"),") + $" Type = SearchParamType.{searchType}," + - $" Path = new string[] {{ {path}}}" + + $" Path = [{path}]" + target + xpath + expression + @@ -1117,10 +1198,8 @@ private void WriteFhirToCs( /// Writes the FHIR version. private void WriteFhirVersion() { - _writer.WriteLineIndented("public static string Version"); - OpenScope(); - _writer.WriteLineIndented($"get {{ return \"{_info.FhirVersionLiteral}\"; }}"); - CloseScope(); + _writer.WriteLineIndented($"""public static string Version => "{_info.FhirVersionLiteral}";"""); + _writer.WriteLine(); } /// Writes the supported resources dictionary. @@ -1163,8 +1242,7 @@ private void WriteSharedValueSets(GenSubset subset) // traverse all versions of all value sets foreach ((string unversionedUrl, string[] versions) in _info.ValueSetVersions.OrderBy(kvp => kvp.Key)) { - if (_exclusionSet.Contains(unversionedUrl) || - (_valueSetBehaviorOverrides.TryGetValue(unversionedUrl, out ValueSetBehaviorOverrides? oddInfo) && (oddInfo.AllowShared == false))) + if (ExclusionSet.Contains(unversionedUrl)) { continue; } @@ -1177,13 +1255,7 @@ private void WriteSharedValueSets(GenSubset subset) continue; } - // we never want to write limited expansions - //if (vs.IsLimitedExpansion()) - //{ - // continue; - //} - - IEnumerable coreBindings = _info.CoreBindingsForVs(vs.Url); + IEnumerable coreBindings = _info.CoreBindingsForVs(vs.Url).ToList(); Hl7.Fhir.Model.BindingStrength? strongestBinding = _info.StrongestBinding(coreBindings); if (strongestBinding != Hl7.Fhir.Model.BindingStrength.Required) @@ -1198,7 +1270,7 @@ required bindings anywhere in the data model. */ IEnumerable referencedBy = coreBindings.cgExtractBaseTypes(_info); - if ((referencedBy.Count() < 2) && !_explicitSharedValueSets.Contains((_info.FhirSequence.ToString(), vs.Url))) + if ((referencedBy.Count() < 2) && !ExplicitSharedValueSets.Contains((_info.FhirSequence.ToString(), vs.Url))) { /* ValueSets that are used in a single POCO are generated as a nested enum inside that * POCO, not here in the shared valuesets */ @@ -1294,16 +1366,6 @@ private void WriteInterface( GenSubset subset) { string exportName = "I" + complex.Name.ToPascalCase(); - - //writtenModels.Add( - // complex.Name, - // new WrittenModelInfo() - // { - // FhirName = complex.Name, - // CsName = $"{Namespace}.{exportName}", - // IsAbstract = complex.Abstract == true, - // }); - string filename = Path.Combine(_exportDirectory, "Generated", $"{exportName}.cs"); _modelWriter.WriteLineIndented($"// {exportName}.cs"); @@ -1359,7 +1421,7 @@ private void WriteResources( { foreach (StructureDefinition complex in complexes.OrderBy(c => c.Name)) { - if (_exclusionSet.Contains(complex.Name)) + if (ExclusionSet.Contains(complex.Name)) { continue; } @@ -1386,12 +1448,8 @@ private void WriteResource( writtenModels.Add( complex.Name, - new WrittenModelInfo() - { - FhirName = complex.Name, - CsName = $"{Namespace}.{exportName}", - IsAbstract = complex.Abstract == true, - }); + new WrittenModelInfo(FhirName: complex.Name, CsName: $"{Namespace}.{exportName}", + IsAbstract: complex.Abstract == true)); string filename = Path.Combine(_exportDirectory, "Generated", $"{exportName}.cs"); @@ -1447,7 +1505,7 @@ private void WriteComplexDataTypes( { foreach (StructureDefinition complex in complexes.OrderBy(c => c.Name)) { - if (_exclusionSet.Contains(complex.Name)) + if (ExclusionSet.Contains(complex.Name)) { continue; } @@ -1479,12 +1537,8 @@ private void WriteComplexDataType( writtenModels.Add( complex.Name, - new WrittenModelInfo() - { - FhirName = complex.Name, - CsName = $"{Namespace}.{exportName}", - IsAbstract = complex.Abstract == true, - }); + new WrittenModelInfo(FhirName: complex.Name, CsName: $"{Namespace}.{exportName}", + IsAbstract: complex.Abstract == true)); string filename = Path.Combine(_exportDirectory, "Generated", $"{exportName}.cs"); @@ -1541,12 +1595,6 @@ private void WriteInterfaceComponent( WriteIndentedComment($"{complex.Element.Short}"); - //WriteSerializable(); - - string fhirTypeConstructor = $"\"{complexName}\",\"{complex.cgUrl()}\""; - - //_writer.WriteLineIndented($"[FhirType({fhirTypeConstructor}, IsResource=true)]"); - StructureDefinition? parentInterface = _info.GetParentInterface(complex.Structure); if (parentInterface == null) @@ -1567,29 +1615,6 @@ private void WriteInterfaceComponent( // open class OpenScope(); - // right now, no interfaces have components - TODO: determine naming convention if this comes up - //foreach (ComponentDefinition component in complex.cgChildComponents(_info)) - //{ - // string componentExportName; - // if (string.IsNullOrEmpty(component.cgExplicitName())) - // { - // componentExportName = - // $"{component.cgName(NamingConvention.PascalCase)}Component"; - // } - // else - // { - // // Consent.provisionActorComponent is explicit lower case... - // componentExportName = - // $"{component.cgExplicitName()}" + - // $"Component"; - // } - // WriteBackboneComponent( - // component, - // componentExportName, - // exportName, - // subset); - //} - WriteInterfaceElements(complex, exportName, ref exportedElements); // close class @@ -1623,7 +1648,7 @@ private void WriteInterfaceComponent( string pn = exportName + "." + interfaceEi.PropertyName; WrittenElementInfo? resourceEi = null; - if (resourceElements.TryGetValue(interfaceEi.FhirElementName ?? string.Empty, out ElementDefinition? resourceEd)) + if (resourceElements.TryGetValue(interfaceEi.FhirElementName, out ElementDefinition? resourceEd)) { resourceEi = BuildElementInfo(resourceExportName, resourceEd); } @@ -1648,65 +1673,28 @@ private void WriteInterfaceElementGettersAndSetters( string interfaceExportName, WrittenElementInfo interfaceEi) { - string pn = interfaceExportName + "." + interfaceEi.PropertyName; - string rt = resourceEi?.PropertyType.PropertyTypeString ?? string.Empty; - string it = interfaceEi.PropertyType.PropertyTypeString; + string pn = interfaceEi.PropertyName; + bool isList = interfaceEi.PropertyType is ListTypeReference; + string it = isList ? interfaceEi.PropertyType.PropertyTypeString : WithNullabilityMarking(interfaceEi.PropertyType.PropertyTypeString); if ((resourceEd == null) || (resourceEi == null)) { - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($"{it} {pn}"); - OpenScope(); - _writer.WriteLineIndented($"get {{ return null; }}"); - _writer.WriteLineIndented($"set {{ throw new NotImplementedException(\"Resource {resourceExportName} does not implement {interfaceExportName}.{interfaceEi.FhirElementName}\");}}"); - CloseScope(); + writeEmptyGetterAndSetter(it, pn, isList); } else if (interfaceEi.PropertyType.PropertyTypeString == resourceEi.PropertyType.PropertyTypeString) { - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($"{it} {pn}" + - $" {{" + - $" get => {resourceEi.PropertyName};" + - $" set {{ {resourceEi.PropertyName} = value; }}" + - $" }}"); - _writer.WriteLine(); + writeOneOnOneGetterAndSetter(it, pn, isList); } + // a resource is allowed to have a scalar in place of a list - else if ((interfaceEi.PropertyType is ListTypeReference interfaceLTR) && - (interfaceLTR.Element.PropertyTypeString == resourceEi.PropertyType.PropertyTypeString)) + else if ((interfaceEi.PropertyType is ListTypeReference interfaceLtr) && + (interfaceLtr.Element.PropertyTypeString == resourceEi.PropertyType.PropertyTypeString)) { - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($"{it} {pn}"); - OpenScope(); - //_writer.WriteLineIndented($"get {{ return new {it}() {{ {resourceEi.PropertyName} }}; }}"); - _writer.WriteLineIndented("get"); - OpenScope(); // getter - _writer.WriteLineIndented($"if ({resourceEi.PropertyName} == null) return new {it}();"); - _writer.WriteLineIndented($"return new {it}() {{ {resourceEi.PropertyName} }};"); - CloseScope(); // getter - - _writer.WriteLineIndented("set"); - OpenScope(); - _writer.WriteLineIndented($"if (value.Count == 0) {{ {resourceEi.PropertyName} = null; }}"); - _writer.WriteLineIndented($"else if (value.Count == 1) {{ {resourceEi.PropertyName} = value.First(); }}"); - _writer.WriteLineIndented($"else {{ throw new NotImplementedException(\"Resource {resourceExportName} can only have a single {pn} value\"); }}"); - CloseScope(); - - CloseScope(); + writeSingleToListGetterAndSetter(it, pn, isList); } else { - WriteIndentedComment( - $"{resourceExportName}.{resourceEi.PropertyName} ({resourceEi.PropertyType}) is incompatible with\n" + - $"{interfaceExportName}.{interfaceEi.FhirElementName} ({interfaceEi.PropertyType})", - isSummary: false, - isRemarks: true); - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($"{it} {pn}"); - OpenScope(); - _writer.WriteLineIndented($"get {{ return null; }}"); - _writer.WriteLineIndented($"set {{ throw new NotImplementedException(\"{resourceExportName}.{resourceEi.PropertyName} ({resourceEi.PropertyType}) is incompatible with {interfaceExportName}.{interfaceEi.FhirElementName} ({interfaceEi.PropertyType})\");}}"); - CloseScope(); + writeIncompatibleGetterSetter(it, pn, isList); } if (!TryGetPrimitiveType(interfaceEi.PropertyType, out PrimitiveTypeReference? interfacePtr)) @@ -1714,69 +1702,105 @@ private void WriteInterfaceElementGettersAndSetters( return; } - string ppn = interfaceExportName + "." + interfaceEi.PrimitiveHelperName; - string prt = (resourceEi?.PropertyType is PrimitiveTypeReference rPTR) ? rPTR.ConveniencePropertyTypeString : string.Empty; - string pit = interfacePtr.ConveniencePropertyTypeString; + string ppn = interfaceEi.PrimitiveHelperName!; + string pit = isList ? interfacePtr.ConveniencePropertyTypeString : WithNullabilityMarking(interfacePtr.ConveniencePropertyTypeString); if ((resourceEd == null) || (resourceEi == null)) { - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($"{pit} {ppn}"); - OpenScope(); - _writer.WriteLineIndented($"get {{ return null; }}"); - _writer.WriteLineIndented($"set {{ throw new NotImplementedException(\"Resource {resourceExportName} does not implement {interfaceExportName}.{interfaceEi.FhirElementName}\");}}"); - CloseScope(); + writeEmptyGetterAndSetter(pit, ppn, isList); } else if (interfaceEi.PropertyType == resourceEi.PropertyType) { + writeOneOnOneGetterAndSetter(pit, ppn, isList); + } + else + { + writeIncompatibleGetterSetter(pit, ppn, isList); + } + + return; + + void writeOneOnOneGetterAndSetter(string propertyType, string propertyName, bool allowNull) + { + if(allowNull) + _writer.WriteLineIndented("[AllowNull]"); _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($"{pit} {ppn}" + - $" {{" + - $" get => {resourceEi.PrimitiveHelperName};" + - $" set {{ {resourceEi.PrimitiveHelperName} = value; }}" + - $" }}"); - _writer.WriteLine(); + _writer.WriteLineIndented($"{propertyType} {interfaceExportName}.{propertyName}"); + OpenScope(); + _writer.WriteLineIndented($"get => {propertyName};"); + _writer.WriteLineIndented($"set => {propertyName} = value!;"); + CloseScope(); } - // a resource is allowed to have a scalar in place of a list - //else if (interfaceEi.PropertyType == "List<" + resourceEi.PropertyType + ">") - else if (interfaceEi.PropertyType is ListTypeReference) + + void writeSingleToListGetterAndSetter(string propertyType, string propertyName, bool allowNull) { + if (allowNull) + _writer.WriteLineIndented("[AllowNull]"); _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($"{pit} {ppn}"); + _writer.WriteLineIndented($"{propertyType} {interfaceExportName}.{propertyName}"); OpenScope(); - _writer.WriteLineIndented($"get {{ return new {pit}() {{ {resourceEi.PropertyType.PropertyTypeString} }}; }}"); + + _writer.WriteLineIndented($"get => {propertyName} is null ? [] : [{propertyName}];"); _writer.WriteLineIndented("set"); OpenScope(); - _writer.WriteLineIndented($"if (value.Count == 1) {{ {resourceEi.PrimitiveHelperName} = value.First(); }}"); - _writer.WriteLineIndented($"else {{ throw new NotImplementedException(\"Resource {resourceExportName} can only have a single {ppn} value\"); }}"); + _writer.WriteLineIndented($"{propertyName} = value switch"); + + OpenScope(); + _writer.WriteLineIndented($"{{ Count: 0 }} => null,"); + _writer.WriteLineIndented($"{{ Count: 1 }} => value.First(),"); + _writer.WriteLineIndented($"_ => throw new NotImplementedException(\"Resource {resourceExportName} can only have a single {propertyName} value\")"); + CloseScope(includeSemicolon: true, suppressNewline: true); + + CloseScope(suppressNewline: true); CloseScope(); + } + + void writeIncompatibleGetterSetter(string propertyType, string propertyName, bool allowNull) + { + string message = $"{resourceExportName}.{resourceEi.PropertyName} is incompatible with " + + $"{interfaceExportName}.{interfaceEi.FhirElementName}."; + WriteIndentedComment(message, isSummary: false, isRemarks: true); + + if (allowNull) + _writer.WriteLineIndented("[AllowNull]"); + _writer.WriteLineIndented("[IgnoreDataMember]"); + _writer.WriteLineIndented($"{propertyType} {interfaceExportName}.{propertyName}"); + OpenScope(); + _writer.WriteLineIndented($"get => {emptyInterfaceType()};"); + _writer.WriteLineIndented($"set => throw new NotImplementedException(\"{message}\");"); CloseScope(); } - else + + string emptyInterfaceType() => interfaceEi.PropertyType is ListTypeReference ? "[]" : "null"; + + void writeEmptyGetterAndSetter(string propertyType, string propertyName, bool allowNull) { - _writer.WriteLineIndented($"// {resourceExportName}.{resourceEi.PropertyName} ({prt}) is incompatible with {interfaceExportName}.{interfaceEi.FhirElementName} ({pit})"); + if (allowNull) + _writer.WriteLineIndented("[AllowNull]"); _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented($" {pit} {ppn}"); + _writer.WriteLineIndented($"{propertyType} {interfaceExportName}.{propertyName}"); OpenScope(); - _writer.WriteLineIndented($"get {{ return null; }}"); - _writer.WriteLineIndented($"set {{ throw new NotImplementedException(\"{resourceExportName}.{resourceEi.PropertyName} ({resourceEi.PropertyType.PropertyTypeString}) is incompatible with {interfaceExportName}.{interfaceEi.FhirElementName} ({interfaceEi.PropertyType.PropertyTypeString})\");}}"); + _writer.WriteLineIndented($"get => {emptyInterfaceType()};"); + _writer.WriteLineIndented($"set => throw new NotImplementedException(\"Resource {resourceExportName}" + + $" does not implement {interfaceExportName}.{interfaceEi.FhirElementName}\");"); CloseScope(); } } + private static string WithNullabilityMarking(string type) => type.EndsWith("?") ? type : type + "?"; + + private void WriteInterfaceElements( ComponentDefinition complex, string exportedComplexName, ref List exportedElements) { - var elementsToGenerate = complex.cgGetChildren() + IOrderedEnumerable elementsToGenerate = complex.cgGetChildren() .Where(e => !e.cgIsInherited(complex.Structure)) .OrderBy(e => e.cgFieldOrder()); - int orderOffset = complex.Element.cgFieldOrder(); - string structureName = complex.cgName(); foreach (ElementDefinition element in elementsToGenerate) @@ -1785,29 +1809,26 @@ private void WriteInterfaceElements( exportedElements.Add(ei); string name = element.cgName(removeChoiceMarker: true); - var since = _sinceAttributes.TryGetValue(element.Path, out string? s) ? s : null; - var until = _untilAttributes.TryGetValue(element.Path, out (string, string) u) ? u : default((string, string)?); + string? since = _sinceAttributes.GetValueOrDefault(element.Path); + (string, string)? until = _untilAttributes.TryGetValue(element.Path, out (string, string) u) ? u : default((string, string)?); - var description = AttributeDescriptionWithSinceInfo(name, element.Short.Replace("{{title}}", structureName), since, until); + string? description = MakeAttributeRemarkForNewOrDeprecatedProperties(name, element.Short.Replace("{{title}}", structureName), since, until); - if (TryGetPrimitiveType(ei.PropertyType, out PrimitiveTypeReference? eiPTR)) + if (TryGetPrimitiveType(ei.PropertyType, out PrimitiveTypeReference? eiPtr)) { WriteIndentedComment(element.Short.Replace("{{title}}", structureName)); _writer.WriteLineIndented($"/// This uses the native .NET datatype, rather than the FHIR equivalent"); - _writer.WriteLineIndented($"{eiPTR.ConveniencePropertyTypeString} {ei.PrimitiveHelperName} {{ get; set; }}"); + + _writer.WriteLineIndented($"{WithNullabilityMarking(eiPtr.ConveniencePropertyTypeString)} {ei.PrimitiveHelperName} {{ get; set; }}"); _writer.WriteLine(); } - //if (ei.IsPrimitive) - //{ - // WriteIndentedComment(element.Short.Replace("{{title}}", structureName)); - // _writer.WriteLineIndented($"/// This uses the native .NET datatype, rather than the FHIR equivalent"); - // _writer.WriteLineIndented($"{ei.PrimitiveHelperType?.Replace("Hl7.Fhir.Model.", string.Empty) ?? string.Empty} {ei.PrimitiveHelperName} {{ get; set; }}"); - // _writer.WriteLine(); - //} - if (description != null) WriteIndentedComment(description); - _writer.WriteLineIndented($"{ei.PropertyType.PropertyTypeString ?? string.Empty} {ei.PropertyName} {{ get; set; }}"); + var typ = ei.PropertyType is ListTypeReference + ? ei.PropertyType.PropertyTypeString + : WithNullabilityMarking(ei.PropertyType.PropertyTypeString); + _writer.WriteLineIndented("[AllowNull]"); + _writer.WriteLineIndented($"{typ} {ei.PropertyName} {{ get; set; }}"); _writer.WriteLine(); } } @@ -1878,15 +1899,7 @@ private void WriteComponent( WriteSerializable(); string fhirTypeConstructor = $"\"{complexName}\",\"{complex.cgUrl()}\""; - - if (isResource) - { - _writer.WriteLineIndented($"[FhirType({fhirTypeConstructor}, IsResource=true)]"); - } - else - { - _writer.WriteLineIndented($"[FhirType({fhirTypeConstructor})]"); - } + _writer.WriteLineIndented($"[FhirType({fhirTypeConstructor})]"); var isPatientClass = false; @@ -1919,18 +1932,19 @@ private void WriteComponent( if (identifierElement.cgIsArray()) interfaces.Add("IIdentifiable>"); else - interfaces.Add("IIdentifiable"); + interfaces.Add("IIdentifiable"); } } - var primaryCodeElementInfo = isResource ? getPrimaryCodedElementInfo(complex, exportName) : null; + WrittenElementInfo? primaryCodeElementInfo = isResource ? getPrimaryCodedElementInfo(complex, exportName) : null; if (primaryCodeElementInfo != null) { - interfaces.Add($"ICoded<{primaryCodeElementInfo.PropertyType.PropertyTypeString}>"); + string nullable = primaryCodeElementInfo.PropertyType is ListTypeReference ? "" : "?"; + interfaces.Add($"ICoded<{primaryCodeElementInfo.PropertyType.PropertyTypeString}{nullable}>"); } - var modifierElement = complex.cgGetChild("modifierExtension"); + ElementDefinition? modifierElement = complex.cgGetChild("modifierExtension"); if (modifierElement != null) { if (!modifierElement.cgIsInherited(complex.Structure)) @@ -1954,7 +1968,8 @@ private void WriteComponent( // open class OpenScope(); - WritePropertyTypeName(complex.cgName()); + if(complex.Structure.Abstract != true) + WritePropertyTypeName(complex.cgName()); string validationRegEx = complex.cgValidationRegEx(); if (!string.IsNullOrEmpty(validationRegEx)) @@ -1975,16 +1990,14 @@ private void WriteComponent( { string componentExportName; - if (string.IsNullOrEmpty(component.cgExplicitName())) + if (GetExplicitName(component.Element, _info.FhirSequence) is {} explicitName) { - componentExportName = - $"{component.cgName(NamingConvention.PascalCase)}Component"; + componentExportName = $"{explicitName}Component"; } else { componentExportName = - $"{component.cgExplicitName()}" + - $"Component"; + $"{component.cgName(NamingConvention.PascalCase)}Component"; } WriteBackboneComponent( @@ -1999,17 +2012,24 @@ private void WriteComponent( if (identifierElement != null) { if (identifierElement.cgIsArray()) - _writer.WriteLineIndented("List IIdentifiable>.Identifier { get => Identifier; set => Identifier = value; }"); + _writer.WriteLineIndented("List IIdentifiable>.Identifier { get => Identifier; set => Identifier = value!; }"); else - _writer.WriteLineIndented("Identifier IIdentifiable.Identifier { get => Identifier; set => Identifier = value; }"); + _writer.WriteLineIndented("Identifier? IIdentifiable.Identifier { get => Identifier; set => Identifier = value!; }"); _writer.WriteLine(string.Empty); } if (primaryCodeElementInfo != null) { - _writer.WriteLineIndented($"{primaryCodeElementInfo.PropertyType.PropertyTypeString} ICoded<{primaryCodeElementInfo.PropertyType.PropertyTypeString}>.Code {{ get => {primaryCodeElementInfo.PropertyName}; set => {primaryCodeElementInfo.PropertyName} = value; }}"); - _writer.WriteLineIndented($"IEnumerable ICoded.ToCodings() => {primaryCodeElementInfo.PropertyName}.ToCodings();"); + var (codedType, bang) = primaryCodeElementInfo.PropertyType switch + { + ListTypeReference { PropertyTypeString: { } n } => (n, string.Empty), + { PropertyTypeString: {} n } => (WithNullabilityMarking(n), "!") + }; + + _writer.WriteLineIndented($"{codedType} ICoded<{codedType}>.Code {{ get => {primaryCodeElementInfo.PropertyName}; " + + $"set => {primaryCodeElementInfo.PropertyName} = value{bang}; }}"); + _writer.WriteLineIndented($"IReadOnlyCollection ICoded.ToCodings() => {primaryCodeElementInfo.PropertyName}?.ToCodings() ?? [];"); _writer.WriteLine(string.Empty); } @@ -2019,7 +2039,7 @@ private void WriteComponent( if (birthdayProperty != null) { - _writer.WriteLineIndented($"Hl7.Fhir.Model.Date {Namespace}.IPatient.BirthDate => {birthdayProperty.PropertyName};"); + _writer.WriteLineIndented($"Hl7.Fhir.Model.Date? {Namespace}.IPatient.BirthDate => {birthdayProperty.PropertyName};"); _writer.WriteLine(string.Empty); } } @@ -2031,12 +2051,8 @@ private void WriteComponent( WriteDeepCopy(exportName); } - WriteMatches(exportName, exportedElements); - WriteIsExactly(exportName, exportedElements); - WriteChildren(exportName, exportedElements); - WriteNamedChildren(exportName, exportedElements); - - WriteIDictionarySupport(exportName, exportedElements); + WriteCompareChildren(exportName, exportedElements); + WriteDictionarySupport(exportName, exportedElements); // close class CloseScope(); @@ -2093,23 +2109,27 @@ private string DetermineExportedBaseTypeName(string baseTypeName) return baseTypeName; } - private void WriteIDictionarySupport(string exportName, IEnumerable exportedElements) + private string getDynamicTypeForAbstractTypeName(string abstractTypeName) => + abstractTypeName switch + { + "Hl7.Fhir.Model.Resource" => "DynamicResource", + "Hl7.Fhir.Model.DataType" => "DynamicDataType", + "Hl7.Fhir.Model.PrimitiveType" => "DynamicPrimitive", + { } s => s + }; + + private void WriteDictionarySupport(string exportName, List exportedElements) { WriteDictionaryTryGetValue(exportName, exportedElements); + WriteDictionaryTrySetValue(exportName, exportedElements); WriteDictionaryPairs(exportName, exportedElements); } - - private string NullCheck(string propertyName, bool isList) => - propertyName + (isList ? "?.Any() == true" : " is not null"); - - private void WriteDictionaryPairs(string exportName, IEnumerable exportedElements) + private void WriteDictionaryPairs(string exportName, List exportedElements) { - // Base implementation differs from subclasses. + // Base implementation differs from subclasses and is hand-written code in a separate partical class if (exportName == "Base") { - _writer.WriteLineIndented("protected virtual IEnumerable> GetElementPairs() => Enumerable.Empty>();"); - _writer.WriteLine(string.Empty); return; } @@ -2118,32 +2138,27 @@ private void WriteDictionaryPairs(string exportName, IEnumerable> GetElementPairs()"); + _writer.WriteLineIndented("public override IEnumerable> EnumerateElements()"); OpenScope(); - _writer.WriteLineIndented("foreach (var kvp in base.GetElementPairs()) yield return kvp;"); + _writer.WriteLineIndented("foreach (var kvp in base.EnumerateElements()) yield return kvp;"); foreach (WrittenElementInfo info in exportedElements) { string elementProp = $"\"{info.FhirElementName}\""; - _writer.WriteLineIndented($"if ({NullCheck(info.PropertyName, info.PropertyType is ListTypeReference)}) yield return new " + - $"KeyValuePair({elementProp},{info.PropertyName});"); + _writer.WriteLineIndented($"if (_{info.PropertyName}{(info.PropertyType is ListTypeReference ? "?.Any() is true" : " is not null")} && !_{info.PropertyName}.InOverflow<{getDynamicTypeForAbstractTypeName(info.PropertyType.PropertyTypeString)}>()) yield return new " + + $"KeyValuePair({elementProp},_{info.PropertyName});"); } CloseScope(); } - private void WriteDictionaryTryGetValue(string exportName, IEnumerable exportedElements) + private void WriteDictionaryTryGetValue(string exportName, List exportedElements) { - // Base implementation differs from subclasses. - if (exportName == "Base") - { - _writer.WriteLineIndented("protected virtual bool TryGetValue(string key, out object value)"); - OpenScope(); - _writer.WriteLineIndented("value = default;"); - _writer.WriteLineIndented("return false;"); - CloseScope(); - return; - } + // Base implementation differs from subclasses and is hand-written code in a separate partial class + if (exportName == "Base") + { + return; + } // Don't override anything if there are no additional elements. if (!exportedElements.Any()) @@ -2151,7 +2166,7 @@ private void WriteDictionaryTryGetValue(string exportName, IEnumerable())"); + _writer.OpenScope(); + _writer.WriteLineIndented($"value = Overflow[\"{key}\"];"); + _writer.WriteLineIndented("return true;"); + _writer.CloseScope(); + _writer.WriteLineIndented($"value = _{propName};"); + _writer.WriteLineIndented($"return (value as {type.PropertyTypeString}){(type is ListTypeReference ? "?.Any() is true" : " is not null")};"); _writer.DecreaseIndent(); } @@ -2188,26 +2210,12 @@ void writeCase(string key, string propName, bool isList) void writeBaseTryGetValue() => _writer.WriteLineIndented("return base.TryGetValue(key, out value);"); } - /// Writes the children of this item. - /// Name of the exported class. - /// The exported elements. - private void WriteNamedChildren(string exportName, - List exportedElements) + + private void WriteDictionaryTrySetValue(string exportName, List exportedElements) { - // Base implementation differs from subclasses. + // Base implementation differs from subclasses and is hand-written code in a separate partical class if (exportName == "Base") { - _writer.WriteIndentedComment(""" - Enumerate all child nodes. - Return a sequence of child elements, components and/or properties. - Child nodes are returned as tuples with the name and the node itself, in the order defined - by the FHIR specification. - First returns child nodes inherited from any base class(es), recursively. - Finally returns child nodes defined by the current class. - """); - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented("public virtual IEnumerable NamedChildren => Enumerable.Empty();"); - _writer.WriteLine(string.Empty); return; } @@ -2217,208 +2225,106 @@ Finally returns child nodes defined by the current class. return; } - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented("public override IEnumerable NamedChildren"); - + _writer.WriteLineIndented("public override Base SetValue(string key, object? value)"); OpenScope(); - _writer.WriteLineIndented("get"); + + _writer.WriteLineIndented("if(value is not (null or Hl7.Fhir.Model.Base or IList)) throw new ArgumentException(\"Value must be a Base or a list of Base\", nameof(value));"); + + // switch + _writer.WriteLineIndented("switch (key)"); OpenScope(); - _writer.WriteLineIndented($"foreach (var item in base.NamedChildren) yield return item;"); foreach (WrittenElementInfo info in exportedElements) { - if (info.PropertyType is ListTypeReference) - { - _writer.WriteLineIndented( - $"foreach (var elem in {info.PropertyName})" + - $" {{ if (elem != null)" + - $" yield return new ElementValue(\"{info.FhirElementName}\", elem);" + - $" }}"); - } - else - { - string yr = NamedChildrenFhirTypeWrapper(info); + writeSetValueCase(info.FhirElementName, null, info.PropertyType, info.PropertyName, info.Required); + } - _writer.WriteLineIndented( - $"if ({info.PropertyName} != null)" + - $" yield return new ElementValue(\"{info.FhirElementName}\", {yr});"); - } + void writeSetValueCase(string fhirName, string? when, TypeReference type, string propName, bool required) + { + string overflowTypeName = getDynamicTypeForAbstractTypeName(type.PropertyTypeString); + + _writer.WriteLineIndented(when is not null ? $"case \"{fhirName}\" when {when}:" : $"case \"{fhirName}\":"); + + _writer.IncreaseIndent(); + _writer.WriteLineIndented($"if (value is not ({type.PropertyTypeString} or null))"); + _writer.OpenScope(); + _writer.WriteLineIndented($"{propName} = OverflowNull<{overflowTypeName}>.INSTANCE;"); + _writer.WriteLineIndented($"Overflow[\"{fhirName}\"] = value;"); + _writer.CloseScope(); + + // Because our list properties are never null when you get them, but can be set to null, + // we need a bang after the assignment here for lists, but not for other elements. + _writer.WriteLineIndented($"else {propName} = ({type.PropertyTypeString}?)value{(type is ListTypeReference || required ? "!" : "")};"); + _writer.WriteLineIndented($"return this;"); + _writer.DecreaseIndent(); } - CloseScope(suppressNewline: true); - CloseScope(); - } + _writer.WriteLineIndented("default:"); + _writer.IncreaseIndent(); + writeBaseTrySetValue(); - // For a limited set of exceptional elements, the Children functions return a - // complex FHIR type wrapper. - private static string NamedChildrenFhirTypeWrapper(WrittenElementInfo info) - { + _writer.DecreaseIndent(); - return info.FhirElementPath switch - { - "Narrative.div" => $"new FhirString({info.PropertyName}.Value)", - "Element.id" => $"new FhirString({info.PropertyName})", - "Extension.url" => $"new FhirUri({info.PropertyName})", - _ => $"{info.PropertyName}" - }; + // end switch + CloseScope(includeSemicolon: false); + + CloseScope(); + + void writeBaseTrySetValue() => _writer.WriteLineIndented("return base.SetValue(key, value);"); } - /// Writes the children of this item. - /// Name of the exported class. + /// Writes the PairwiseEquality. + /// Name of the export. /// The exported elements. - private void WriteChildren(string exportName, + private void WriteCompareChildren( + string exportName, List exportedElements) { - // Base implementation differs from subclasses. + // Base implementation is hand-written code in a separate partial class. if (exportName == "Base") - { - _writer.WriteIndentedComment( - """ - Enumerate all child nodes. - Return a sequence of child elements, components and/or properties. - Child nodes are returned in the order defined by the FHIR specification. - First returns child nodes inherited from any base class(es), recursively. - Finally returns child nodes defined by the current class. - """); - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented("public virtual IEnumerable Children => Enumerable.Empty();"); - _writer.WriteLine(string.Empty); - return; - } - - // Don't override anything if there are no additional elements. - if (!exportedElements.Any()) { return; } - _writer.WriteLineIndented("[IgnoreDataMember]"); - _writer.WriteLineIndented("public override IEnumerable Children"); + _writer.WriteLineIndented("public override bool CompareChildren(Base other, IEqualityComparer comparer)"); OpenScope(); - _writer.WriteLineIndented("get"); - OpenScope(); - _writer.WriteLineIndented($"foreach (var item in base.Children) yield return item;"); + _writer.WriteLineIndented($"if(other is not {exportName} otherT) return false;"); + _writer.WriteLine(string.Empty); + + _writer.WriteLineIndented("if(!base.CompareChildren(otherT, comparer)) return false;"); + + _writer.WriteLineIndented( + "#pragma warning disable CS8604 // Possible null reference argument - netstd2.1 has a wrong nullable signature here"); foreach (WrittenElementInfo info in exportedElements) { - if (info.PropertyType is ListTypeReference) + if(info.PropertyType is CqlTypeReference) { _writer.WriteLineIndented( - $"foreach (var elem in {info.PropertyName})" + - $" {{ if (elem != null) yield return elem; }}"); + $"if( _{info.PropertyName} != otherT._{info.PropertyName} )" + + $" return false;"); + } + else if (info.PropertyType is ListTypeReference) + { + _writer.WriteLineIndented( + $"if(!comparer.ListEquals(_{info.PropertyName}, otherT._{info.PropertyName}))" + + $" return false;"); } else { - string yr = NamedChildrenFhirTypeWrapper(info); _writer.WriteLineIndented( - $"if ({info.PropertyName} != null)" + - $" yield return {yr};"); + $"if(!comparer.Equals(_{info.PropertyName}, otherT._{info.PropertyName}))" + + $" return false;"); } } - CloseScope(suppressNewline: true); - CloseScope(); - } - - /// Writes the matches. - /// Name of the exported class. - /// The exported elements. - private void WriteMatches( - string exportName, - List exportedElements) - { - _writer.WriteLineIndented("///"); + _writer.WriteLineIndented("#pragma warning restore CS8604 // Possible null reference argument."); + _writer.WriteLine(string.Empty); - // Base implementation differs from subclasses. - if (exportName == "Base") + if (exportName == "PrimitiveType") { - _writer.WriteLineIndented("public virtual bool Matches(IDeepComparable other) => other is Base;"); - _writer.WriteLine(string.Empty); - return; - } - - if (exportName == "PrimitiveType") - { - _writer.WriteLineIndented("public override bool Matches(IDeepComparable other) => IsExactly(other);"); - _writer.WriteLine(string.Empty); - return; - } - - _writer.WriteLineIndented("public override bool Matches(IDeepComparable other)"); - OpenScope(); - _writer.WriteLineIndented($"var otherT = other as {exportName};"); - _writer.WriteLineIndented("if(otherT == null) return false;"); - _writer.WriteLine(string.Empty); - _writer.WriteLineIndented("if(!base.Matches(otherT)) return false;"); - - foreach (WrittenElementInfo info in exportedElements) - { - if (info.PropertyType is CqlTypeReference) - { - _writer.WriteLineIndented( - $"if( {info.PropertyName} != otherT.{info.PropertyName} )" + - $" return false;"); - } - else - _writer.WriteLineIndented( - $"if( !DeepComparable.Matches({info.PropertyName}, otherT.{info.PropertyName}))" + - $" return false;"); - } - - _writer.WriteLine(string.Empty); - _writer.WriteLineIndented("return true;"); - - CloseScope(); - } - - /// Writes the is exactly. - /// Name of the export. - /// The exported elements. - private void WriteIsExactly( - string exportName, - List exportedElements) - { - // Base implementation differs from subclasses. - if (exportName == "Base") - { - _writer.WriteLineIndented("public virtual bool IsExactly(IDeepComparable other) => other is Base;"); - _writer.WriteLine(string.Empty); - return; - } - - _writer.WriteLineIndented("public override bool IsExactly(IDeepComparable other)"); - - OpenScope(); - _writer.WriteLineIndented($"var otherT = other as {exportName};"); - _writer.WriteLineIndented("if(otherT == null) return false;"); - _writer.WriteLine(string.Empty); - - _writer.WriteLineIndented("if(!base.IsExactly(otherT)) return false;"); - - foreach (WrittenElementInfo info in exportedElements) - { - _writer.WriteLineIndented( - info.PropertyType is CqlTypeReference - ? $"if({info.PropertyName} != otherT.{info.PropertyName}) return false;" - : $"if( !DeepComparable.IsExactly({info.PropertyName}, otherT.{info.PropertyName}))" + - $" return false;"); - } - - _writer.WriteLine(string.Empty); - - if (exportName == "PrimitiveType") - { - _writer.WriteLineIndented("var otherValue = otherT.ObjectValue;"); - - _writer.WriteLineIndented("if (ObjectValue is byte[] bytes && otherValue is byte[] bytesOther)"); - _writer.IncreaseIndent(); - _writer.WriteLineIndented("return Enumerable.SequenceEqual(bytes, bytesOther);"); - _writer.DecreaseIndent(); - _writer.WriteLineIndented("else"); - _writer.IncreaseIndent(); - _writer.WriteLineIndented("return Equals(ObjectValue, otherT.ObjectValue);"); - _writer.DecreaseIndent(); + _writer.WriteLineIndented("return Equals(JsonValue, otherT.JsonValue);"); _writer.WriteLine(string.Empty); } else @@ -2437,15 +2343,13 @@ private void WriteCopyTo( List exportedElements) { var specifier = exportName == "Base" ? "virtual" : "override"; - _writer.WriteLineIndented($"public {specifier} IDeepCopyable CopyTo(IDeepCopyable other)"); - OpenScope(); - _writer.WriteLineIndented($"var dest = other as {exportName};"); - _writer.WriteLine(string.Empty); - - _writer.WriteLineIndented("if (dest == null)"); + _writer.WriteLineIndented($"protected internal {specifier} void CopyToInternal(Base other)"); OpenScope(); + _writer.WriteLineIndented($"if(other is not {exportName} dest)"); + _writer.IncreaseIndent(); _writer.WriteLineIndented("throw new ArgumentException(\"Can only copy to an object of the same type\", \"other\");"); - CloseScope(); + _writer.DecreaseIndent(); + _writer.WriteLine(); if (exportName == "Base") { @@ -2454,10 +2358,14 @@ private void WriteCopyTo( _writer.WriteLineIndented("dest.annotations.AddRange(annotations);"); _writer.DecreaseIndent(); _writer.WriteLine(string.Empty); + _writer.WriteLineIndented("if (HasOverflow)"); + _writer.IncreaseIndent(); + _writer.WriteLineIndented("Overflow.CopyToInternal(dest.Overflow);"); + _writer.DecreaseIndent(); } else { - _writer.WriteLineIndented("base.CopyTo(dest);"); + _writer.WriteLineIndented("base.CopyToInternal(dest);"); } foreach (WrittenElementInfo info in exportedElements) @@ -2465,23 +2373,21 @@ private void WriteCopyTo( if (info.PropertyType is ListTypeReference) { _writer.WriteLineIndented( - $"if({info.PropertyName}.Any())" + - $" dest.{info.PropertyName} = new {info.PropertyType.PropertyTypeString}({info.PropertyName}.DeepCopy());"); + $"if(_{info.PropertyName} is not null)" + + $" dest.{info.PropertyName} = new {info.PropertyType.PropertyTypeString}(_{info.PropertyName}.DeepCopyInternal());"); } else { _writer.WriteLineIndented( - $"if({info.PropertyName} != null) dest.{info.PropertyName} = " + + $"if(_{info.PropertyName} is not null) dest.{info.PropertyName} = " + (info.PropertyType is CqlTypeReference ? - $"{info.PropertyName};" : - $"({info.PropertyType.PropertyTypeString}){info.PropertyName}.DeepCopy();")); + $"_{info.PropertyName};" : + $"({info.PropertyType.PropertyTypeString})_{info.PropertyName}.DeepCopyInternal();")); } } if (exportName == "PrimitiveType") - _writer.WriteLineIndented("if (ObjectValue != null) dest.ObjectValue = ObjectValue;"); - - _writer.WriteLineIndented("return dest;"); + _writer.WriteLineIndented("if (JsonValue != null) dest.JsonValue = JsonValue;"); CloseScope(); } @@ -2494,17 +2400,16 @@ private void WriteDeepCopy( // Base implementation differs from subclasses. if (exportName == "Base") { - _writer.WriteLineIndented("public virtual IDeepCopyable DeepCopy() =>"); - _writer.IncreaseIndent(); - _writer.WriteLineIndented("CopyTo((IDeepCopyable)Activator.CreateInstance(GetType())!);"); - _writer.DecreaseIndent(); + _writer.WriteLineIndented("protected internal abstract Base DeepCopyInternal();"); _writer.WriteLine(string.Empty); return; } - _writer.WriteLineIndented("public override IDeepCopyable DeepCopy()"); + _writer.WriteLineIndented("protected internal override Base DeepCopyInternal()"); OpenScope(); - _writer.WriteLineIndented($"return CopyTo(new {exportName}());"); + _writer.WriteLineIndented($"var instance = new {exportName}();"); + _writer.WriteLineIndented("CopyToInternal(instance);"); + _writer.WriteLineIndented("return instance;"); CloseScope(); } @@ -2523,11 +2428,14 @@ private void WriteConstrainedQuantity( // open class OpenScope(); - WritePropertyTypeName(complex.Structure.Name); + if(complex.Structure.Abstract != true) + WritePropertyTypeName(complex.Structure.Name); - _writer.WriteLineIndented("public override IDeepCopyable DeepCopy()"); + _writer.WriteLineIndented("protected internal override Base DeepCopyInternal()"); OpenScope(); - _writer.WriteLineIndented($"return CopyTo(new {exportName}());"); + _writer.WriteLineIndented($"var instance = new {exportName}();"); + _writer.WriteLineIndented("CopyToInternal(instance);"); + _writer.WriteLineIndented("return instance;"); CloseScope(); //_writer.WriteLineIndented("// TODO: Add code to enforce these constraints:"); @@ -2559,50 +2467,16 @@ private void WriteBackboneComponent( WriteComponentComment(complex); - string explicitName = complex.cgExplicitName(); - - /* TODO(ginoc): 2024.06.28 - Special cases to remove in SDK 6.0 - * - Evidence.statistic.attributeEstimate.attributeEstimate the explicit name is duplicative and was not passed through. - * - Citation.citedArtifact.contributorship.summary had a generator prefix. - */ - switch (explicitName) - { - case "AttributeEstimateAttributeEstimate": - explicitName = "AttributeEstimate"; - break; - case "ContributorshipSummary": - explicitName = "CitedArtifactContributorshipSummary"; - break; - } - - // 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 (complex.Id.StartsWith("Citation") || complex.Id.StartsWith("Statistic") || complex.Id.StartsWith("DeviceDefinition")) - { - string parentName = complex.Id.Substring(0, complex.Id.IndexOf('.')); - var sillyBackboneName = complex.Id.Substring(parentName.Length); - explicitName = capitalizeThoseSillyBackboneNames(sillyBackboneName); - exportName = explicitName + "Component"; - } - // end of repair - */ + string? explicitName = GetExplicitName(complex.Element, _info.FhirSequence); bool useConcatenationInName = complex.Structure.Name == "Citation"; - string explicitNamePart = string.IsNullOrEmpty(explicitName) - ? complex.cgName(NamingConvention.PascalCase, useConcatenationInName, useConcatenationInName) - : explicitName; - string componentName = parentExportName + "#" + explicitNamePart; + string explicitNamePart = explicitName ?? + complex.cgName(NamingConvention.PascalCase, useConcatenationInName, useConcatenationInName); + string componentName = complex.Element.Path; WriteSerializable(); - _writer.WriteLineIndented($"[FhirType(\"{componentName}\", IsNestedType=true)]"); - - _writer.WriteLineIndented($"[BackboneType(\"{complex.Element.Path}\")]"); + _writer.WriteLineIndented($"[FhirType(\"{componentName}\", IsBackboneType=true)]"); _writer.WriteLineIndented( $"public partial class" + @@ -2612,7 +2486,8 @@ private void WriteBackboneComponent( // open class OpenScope(); - WritePropertyTypeName(componentName); + if(complex.Structure.Abstract != true) + WritePropertyTypeName(componentName); WriteElements(complex, exportName, ref exportedElements, subset); @@ -2625,11 +2500,8 @@ private void WriteBackboneComponent( if (exportedElements.Count > 0) { - WriteMatches(exportName, exportedElements); - WriteIsExactly(exportName, exportedElements); - WriteChildren(exportName, exportedElements); - WriteNamedChildren(exportName, exportedElements); - WriteIDictionarySupport(exportName, exportedElements); + WriteCompareChildren(exportName, exportedElements); + WriteDictionarySupport(exportName, exportedElements); } // close class @@ -2639,53 +2511,15 @@ private void WriteBackboneComponent( foreach (ComponentDefinition component in complex.cgChildComponents(_info)) { string componentExportName; - string componentExplicitName = component.cgExplicitName(); - if (string.IsNullOrEmpty(componentExplicitName)) + if (GetExplicitName(component.Element, _info.FhirSequence) is {} componentExplicitName) { - componentExportName = - $"{component.cgName(NamingConvention.PascalCase, useConcatenationInName, useConcatenationInName)}Component"; + componentExportName = $"{componentExplicitName}Component"; } else { - /* TODO(ginoc): 2024.06.28 - Special cases to remove in SDK 6.0 - * - Evidence.statistic.attributeEstimate.attributeEstimate the explicit name is duplicative and was not passed through. - * - Citation.citedArtifact.contributorship.summary had a generator prefix. - */ - - switch (componentExplicitName) - { - case "AttributeEstimateAttributeEstimate": - componentExportName = "AttributeEstimateComponent"; - break; - case "ContributorshipSummary": - componentExportName = "CitedArtifactContributorshipSummaryComponent"; - break; - default: - // Consent.provisionActorComponent is explicit lower case... - componentExportName = $"{component.cgExplicitName()}Component"; - break; - } - - ///* TODO(ginoc): 2024.06.28 - Special cases to remove in SDK 6.0 - // * - Consent.provision is explicit lower case in R4B and earlier - // * - Consent.provision.actor is explicit lower case in R4B and earlier - // */ - //if (_info.FhirSequence < FhirReleases.FhirSequenceCodes.R5) - //{ - // switch (complex.Element.Path) - // { - // case "Consent.provision": - // componentExportName = "provisionComponent"; - // break; - // case "Consent.provision.actor": - // componentExportName = "provisionActorComponent"; - // break; - // case "Consent.provision.data": - // componentExportName = "provisionDataComponent"; - // break; - // } - //} + componentExportName = + $"{component.cgName(NamingConvention.PascalCase, useConcatenationInName, useConcatenationInName)}Component"; } WriteBackboneComponent( @@ -2731,16 +2565,6 @@ private void WriteEnums( WriteEnums(component, className, usedEnumNames, processedValueSets); } - // after processing, we need to look for value sets we are forcing in - foreach ((string url, ValueSetBehaviorOverrides behaviors) in _valueSetBehaviorOverrides) - { - if (behaviors.ForceInClasses.Contains(className) && - _info.TryExpandVs(url, out ValueSet? vs) && - !processedValueSets.Contains(vs.Url)) - { - WriteEnum(vs, className, usedEnumNames); - } - } } /// Writes a value set as an enum. @@ -2754,40 +2578,16 @@ private bool WriteEnum( HashSet usedEnumNames, bool silent = false) { - bool passes = false; - - if (_valueSetBehaviorOverrides.TryGetValue(vs.Url, out ValueSetBehaviorOverrides? behaviors)) - { - if (behaviors.ForceInClasses.Contains(className)) - { - // skip other checks - passes = true; - } - else if (behaviors.AllowInClasses == false) - { - return false; - } - } - - if (passes || _writtenValueSets.ContainsKey(vs.Url)) + if (_writtenValueSets.ContainsKey(vs.Url)) { return true; } - if (passes || _exclusionSet.Contains(vs.Url)) + if (ExclusionSet.Contains(vs.Url)) { return false; } - FhirConcept[] concepts = vs.cgGetFlatConcepts(_info).ToArray(); - - if (concepts.Length == 0) - { - // TODO(ginoc): 2024.09.19 - do we want to start using a Terminology server to expand these? - // value set that cannot be expanded and does not have an expansion provided - return false; - } - string name = (vs.Name ?? vs.Id) .Replace(" ", string.Empty, StringComparison.Ordinal) .Replace("_", string.Empty, StringComparison.Ordinal); @@ -2796,7 +2596,7 @@ private bool WriteEnum( // Enums and their containing classes cannot have the same name, // so we have to correct these here - if (_enumNamesOverride.TryGetValue(vs.Url, out var replacementName)) + if (EnumNamesOverride.TryGetValue(vs.Url, out var replacementName)) { nameSanitized = replacementName; } @@ -2812,16 +2612,22 @@ private bool WriteEnum( { _writtenValueSets.Add( vs.Url, - new WrittenValueSetInfo() - { - ClassName = className, - ValueSetName = nameSanitized, - }); + new WrittenValueSetInfo(className, nameSanitized)); return true; } - IEnumerable referencedCodeSystems = vs.cgReferencedCodeSystems(); + FhirConcept[] concepts = vs.cgGetFlatConcepts(_info).ToArray(); + + if (concepts.Length == 0) + { + // TODO(ginoc): 2024.09.19 - do we want to start using a Terminology server to expand these? + // value set that cannot be expanded and does not have an expansion provided + return false; + } + + + IEnumerable referencedCodeSystems = vs.cgReferencedCodeSystems().ToList(); if (referencedCodeSystems.Count() == 1) { @@ -2838,56 +2644,7 @@ private bool WriteEnum( $"(systems: {referencedCodeSystems.Count()})"); } - /* TODO(ginoc): 2024.07.01 - Special cases to remove in SDK 6.0 - * - ValueSet http://hl7.org/fhir/ValueSet/item-type used to enumerate non-selectable: 'question' - * - ValueSet http://hl7.org/fhir/ValueSet/v3-ActInvoiceGroupCode in STU3 used to enumerate non-selectable: '_ActInvoiceInterGroupCode' and '_ActInvoiceRootGroupCode' - */ - switch (vs.Url) - { - case "http://hl7.org/fhir/ValueSet/item-type": - { - if (!vs.Expansion.Contains.Any(vsContains => vsContains.Code == "question")) - { - vs.Expansion.Contains.Insert(2, new ValueSet.ContainsComponent() - { - System = "http://hl7.org/fhir/item-type", - Code = "question", - Display = "Question", - }); - } - } - break; - - case "http://hl7.org/fhir/ValueSet/v3-ActInvoiceGroupCode": - { - // only care about the version present in STU3 - if (vs.Version == "2014-03-26") - { - if (!vs.Expansion.Contains.Any(vsContains => vsContains.Code == "_ActInvoiceInterGroupCode")) - { - vs.Expansion.Contains.Insert(0, new ValueSet.ContainsComponent() - { - System = "http://hl7.org/fhir/v3/ActCode", - Code = "_ActInvoiceInterGroupCode", - Display = "ActInvoiceInterGroupCode", - }); - } - - if (!vs.Expansion.Contains.Any(vsContains => vsContains.Code == "_ActInvoiceRootGroupCode")) - { - vs.Expansion.Contains.Insert(8, new ValueSet.ContainsComponent() - { - System = "http://hl7.org/fhir/v3/ActCode", - Code = "_ActInvoiceRootGroupCode", - Display = "ActInvoiceRootGroupCode", - }); - } - } - } - break; - } - - var defaultSystem = GetDefaultCodeSystem(concepts); + string defaultSystem = GetDefaultCodeSystem(concepts); _writer.WriteLineIndented($"[FhirEnumeration(\"{name}\", \"{vs.Url}\", \"{defaultSystem}\")]"); @@ -2899,6 +2656,9 @@ private bool WriteEnum( foreach (FhirConcept concept in concepts) { + if (concept.IsAbstract is true) + continue; + string codeName = ConvertEnumValue(concept.Code); string codeValue = FhirSanitizationUtils.SanitizeForValue(concept.Code); string description = string.IsNullOrEmpty(concept.Definition) @@ -2943,15 +2703,52 @@ private bool WriteEnum( _writer.WriteLineIndented($"{codeName},"); } + // HACK for short-term R6 support.... + // We need to add the FHIR version enum literals for R6, as they are not part of the + // R5 distribution, which we are using at this moment to generate Base/Conformance. + if (vs.Url == "http://hl7.org/fhir/ValueSet/FHIR-version") + { + _writer.WriteIndented( + """ + /// + /// R6 Versions. + /// (system: http://hl7.org/fhir/FHIR-version) + /// + [EnumLiteral("6.0"), Description("6.0")] + N6_0, + /// + /// R6 Final Version. + /// (system: http://hl7.org/fhir/FHIR-version) + /// + [EnumLiteral("6.0.0"), Description("6.0.0")] + N6_0_0, + /// + /// R6 1st Draft Ballot. + /// (system: http://hl7.org/fhir/FHIR-version) + /// + [EnumLiteral("6.0.0-ballo1"), Description("6.0.0-ballot1")] + N6_0_0Ballo1, + /// + /// R6 2nd Draft Ballot. + /// (system: http://hl7.org/fhir/FHIR-version) + /// + [EnumLiteral("6.0.0-ballot2"), Description("6.0.0-ballot2")] + N6_0_0Ballot2, + /// + /// R6 3rd Draft Ballot. + /// (system: http://hl7.org/fhir/FHIR-version) + /// + [EnumLiteral("6.0.0-ballot3"), Description("6.0.0-ballot3")] + N6_0_0Ballot3, + """); + } + + CloseScope(); _writtenValueSets.Add( vs.Url, - new WrittenValueSetInfo() - { - ClassName = className, - ValueSetName = nameSanitized, - }); + new WrittenValueSetInfo(className, nameSanitized)); return true; } @@ -3008,15 +2805,22 @@ 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, + string choice, string fiveWs, string? since = null, (string, string)? until = null, + string? xmlSerialization = null, string? declaredType = null) { var xmlser = xmlSerialization is null ? null : $", XmlSerialization = XmlRepresentation.{xmlSerialization}"; string attributeText = $"[FhirElement(\"{name}\"{xmlser}{summary}{isModifier}, Order={GetOrder(element)}{choice}{fiveWs}"; - if (since is { }) + if (since is not null) { attributeText += $", Since=FhirRelease.{since}"; } + if (declaredType is not null) + { + attributeText += $", DeclaredType={declaredType}"; + } + attributeText += ")]"; _writer.WriteLineIndented(attributeText); @@ -3062,72 +2866,42 @@ private void WriteElement( } string path = element.cgPath(); - - var since = _sinceAttributes.GetValueOrDefault(path); - var until = _untilAttributes.TryGetValue(path, out (string, string) u) ? u : default((string, string)?); + string? since = _sinceAttributes.GetValueOrDefault(path); + (string, string)? until = _untilAttributes.TryGetValue(path, out (string, string) u) ? u : default((string, string)?); // TODO: Modify these elements in ModifyDefinitionsForConsistency - var description = path switch + string? remarks = path switch { - "Signature.who" => element.Short + ".\nNote 1: Since R4 the type of this element should be a fixed type (ResourceReference). For backwards compatibility it remains of type DataType.\nNote 2: Since R5 the cardinality is expanded to 0..1 (previous it was 1..1).", - "Signature.onBehalfOf" => element.Short + ".\nNote: Since R4 the type of this element should be a fixed type (ResourceReference). For backwards compatibility it remains of type DataType.", - "Signature.when" => element.Short + ".\nNote: Since R5 the cardinality is expanded to 0..1 (previous it was 1..1).", - "Signature.type" => element.Short + ".\nNote: Since R5 the cardinality is expanded to 0..* (previous it was 1..*).", - _ => AttributeDescriptionWithSinceInfo(name, element.Short, since, until) + "Signature.who" => "Note 1: Since R4 the type of this element should be a fixed type (ResourceReference). For backwards compatibility it remains of type DataType.\nNote 2: Since R5 the cardinality is expanded to 0..1 (previous it was 1..1).", + "Signature.onBehalfOf" => "Since R4 the type of this element should be a fixed type (ResourceReference). For backwards compatibility it remains of type DataType.", + "Signature.when" => "Since R5 the cardinality is expanded to 0..1 (previous it was 1..1).", + "Signature.type" => "Since R5 the cardinality is expanded to 0..* (previous it was 1..*).", + _ => MakeAttributeRemarkForNewOrDeprecatedProperties(name, null, since, until) }; + if(element.Short is not null) WriteIndentedComment(element.Short.EnsurePeriod()); + remarks = MakeAttributeRemarkForChangedTypes(path, remarks); + if (remarks is not null) WriteIndentedComment(remarks, isSummary: false, isRemarks: true); + string? xmlSerialization = path == "Narrative.div" ? "XHtml" : + path is "Extension.url" or "Element.id" ? "XmlAttr" : ei.PropertyType is CqlTypeReference ? "XmlAttr" : null; - if (description is not null) WriteIndentedComment(description); - 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, choice, fiveWs); + WriteFhirElementAttribute(name, summary, null, element, 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); - _writer.WriteLineIndented($"[DeclaredType(Type = typeof(ResourceReference), Since = FhirRelease.R4)]"); + WriteFhirElementAttribute(name, summary, isModifier, element, ", Choice = ChoiceType.DatatypeChoice", fiveWs); + WriteFhirElementAttribute(name, summary, isModifier, element, "", fiveWs, since: since); + _writer.WriteLineIndented($"[AllowedTypes(typeof(ResourceReference), Since = FhirRelease.R4)]"); } else { - BuildFhirElementAttribute(name, summary, isModifier, element, orderOffset, choice, fiveWs, since, until, xmlSerialization); - } - - if (ei.PropertyType is CqlTypeReference ctr) - { - _writer.WriteLineIndented($"[DeclaredType(Type = typeof({ctr.DeclaredTypeString}))]"); - } - else if (path == "Meta.profile") - { - _writer.WriteLineIndented($"[DeclaredType(Type = typeof(Canonical), Since = FhirRelease.R4)]"); - } - else if (path == "Bundle.link.relation") - { - _writer.WriteLineIndented($"[DeclaredType(Type = typeof(Code), Since = FhirRelease.R5)]"); - } - else if (path == "Attachment.size") - { - _writer.WriteLineIndented($"[DeclaredType(Type = typeof(UnsignedInt), Since = FhirRelease.STU3)]"); - _writer.WriteLineIndented($"[DeclaredType(Type = typeof(Integer64), Since = FhirRelease.R5)]"); - } - else if (path is - "ElementDefinition.constraint.requirements" or - "ElementDefinition.binding.description" or - "ElementDefinition.mapping.comment" or - "CapabilityStatement.implementation.description") - { - _writer.WriteLineIndented($"[DeclaredType(Type = typeof(FhirString))]"); - _writer.WriteLineIndented($"[DeclaredType(Type = typeof(Markdown), Since = FhirRelease.R5)]"); - } - - if (TryGetPrimitiveType(ei.PropertyType, out var ptr) && ptr is CodedTypeReference) - { - _writer.WriteLineIndented("[DeclaredType(Type = typeof(Code))]"); + WriteFhirElementAttribute(name, summary, isModifier, element, choice, fiveWs, since, until, xmlSerialization); } if (!string.IsNullOrEmpty(element.cgBindingName())) @@ -3135,8 +2909,13 @@ private void WriteElement( _writer.WriteLineIndented($"[Binding(\"{element.cgBindingName()}\")]"); } - if (element.cgIsSimple() && element.Type.Count == 1 && element.Type.Single().cgName() == "uri") - _writer.WriteLineIndented("[UriPattern]"); + if (_elementTypeChanges.TryGetValue(path, out ElementTypeChange[]? changes)) + { + foreach(ElementTypeChange change in changes) + { + _writer.WriteLineIndented(BuildAllowedTypesAttribute([change.DeclaredTypeReference], change.Since)); + } + } bool notClsCompliant = !string.IsNullOrEmpty(allowedTypes) || !string.IsNullOrEmpty(resourceReferences); @@ -3171,34 +2950,51 @@ private void WriteElement( _writer.WriteLineIndented($"[Cardinality(Min={element.Min},Max={element.cgCardinalityMax()})]"); } - writeElementGettersAndSetters(element, ei); + WriteElementGettersAndSetters(element, ei); } - private static string? AttributeDescriptionWithSinceInfo(string name, string baseDescription, string? since = null, (string, string)? until = null) + private static string? MakeAttributeRemarkForNewOrDeprecatedProperties(string name, string? baseRemark, string? since = null, (string, string)? until = null) { - return (since, until, baseDescription) switch + var deprecationRemark = (since, until, baseRemark) switch { - (_, _, null) => null, - (not null, _, _) => baseDescription + - $". Note: Element was introduced in {since}, do not use when working with older releases.", - (_, (var release, ""), _) => baseDescription + - $". Note: Element is deprecated since {release}, do not use with {release} and newer releases.", - (_, (var release, var replacedBy), _) => baseDescription + - $". Note: Element is replaced by '{replacedBy}' since {release}. Do not use this element '{name}' with {release} and newer releases.", - _ => baseDescription + (not null, _, _) => $"Element was introduced in {since}, do not use when working with older releases.", + (_, (var release, ""), _) => $"Element is deprecated since {release}, do not use with {release} and newer releases.", + (_, (var release, var replacedBy), _) => $"Element is replaced by '{replacedBy}' since {release}. Do not use this element '{name}' with {release} and newer releases.", + _ => null }; + + if(baseRemark is null) + { + return deprecationRemark; + } + + return $"{baseRemark}. {deprecationRemark}"; } - private static PrimitiveTypeReference BuildTypeReferenceForCode(DefinitionCollection info, ElementDefinition element, Dictionary writtenValueSets) + private static string? MakeAttributeRemarkForChangedTypes(string path, string? baseDescription) + { + if(!_elementTypeChanges.TryGetValue(path, out ElementTypeChange[]? changes)) + { + return baseDescription; + } + + string changedDescription = $"The type of this element has changed over time. Make sure to use " + + string.Join(", ", + changes.Select(change => $"{change.DeclaredTypeReference.PropertyTypeString} {VersionChangeMessage(changes, change, false)}")) + "."; + + return baseDescription is null ? changedDescription : $"{baseDescription}. {changedDescription}"; + } + + private static (string? enumName, string? enumClass) GetVsInfoForCodedElement(DefinitionCollection info, ElementDefinition element, Dictionary writtenValueSets) { if ((element.Binding?.Strength != Hl7.Fhir.Model.BindingStrength.Required) || (!info.TryExpandVs(element.Binding.ValueSet, out ValueSet? vs)) || - _exclusionSet.Contains(vs.Url) || + ExclusionSet.Contains(vs.Url) || (_codedElementOverrides.Contains(element.Path) && info.FhirSequence >= FhirReleases.FhirSequenceCodes.R4) || - !writtenValueSets.TryGetValue(vs.Url, out WrittenValueSetInfo vsInfo)) + !writtenValueSets.TryGetValue(vs.Url, out WrittenValueSetInfo? vsInfo)) { - return PrimitiveTypeReference.GetTypeReference("code"); + return (null, null); } string vsClass = vsInfo.ClassName; @@ -3206,7 +3002,7 @@ private static PrimitiveTypeReference BuildTypeReferenceForCode(DefinitionCollec if (string.IsNullOrEmpty(vsClass)) { - return new CodedTypeReference(vsName, null); + return (vsName, null); } string pascal = element.cgName().ToPascalCase(); @@ -3217,7 +3013,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( @@ -3232,38 +3028,26 @@ private static TypeReference DetermineTypeReferenceForFhirElement( TypeReference determineTypeReferenceForFhirElementName() { - if (element.Path is "Meta.profile") + if(_elementTypeChanges.TryGetValue(element.Path, out ElementTypeChange[]? changes)) { - /* we want to share Meta across different FHIR versions, - * so we use the "most common" type to the versions, which - * is uri rather than the more specific canonical. */ - return PrimitiveTypeReference.GetTypeReference("uri"); - } + // If the element has a type change, we need to use DataType, to make + // sure the property can capture all the types. + if(changes.All(c => c.DeclaredTypeReference is PrimitiveTypeReference)) + { + return PrimitiveTypeReference.PrimitiveType; + } - if (element.Path is "Element.id" or "Extension.url") - { - /* these two properties formally use a CQL primitive (at least, - * that's how they are encoded in the StructureDefinition. */ - return CqlTypeReference.SystemString; + return ComplexTypeReference.DataTypeReference; } - var 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(); + string initialTypeName = getTypeNameFromElement(); // Elements of type Code or Code have their own naming/types, so handle those separately. - if (initialTypeName == "code") - return BuildTypeReferenceForCode(info, element, writtenValueSets); - - if (PrimitiveTypeReference.IsFhirPrimitiveType(initialTypeName)) - return PrimitiveTypeReference.GetTypeReference(initialTypeName); + var (vsName,vsClass) = initialTypeName == "code" + ? GetVsInfoForCodedElement(info, element, writtenValueSets) + : (null,null); - // Otherwise, this is a "normal" name for a complex type. - return new ComplexTypeReference(initialTypeName, getPocoNameForComplexTypeReference(initialTypeName)); + return TypeReference.BuildFromFhirTypeName(initialTypeName, vsName, vsClass); string getTypeNameFromElement() { @@ -3272,9 +3056,9 @@ 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); + return BuildTypeNameForNestedComplexType(targetEd, btn, info.FhirSequence); } return btn; @@ -3284,32 +3068,23 @@ string getTypeNameFromElement() ? element.Type.First().cgName() : "DataType"; } - - string getPocoNameForComplexTypeReference(string name) - { - return name.Contains('.') - ? BuildTypeNameForNestedComplexType(element, name) - : TypeReference.MapTypeName(name); - } } } internal static bool TryGetPrimitiveType(TypeReference tr, [NotNullWhen(true)] out PrimitiveTypeReference? ptr) { - if (tr is PrimitiveTypeReference p) - { - ptr = p; - return true; - } - - if (tr is ListTypeReference { Element: PrimitiveTypeReference pltr }) + switch (tr) { - ptr = pltr; - return true; + case PrimitiveTypeReference p: + ptr = p; + return true; + case ListTypeReference { Element: PrimitiveTypeReference pltr }: + ptr = pltr; + return true; + default: + ptr = null; + return false; } - - ptr = null; - return false; } internal WrittenElementInfo BuildElementInfo( @@ -3341,103 +3116,157 @@ internal static WrittenElementInfo BuildElementInfo( PropertyType: typeRef, PrimitiveHelperName: forPrimitiveType ? (pascal == exportedComplexName ? $"{pascal}_" : pascal) - : null // Since properties cannot have the same name as their enclosing types, we'll add a '_' suffix if this happens. + : null, // Since properties cannot have the same name as their enclosing types, we'll add a '_' suffix if this happens. + Required: element.Min > 0 ); } - private void writeElementGettersAndSetters(ElementDefinition element, WrittenElementInfo ei) + private void WriteElementGettersAndSetters(ElementDefinition element, WrittenElementInfo ei) { _writer.WriteLineIndented("[DataMember]"); - if (ei.PropertyType is not ListTypeReference) + string overflowTypeName = ei.PropertyType.PropertyTypeString switch { - _writer.WriteLineIndented($"public {ei.PropertyType.PropertyTypeString} {ei.PropertyName}"); + "Hl7.Fhir.Model.Resource" => "DynamicResource", + "Hl7.Fhir.Model.DataType" => "DynamicDataType", + "Hl7.Fhir.Model.PrimitiveType" => "DynamicPrimitive", + { } s => s + }; - OpenScope(); - _writer.WriteLineIndented($"get {{ return _{ei.PropertyName}; }}"); - _writer.WriteLineIndented($"set {{ _{ei.PropertyName} = value; OnPropertyChanged(\"{ei.PropertyName}\"); }}"); - CloseScope(); + if (ei.PropertyType is ListTypeReference) + _writer.WriteLineIndented("[AllowNull]"); + _writer.WriteLineIndented($"public {(ei.PropertyType is ListTypeReference || ei.Required ? ei.PropertyType.PropertyTypeString : $"{ei.PropertyType.PropertyTypeString}?")} {ei.PropertyName}"); - _writer.WriteLineIndented($"private {ei.PropertyType.PropertyTypeString} _{ei.PropertyName};"); - _writer.WriteLine(string.Empty); - } - else - { - _writer.WriteLineIndented($"public {ei.PropertyType.PropertyTypeString} {ei.PropertyName}"); + OpenScope(); - OpenScope(); - _writer.WriteLineIndented($"get {{ if(_{ei.PropertyName}==null) _{ei.PropertyName} =" + - $" new {ei.PropertyType.PropertyTypeString}(); return _{ei.PropertyName}; }}"); - _writer.WriteLineIndented($"set {{ _{ei.PropertyName} = value; OnPropertyChanged(\"{ei.PropertyName}\"); }}"); - CloseScope(); + _writer.WriteLineIndented("get"); + OpenScope(); + _writer.WriteLineIndented($"if(_{ei.PropertyName}.InOverflow<{overflowTypeName}>())"); + _writer.IncreaseIndent(); + _writer.WriteLineIndented($"throw CodedValidationException.FromTypes(typeof({ei.PropertyType.PropertyTypeString}), Overflow[\"{ei.FhirElementName}\"]);"); + _writer.DecreaseIndent(); + _writer.WriteLineIndented(ei.PropertyType is not ListTypeReference ? $"return _{ei.PropertyName}{(ei.Required ? "!" : "")};" : $"return _{ei.PropertyName} ??= [];"); - _writer.WriteLineIndented($"private {ei.PropertyType.PropertyTypeString} _{ei.PropertyName};"); - _writer.WriteLine(string.Empty); - } + CloseScope(); + + _writer.WriteLineIndented("set"); + OpenScope(); + _writer.WriteLineIndented($"if (_{ei.PropertyName}.InOverflow<{overflowTypeName}>())"); + _writer.IncreaseIndent(); + _writer.WriteLineIndented($"Overflow.Remove(\"{ei.FhirElementName}\");"); + _writer.DecreaseIndent(); + _writer.WriteLineIndented($"_{ei.PropertyName} = value;"); + _writer.WriteLineIndented($"OnPropertyChanged(\"{ei.PropertyName}\");"); + CloseScope(); - bool needsPrimitiveProperty = ei.PropertyType is + CloseScope(); + _writer.WriteLineIndented($"private {ei.PropertyType.PropertyTypeString}? _{ei.PropertyName};"); + _writer.WriteLine(string.Empty); + + bool needsHelperProperty = ei.PropertyType is PrimitiveTypeReference or ListTypeReference { Element: PrimitiveTypeReference }; - if (!needsPrimitiveProperty) + if (needsHelperProperty) { - return; + // If the property has had multiple types over time, we need to generate a helper property for each type. + if(_elementTypeChanges.TryGetValue(element.Path, out ElementTypeChange[]? changes)) + { + ElementTypeChange lastChange = changes.Last(); + + foreach(ElementTypeChange change in changes) + { + // The DeclaredType given by the maintainer is the type of the element, even if it repeats, + // so let's wrap that type in a list if applicable. + TypeReference propType = ei.PropertyType is ListTypeReference ? + new ListTypeReference(change.DeclaredTypeReference) : change.DeclaredTypeReference; + string helperName = change == lastChange + ? ei.PrimitiveHelperName! + : $"{ei.PrimitiveHelperName}{change.DeclaredTypeReference.Name.ToPascalCase()}"; + string versionsRemark = $"Use this property {VersionChangeMessage(changes, change, false)}."; + WritePrimitiveHelperProperty(element.Short, ei, propType, helperName, versionsRemark); + } + } + else + { + WritePrimitiveHelperProperty(element.Short, ei, ei.PropertyType, ei.PrimitiveHelperName!); + } } + } - WriteIndentedComment(element.Short); - _writer.WriteLineIndented($"/// This uses the native .NET datatype, rather than the FHIR equivalent"); + private void WritePrimitiveHelperProperty(string description, WrittenElementInfo ei, + TypeReference? propType, string helperPropName, string? versionsRemark = null) + { + string descriptionText = versionsRemark is null + ? description + : $"{description}. {versionsRemark}"; + WriteIndentedComment(descriptionText); + _writer.WriteLineIndented("/// This uses the native .NET datatype, rather than the FHIR equivalent"); _writer.WriteLineIndented("[IgnoreDataMember]"); - if (ei.PropertyType is PrimitiveTypeReference ptr) + switch (propType) { - _writer.WriteLineIndented($"public {ptr.ConveniencePropertyTypeString} {ei.PrimitiveHelperName}"); - - OpenScope(); - _writer.WriteLineIndented($"get {{ return {ei.PropertyName} != null ? {ei.PropertyName}.Value : null; }}"); - _writer.WriteLineIndented("set"); - OpenScope(); + case PrimitiveTypeReference ptr: + string typeString = WithNullabilityMarking(ptr.ConveniencePropertyTypeString); + _writer.WriteLineIndented($"public {typeString} {helperPropName}"); - _writer.WriteLineIndented($"if (value == null)"); + OpenScope(); + string propAccess = versionsRemark is not null + ? $"(({MostGeneralValueAccessorType(ptr)}?){ei.PropertyName})" + : $"{ei.PropertyName}"; + _writer.WriteLineIndented($"get => {propAccess}?.Value;"); - _writer.IncreaseIndent(); - _writer.WriteLineIndented($"{ei.PropertyName} = null;"); - _writer.DecreaseIndent(); - _writer.WriteLineIndented("else"); - _writer.IncreaseIndent(); - _writer.WriteLineIndented($"{ei.PropertyName} = new {ei.PropertyType.PropertyTypeString}(value);"); - _writer.DecreaseIndent(); - _writer.WriteLineIndented($"OnPropertyChanged(\"{ei.PrimitiveHelperName}\");"); - CloseScope(suppressNewline: true); - CloseScope(); + _writer.WriteLineIndented("set"); + OpenScope(); + _writer.WriteLineIndented($"{ei.PropertyName} = value is null ? null! : new {ptr.PropertyTypeString}(value);"); + _writer.WriteLineIndented($"OnPropertyChanged(\"{helperPropName}\");"); + CloseScope(suppressNewline: true); + CloseScope(); + break; + case ListTypeReference { Element: PrimitiveTypeReference lptr }: + string nullableTypeList = WithNullabilityMarking(lptr.ConveniencePropertyTypeString); + _writer.WriteLineIndented($"public IEnumerable<{nullableTypeList}> {helperPropName}"); + + OpenScope(); + + _writer.WriteIndented($"get => _{ei.PropertyName}"); + if(versionsRemark is not null) + _writer.Write($"?.Cast<{MostGeneralValueAccessorType(lptr)}>()"); + _writer.WriteLine($"?.Select(elem => elem.Value) ?? [];"); + + _writer.WriteLineIndented("set"); + OpenScope(); + + _writer.WriteLineIndented($"if (value == null)"); + + _writer.IncreaseIndent(); + _writer.WriteLineIndented($"{ei.PropertyName} = null!;"); + _writer.DecreaseIndent(); + _writer.WriteLineIndented("else"); + _writer.IncreaseIndent(); + _writer.WriteLineIndented($"{ei.PropertyName} = " + + $"new {ei.PropertyType.PropertyTypeString}" + + $"(value.Select(elem=>new {lptr.PropertyTypeString}(elem)));"); + _writer.DecreaseIndent(); + + _writer.WriteLineIndented($"OnPropertyChanged(\"{helperPropName}\");"); + CloseScope(suppressNewline: true); + CloseScope(); + break; } - else if (ei.PropertyType is ListTypeReference { Element: PrimitiveTypeReference lptr }) - { - _writer.WriteLineIndented($"public IEnumerable<{lptr.ConveniencePropertyTypeString}> {ei.PrimitiveHelperName}"); - - OpenScope(); - _writer.WriteLineIndented($"get {{ return {ei.PropertyName} != null ? {ei.PropertyName}.Select(elem => elem.Value) : null; }}"); - _writer.WriteLineIndented("set"); - OpenScope(); - - _writer.WriteLineIndented($"if (value == null)"); - - _writer.IncreaseIndent(); - _writer.WriteLineIndented($"{ei.PropertyName} = null;"); - _writer.DecreaseIndent(); - _writer.WriteLineIndented("else"); - _writer.IncreaseIndent(); - _writer.WriteLineIndented($"{ei.PropertyName} = " + - $"new {ei.PropertyType.PropertyTypeString}" + - $"(value.Select(elem=>new {lptr.PropertyTypeString}(elem)));"); - _writer.DecreaseIndent(); + } - _writer.WriteLineIndented($"OnPropertyChanged(\"{ei.PrimitiveHelperName}\");"); - CloseScope(suppressNewline: true); - CloseScope(); - } + private static string MostGeneralValueAccessorType(PrimitiveTypeReference ptr) + { + return ptr.ConveniencePropertyTypeString switch + { + "string" => "IValue", + _ => ptr.PropertyTypeString + }; } + /// /// Determine the type name for an element that has child elements, based on the definition and /// the declared type. @@ -3445,43 +3274,10 @@ PrimitiveTypeReference or /// The ed. /// The type. /// A string. - private static string BuildTypeNameForNestedComplexType(ElementDefinition ed, string type) + private static string BuildTypeNameForNestedComplexType(ElementDefinition ed, string type, FhirReleases.FhirSequenceCodes sequence) { - // 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)) + if (GetExplicitName(ed, sequence) is {} explicitTypeName) { - /* TODO(ginoc): 2024.06.28 - Special cases to remove in SDK 6.0 - * - Evidence.statistic.attributeEstimate.attributeEstimate the explicit name is duplicative and was not passed through. - * - Citation.citedArtifact.contributorship.summary had a generator prefix. - */ - - switch (explicitTypeName) - { - case "AttributeEstimateAttributeEstimate": - explicitTypeName = "AttributeEstimate"; - break; - case "ContributorshipSummary": - explicitTypeName = "CitedArtifactContributorshipSummary"; - break; - } - string parentName = type.Substring(0, type.IndexOf('.')); return $"{parentName}" + $".{explicitTypeName}" + @@ -3563,7 +3359,6 @@ internal static void BuildElementOptionalFlags( if (elementType == "Resource") { choice = ", Choice=ChoiceType.ResourceChoice"; - allowedTypes = $"[AllowedTypes(typeof({Namespace}.Resource))]"; } } else @@ -3586,7 +3381,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 @@ -3597,44 +3392,23 @@ internal static void BuildElementOptionalFlags( if (allTypesAvailable) { - StringBuilder sb = new(); - sb.Append("[AllowedTypes("); - - bool needsSep = false; - foreach ((string etName, ElementDefinition.TypeRefComponent elementType) 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 typeRefs = elementTypes.Values.Select(v => TypeReference.BuildFromFhirTypeName(v.Code)); + allowedTypes = BuildAllowedTypesAttribute(typeRefs, null); + } + else if (elementTypes.Count > 30) + { + allowedTypes = BuildOpenAllowedTypesAttribute(); } + else + throw new InvalidOperationException("Cannot generate AllowedTypes attribute for element " + + $"{element.Path} with types {string.Join(", ", elementTypes.Keys)} because " + + $"not all types in the choice are available in the current subset ({subset})."); } } 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 + "\"")) + @@ -3649,8 +3423,8 @@ internal static void BuildElementOptionalFlags( private void WritePropertyTypeName(string name) { WriteIndentedComment("FHIR Type Name"); - var specifier = name == "Base" ? "virtual" : "override"; - _writer.WriteLineIndented($"public {specifier} string TypeName {{ get {{ return \"{name}\"; }} }}"); + + _writer.WriteLineIndented($"""public override string TypeName => "{name}";"""); _writer.WriteLine(string.Empty); } @@ -3669,7 +3443,7 @@ private void WritePrimitiveTypes( foreach (StructureDefinition primitive in primitives.OrderBy(sd => sd.Name)) { - if (_exclusionSet.Contains(primitive.Name)) + if (ExclusionSet.Contains(primitive.Name)) { continue; } @@ -3708,12 +3482,7 @@ private void WritePrimitiveType( writtenModels.Add( primitive.Name, - new WrittenModelInfo() - { - FhirName = primitive.Name, - CsName = $"{Namespace}.{exportName}", - IsAbstract = false, // no abstract primitives - }); + new WrittenModelInfo(FhirName: primitive.Name, CsName: $"{Namespace}.{exportName}", IsAbstract: false)); string filename = Path.Combine(_exportDirectory, "Generated", $"{exportName}.cs"); @@ -3757,14 +3526,15 @@ private void WritePrimitiveType( _writer.WriteLineIndented( $"public partial class" + - $" {exportName}" + - $" : PrimitiveType, " + - PrimitiveValueInterface(typeName)); + $" {exportName}" + + $" : PrimitiveType, " + + PrimitiveValueInterface(typeName)); // open class OpenScope(); - WritePropertyTypeName(primitive.Name); + if (primitive.Abstract != true) + WritePropertyTypeName(primitive.Name); if (!string.IsNullOrEmpty(primitive.cgpValidationRegEx())) { @@ -3776,30 +3546,37 @@ private void WritePrimitiveType( _writer.WriteLine(string.Empty); } - _writer.WriteLineIndented($"public {exportName}({typeName} value)"); + var nullableTypeName = typeName.EndsWith('?') ? typeName : typeName + '?'; + + _writer.WriteLineIndented($"public {exportName}({nullableTypeName} value)"); OpenScope(); _writer.WriteLineIndented("Value = value;"); CloseScope(); - _writer.WriteLineIndented($"public {exportName}(): this(({typeName})null) {{}}"); + _writer.WriteLineIndented($"public {exportName}(): this(({nullableTypeName})null) {{}}"); _writer.WriteLine(string.Empty); - WriteIndentedComment("Primitive value of the element"); + // For some primitive pocos, we need a hand-written value propery since we are using + // a different value type for JsonValue and Value. + if (exportName is not ("Integer64" or "Base64Binary" or "Instant")) + { + WriteIndentedComment("Primitive value of the element"); + + _writer.WriteLineIndented( + "[FhirElement(\"value\", IsPrimitiveValue=true, XmlSerialization=XmlRepresentation.XmlAttr, InSummary=true, Order=30)]"); - _writer.WriteLineIndented("[FhirElement(\"value\", IsPrimitiveValue=true, XmlSerialization=XmlRepresentation.XmlAttr, InSummary=true, Order=30)]"); - _writer.WriteLineIndented($"[DeclaredType(Type = typeof({getSystemTypeForFhirType(primitive.Name)}))]"); + _writer.WriteLineIndented("[DataMember]"); - if (PrimitiveValidationPatterns.TryGetValue(primitive.Name, out string? primitivePattern)) - { - _writer.WriteLineIndented($"[{primitivePattern}]"); + _writer.WriteLineIndented($"public {nullableTypeName} Value"); + OpenScope(); + + var typeNameInSwitch = typeName.EndsWith("?") ? typeName[..^1] : typeName; + _writer.WriteLineIndented($"get {{ return JsonValue is {typeNameInSwitch} or null ? ({nullableTypeName})JsonValue : throw COVE.FromTypes(typeof({exportName}), JsonValue); }}"); + _writer.WriteLineIndented("set { JsonValue = value; OnPropertyChanged(\"Value\"); }"); + CloseScope(); } - _writer.WriteLineIndented("[DataMember]"); - _writer.WriteLineIndented($"public {typeName} Value"); - OpenScope(); - _writer.WriteLineIndented($"get {{ return ({typeName})ObjectValue; }}"); - _writer.WriteLineIndented("set { ObjectValue = value; OnPropertyChanged(\"Value\"); }"); - CloseScope(); + WriteDeepCopy(exportName); // close class CloseScope(); @@ -3894,6 +3671,7 @@ private void WriteHeaderComplexDataType() WriteGenerationComment(); _writer.WriteLineIndented("using System;"); + _writer.WriteLineIndented("using System.Collections;"); _writer.WriteLineIndented("using System.Collections.Generic;"); _writer.WriteLineIndented("using System.Linq;"); _writer.WriteLineIndented("using System.Runtime.Serialization;"); @@ -3902,8 +3680,12 @@ private void WriteHeaderComplexDataType() _writer.WriteLineIndented("using Hl7.Fhir.Specification;"); _writer.WriteLineIndented("using Hl7.Fhir.Utility;"); _writer.WriteLineIndented("using Hl7.Fhir.Validation;"); + _writer.WriteLineIndented("using System.Diagnostics.CodeAnalysis;"); _writer.WriteLineIndented("using SystemPrimitive = Hl7.Fhir.ElementModel.Types;"); - _writer.WriteLine(string.Empty); + _writer.WriteLine(); + + _writer.WriteLineIndented("#nullable enable"); + _writer.WriteLine(); WriteCopyright(); @@ -3924,9 +3706,14 @@ private void WriteHeaderPrimitive() _writer.WriteLineIndented("using Hl7.Fhir.Introspection;"); _writer.WriteLineIndented("using Hl7.Fhir.Specification;"); _writer.WriteLineIndented("using Hl7.Fhir.Validation;"); + _writer.WriteLineIndented("using System.Diagnostics.CodeAnalysis;"); _writer.WriteLineIndented("using SystemPrimitive = Hl7.Fhir.ElementModel.Types;"); + _writer.WriteLineIndented("using COVE=Hl7.Fhir.Validation.CodedValidationException;"); _writer.WriteLine(string.Empty); + _writer.WriteLineIndented("#nullable enable"); + _writer.WriteLine(); + WriteCopyright(); } @@ -4039,21 +3826,12 @@ WrittenModelInfo CreateWMI(StructureDefinition t) exportName = t.Name.ToPascalCase(); } - return new WrittenModelInfo() - { - FhirName = t.Name, - CsName = $"{Namespace}.{exportName}", - IsAbstract = t.Abstract == true, - }; + return new WrittenModelInfo(FhirName: t.Name, CsName: $"{Namespace}.{exportName}", IsAbstract: t.Abstract == true); } } /// Information about a written value set. - internal struct WrittenValueSetInfo - { - internal string ClassName; - internal string ValueSetName; - } + internal record WrittenValueSetInfo(string ClassName, string ValueSetName); /// Information about the written element. internal record WrittenElementInfo( @@ -4061,16 +3839,9 @@ internal record WrittenElementInfo( string FhirElementPath, string PropertyName, TypeReference PropertyType, - string? PrimitiveHelperName) - { - //public string FhirElementName => FhirElementPath.Split('.').Last(); - } + string? PrimitiveHelperName, + bool Required = false); /// Information about the written model. - internal struct WrittenModelInfo - { - internal string FhirName; - internal string CsName; - internal bool IsAbstract; - } + internal record WrittenModelInfo(string FhirName, string CsName, bool IsAbstract); } diff --git a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirelyCommon.cs b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirelyCommon.cs index a3662d2c7..fc93f9863 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirelyCommon.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/CSharpFirelyCommon.cs @@ -4,8 +4,12 @@ // 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; @@ -17,7 +21,7 @@ public static class CSharpFirelyCommon { /// Dictionary mapping FHIR primitive types to language equivalents (see Template-Model.tt#1252). - public static readonly Dictionary PrimitiveTypeMap = new Dictionary() + public static readonly Dictionary PrimitiveTypeMap = new() { { "base64Binary", "byte[]" }, { "boolean", "bool?" }, @@ -61,22 +65,6 @@ public static class CSharpFirelyCommon { "Resource", "DomainResource" }, }; - /// Primitive types that have a specific validation attribute on their Value property. - public static readonly Dictionary PrimitiveValidationPatterns = new() - { - ["uri"] = "UriPattern", - ["uuid"] = "UuidPattern", - ["id"] = "IdPattern", - ["date"] = "DatePattern", - ["dateTime"] = "DateTimePattern", - ["oid"] = "OidPattern", - ["code"] = "CodePattern", - ["time"] = "TimePattern", - ["string"] = "StringPattern", - ["markdown"] = "StringPattern", - ["xhtml"] = "NarrativeXhtmlPattern" - }; - /// /// Determines the subset of code to generate. /// @@ -251,4 +239,27 @@ public static int GetOrder(int relativeOrder) { return (relativeOrder * 10) + 10; } + + public static string BuildOpenAllowedTypesAttribute() => "[AllowedTypes(OpenChoice = true)]"; + + public static string BuildAllowedTypesAttribute(IEnumerable 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(); + } +} + + +public static class StringHelpers +{ + public static string EnsurePeriod(this string s) => s.EndsWith('.') ? s : s + "."; } diff --git a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/FirelyNetIG.cs b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/FirelyNetIG.cs index 1a1a64635..1fbf6c5f6 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/FirelyNetIG.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/FirelyNetIG.cs @@ -724,7 +724,7 @@ private void ProcessAndWriteValueSets() foreach ((string unversionedUrl, string[] versions) in _info.ValueSetVersions.OrderBy(kvp => kvp.Key)) { // check for exclusions - if (CSharpFirely2._exclusionSet.Contains(unversionedUrl)) + if (CSharpFirely2.ExclusionSet.Contains(unversionedUrl)) { continue; } @@ -808,7 +808,7 @@ private void ProcessAndWriteValueSet( // Enums and their containing classes cannot have the same name, // so we have to correct these here - if (CSharpFirely2._enumNamesOverride.TryGetValue(vs.Url, out var replacementName)) + if (CSharpFirely2.EnumNamesOverride.TryGetValue(vs.Url, out var replacementName)) { nameSanitized = replacementName; } @@ -2159,9 +2159,9 @@ private void WriteProfile(StructureDefinition sd) break; //throw new Exception($"Found multiple discriminators for {id}"); } - + if (discriminators.Length == 1) - { + { discriminator = discriminators[0]; bool isExtensionSlice = _findExtensionPathRegex.IsMatch(discriminator.Path); @@ -3361,7 +3361,7 @@ private ExtensionData GetExtensionData( remarks = (remarks == null ? string.Empty : remarks + "\n") + $"Structure Definition Name: {cd.Structure.Name}"; } - + string directive; if (_info.TryGetPackageSource(cd.Structure, out string packageId, out string packageVersion)) { @@ -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", diff --git a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/TypeReference.cs b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/TypeReference.cs index 19b7b2a09..cf8360a8b 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/TypeReference.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/Language/Firely/TypeReference.cs @@ -1,5 +1,3 @@ -#nullable enable - using Microsoft.Health.Fhir.CodeGenCommon.Extensions; using Microsoft.Health.Fhir.CodeGenCommon.Utils; @@ -7,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 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) @@ -43,27 +54,42 @@ public record PrimitiveTypeReference(string Name, string PocoTypeName, Type Conv public static PrimitiveTypeReference ForTypeName(string name, Type propertyType) => new(name, MapTypeName(name), propertyType); + public static readonly PrimitiveTypeReference PrimitiveType = ForTypeName("PrimitiveType", typeof(object)); + public static readonly PrimitiveTypeReference Boolean = ForTypeName("boolean", typeof(bool)); + public static readonly PrimitiveTypeReference Base64Binary = ForTypeName("base64Binary", typeof(byte[])); + public static readonly PrimitiveTypeReference Canonical = ForTypeName("canonical", typeof(string)); + public static readonly PrimitiveTypeReference Code = ForTypeName("code", typeof(string)); + public static readonly PrimitiveTypeReference Date = ForTypeName("date", typeof(string)); + public static readonly PrimitiveTypeReference DateTime = ForTypeName("dateTime", typeof(string)); + public static readonly PrimitiveTypeReference Decimal = ForTypeName("decimal", typeof(decimal)); + public static readonly PrimitiveTypeReference Id = ForTypeName("id", typeof(string)); + public static readonly PrimitiveTypeReference Instant = ForTypeName("instant", typeof(DateTimeOffset)); + public static readonly PrimitiveTypeReference Integer = ForTypeName("integer", typeof(int)); + public static readonly PrimitiveTypeReference Integer64 = ForTypeName("integer64", typeof(long)); + public static readonly PrimitiveTypeReference Oid = ForTypeName("oid", typeof(string)); + public static readonly PrimitiveTypeReference PositiveInt = ForTypeName("positiveInt", typeof(int)); + public static readonly PrimitiveTypeReference String = ForTypeName("string", typeof(string)); + public static readonly PrimitiveTypeReference Time = ForTypeName("time", typeof(string)); + public static readonly PrimitiveTypeReference UnsignedInt = ForTypeName("unsignedInt", typeof(int)); + public static readonly PrimitiveTypeReference Uri = ForTypeName("uri", typeof(string)); + public static readonly PrimitiveTypeReference Url = ForTypeName("url", typeof(string)); + public static readonly PrimitiveTypeReference Xhtml = ForTypeName("xhtml", typeof(string)); + public static readonly PrimitiveTypeReference Markdown = ForTypeName("markdown", typeof(string)); + public static readonly IReadOnlyCollection PrimitiveList = [ - ForTypeName("base64Binary", typeof(byte[])), ForTypeName("boolean", typeof(bool)), - ForTypeName("canonical", typeof(string)), ForTypeName("code", typeof(string)), - ForTypeName("date", typeof(string)), ForTypeName("dateTime", typeof(string)), - ForTypeName("decimal", typeof(decimal)), ForTypeName("id", typeof(string)), - ForTypeName("instant", typeof(DateTimeOffset)), ForTypeName("integer", typeof(int)), - ForTypeName("integer64", typeof(long)), ForTypeName("oid", typeof(string)), - ForTypeName("positiveInt", typeof(int)), ForTypeName("string", typeof(string)), - ForTypeName("time", typeof(string)), ForTypeName("unsignedInt", typeof(int)), - ForTypeName("uri", typeof(string)), ForTypeName("url", typeof(string)), - ForTypeName("xhtml", typeof(string)), ForTypeName("markdown", typeof(string)) + Boolean, Base64Binary, Canonical, Code, Date, DateTime, Decimal, Id, + Instant, Integer, Integer64, Oid, PositiveInt, String, Time, UnsignedInt, + Uri, Url, Xhtml, Markdown ]; - private static readonly Dictionary s_primitiveDictionary = + private static readonly Dictionary _primitiveDictionary = PrimitiveList.ToDictionary(ptr => ptr.Name); - public static bool IsFhirPrimitiveType(string name) => s_primitiveDictionary.ContainsKey(name); + public static bool IsFhirPrimitiveType(string name) => _primitiveDictionary.ContainsKey(name); public static PrimitiveTypeReference GetTypeReference(string name) => - s_primitiveDictionary.TryGetValue(name, out var tr) + _primitiveDictionary.TryGetValue(name, out var tr) ? tr : throw new InvalidOperationException($"Unknown FHIR primitive {name}"); @@ -83,10 +109,12 @@ public record CqlTypeReference(string Name, Type PropertyType) : TypeReference(N public record ComplexTypeReference(string Name, string PocoTypeName) : TypeReference(Name) { + public ComplexTypeReference(string name) : this(name, name) { } + public override string PropertyTypeString => $"Hl7.Fhir.Model.{PocoTypeName}"; -} -public record ChoiceTypeReference() : ComplexTypeReference("DataType", "DataType"); + public static readonly ComplexTypeReference DataTypeReference = new("DataType"); +} public record CodedTypeReference(string EnumName, string? EnumClassName) : PrimitiveTypeReference("code", EnumName, typeof(Enum)) diff --git a/src/Microsoft.Health.Fhir.CodeGen/Loader/PackageLoader.cs b/src/Microsoft.Health.Fhir.CodeGen/Loader/PackageLoader.cs index 2ee8e348e..75126bda7 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Loader/PackageLoader.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/Loader/PackageLoader.cs @@ -126,7 +126,7 @@ public PackageLoader(ConfigRoot? config = null, LoaderOptions? opts = null) _defaultFhirVersion = FhirReleases.FhirVersionToSequence(_rootConfiguration.FhirVersion); - _jsonOptions = opts.FhirJsonOptions; + _jsonOptions = opts.FhirJsonOptions.UsingMode(DeserializerModes.Ostrich); _jsonParser = new(opts.FhirJsonSettings); #if !DISABLE_XML diff --git a/src/Microsoft.Health.Fhir.CodeGen/Microsoft.Health.Fhir.CodeGen.csproj b/src/Microsoft.Health.Fhir.CodeGen/Microsoft.Health.Fhir.CodeGen.csproj index cef55dd98..f5018a55c 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Microsoft.Health.Fhir.CodeGen.csproj +++ b/src/Microsoft.Health.Fhir.CodeGen/Microsoft.Health.Fhir.CodeGen.csproj @@ -33,7 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -51,5 +51,5 @@ - + diff --git a/src/Microsoft.Health.Fhir.CodeGen/Models/ComponentDefinition.cs b/src/Microsoft.Health.Fhir.CodeGen/Models/ComponentDefinition.cs index c477ed497..932b79295 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Models/ComponentDefinition.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/Models/ComponentDefinition.cs @@ -60,7 +60,7 @@ public ComponentDefinition(StructureDefinition sd) /// Cg explicit name. /// A string. - public string cgExplicitName() => Element?.cgExplicitName() ?? string.Empty; + public string? cgExplicitName() => Element.cgExplicitName(); /// Gets the code generation name. /// Note: Firely generation uses this version. diff --git a/src/Microsoft.Health.Fhir.CodeGen/Models/DefinitionCollection.cs b/src/Microsoft.Health.Fhir.CodeGen/Models/DefinitionCollection.cs index c6baa3412..ce2c877b2 100644 --- a/src/Microsoft.Health.Fhir.CodeGen/Models/DefinitionCollection.cs +++ b/src/Microsoft.Health.Fhir.CodeGen/Models/DefinitionCollection.cs @@ -854,8 +854,14 @@ private void ProcessParameters(OperationDefinition op) fo = outFieldOrder.Count + 1; outFieldOrder.Add(pc.Name, fo); } - else + else { + if (inFieldOrder.ContainsKey(pc.Name)) + { + Console.WriteLine($"Operation: {op.Id} ({op.Url}) defines the parameter {pc.Name} more than once!!!"); + continue; + } + fo = inFieldOrder.Count + 1; inFieldOrder.Add(pc.Name, fo); } diff --git a/src/Microsoft.Health.Fhir.CodeGenCommon/Extensions/FhirNameConventionExtensions.cs b/src/Microsoft.Health.Fhir.CodeGenCommon/Extensions/FhirNameConventionExtensions.cs index 893967a04..54b7c976b 100644 --- a/src/Microsoft.Health.Fhir.CodeGenCommon/Extensions/FhirNameConventionExtensions.cs +++ b/src/Microsoft.Health.Fhir.CodeGenCommon/Extensions/FhirNameConventionExtensions.cs @@ -95,7 +95,7 @@ public static string ToPascalCase( return string.Join(joinDelimiter, word.Split(delimitersToRemove, _wordSplitOptions).Select(w => w.ToPascalCase(false))); } - return string.Concat(word.Substring(0, 1).ToUpperInvariant(), word.Substring(1)); + return string.Concat(word[..1].ToUpperInvariant(), word[1..]); } /// An extension method that converts an array of words each to PascalCase. @@ -163,10 +163,10 @@ public static string ToCamelCase( { // converting to pascal and changing the initial letter is faster than accumulating here string pc = word.ToPascalCase(removeDelimiters, joinDelimiter, delimitersToRemove); - return string.Concat(pc.Substring(0, 1).ToLowerInvariant(), pc.Substring(1)); + return string.Concat(pc[..1].ToLowerInvariant(), pc[1..]); } - return string.Concat(word.Substring(0, 1).ToLowerInvariant(), word.Substring(1)); + return string.Concat(word[..1].ToLowerInvariant(), word[1..]); } /// An extension method that converts an array of words each to camelCase. diff --git a/src/Microsoft.Health.Fhir.CodeGenCommon/Microsoft.Health.Fhir.CodeGenCommon.csproj b/src/Microsoft.Health.Fhir.CodeGenCommon/Microsoft.Health.Fhir.CodeGenCommon.csproj index 2ed73f60f..7fbee7921 100644 --- a/src/Microsoft.Health.Fhir.CodeGenCommon/Microsoft.Health.Fhir.CodeGenCommon.csproj +++ b/src/Microsoft.Health.Fhir.CodeGenCommon/Microsoft.Health.Fhir.CodeGenCommon.csproj @@ -10,7 +10,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.Health.Fhir.CodeGenCommon/Packaging/FhirReleases.cs b/src/Microsoft.Health.Fhir.CodeGenCommon/Packaging/FhirReleases.cs index b6617fc12..f2ee611a3 100644 --- a/src/Microsoft.Health.Fhir.CodeGenCommon/Packaging/FhirReleases.cs +++ b/src/Microsoft.Health.Fhir.CodeGenCommon/Packaging/FhirReleases.cs @@ -37,7 +37,7 @@ public enum FhirSequenceCodes : int /// FHIR R5. R5 = 5, - /// FHIR R5. + /// FHIR R6. R6 = 6, } diff --git a/src/Microsoft.Health.Fhir.CrossVersion/Microsoft.Health.Fhir.CrossVersion.csproj b/src/Microsoft.Health.Fhir.CrossVersion/Microsoft.Health.Fhir.CrossVersion.csproj index 7c434ab8b..735bed944 100644 --- a/src/Microsoft.Health.Fhir.CrossVersion/Microsoft.Health.Fhir.CrossVersion.csproj +++ b/src/Microsoft.Health.Fhir.CrossVersion/Microsoft.Health.Fhir.CrossVersion.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.Health.Fhir.MappingLanguage/Microsoft.Health.Fhir.MappingLanguage.csproj b/src/Microsoft.Health.Fhir.MappingLanguage/Microsoft.Health.Fhir.MappingLanguage.csproj index 594c46597..68c146ff8 100644 --- a/src/Microsoft.Health.Fhir.MappingLanguage/Microsoft.Health.Fhir.MappingLanguage.csproj +++ b/src/Microsoft.Health.Fhir.MappingLanguage/Microsoft.Health.Fhir.MappingLanguage.csproj @@ -16,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/fhir-codegen/Properties/launchSettings.json b/src/fhir-codegen/Properties/launchSettings.json index 0ad39befd..baded558a 100644 --- a/src/fhir-codegen/Properties/launchSettings.json +++ b/src/fhir-codegen/Properties/launchSettings.json @@ -7,157 +7,157 @@ }, "Firely 5.x Base": { "commandName": "Project", - "commandLineArgs": "generate CSharpFirely2 --output-path ..\\..\\..\\firely-net-sdk\\src\\Hl7.Fhir.Base\\Model -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0 --subset base", + "commandLineArgs": "generate CSharpFirely2 --output-path ../../../firely-net-sdk/src/Hl7.Fhir.Base/Model -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0 --subset base", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely 5.x Conformance": { "commandName": "Project", - "commandLineArgs": "generate CSharpFirely2 --output-path ..\\..\\..\\firely-net-sdk\\src\\Hl7.Fhir.Conformance\\Model -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0 --subset conformance", + "commandLineArgs": "generate CSharpFirely2 --output-path ../../../firely-net-sdk/src/Hl7.Fhir.Conformance/Model -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0 --subset conformance", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely 5.x STU3": { "commandName": "Project", - "commandLineArgs": "generate CSharpFirely2 --output-path ..\\..\\..\\firely-net-sdk\\src\\Hl7.Fhir.STU3\\Model -p hl7.fhir.r3.core#3.0.2 -p hl7.fhir.r3.expansions#3.0.2 --subset satellite", + "commandLineArgs": "generate CSharpFirely2 --output-path ../../../firely-net-sdk/src/Hl7.Fhir.STU3/Model -p hl7.fhir.r3.core#3.0.2 -p hl7.fhir.r3.expansions#3.0.2 --subset satellite", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely 5.x R4": { "commandName": "Project", - "commandLineArgs": "generate CSharpFirely2 --output-path ..\\..\\..\\firely-net-sdk\\src\\Hl7.Fhir.R4\\Model -p hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r4.expansions#4.0.1 --subset satellite --cql-model Fhir401", + "commandLineArgs": "generate CSharpFirely2 --output-path ../../../firely-net-sdk/src/Hl7.Fhir.R4/Model -p hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r4.expansions#4.0.1 --subset satellite --cql-model Fhir401", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely 5.x R4B": { "commandName": "Project", - "commandLineArgs": "generate CSharpFirely2 --output-path ..\\..\\..\\firely-net-sdk\\src\\Hl7.Fhir.R4B\\Model -p hl7.fhir.r4b.core#4.3.0 -p hl7.fhir.r4b.expansions#4.3.0 --subset satellite", + "commandLineArgs": "generate CSharpFirely2 --output-path ../../../firely-net-sdk/src/Hl7.Fhir.R4B/Model -p hl7.fhir.r4b.core#4.3.0 -p hl7.fhir.r4b.expansions#4.3.0 --subset satellite", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely 5.x R5": { "commandName": "Project", - "commandLineArgs": "generate CSharpFirely2 --output-path ..\\..\\..\\firely-net-sdk\\src\\Hl7.Fhir.R5\\Model -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0 --subset satellite", + "commandLineArgs": "generate CSharpFirely2 --output-path ../../../firely-net-sdk/src/Hl7.Fhir.R5/Model -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0 --subset satellite", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely 5.x R6": { "commandName": "Project", - "commandLineArgs": "generate CSharpFirely2 --output-path ..\\..\\..\\firely-net-sdk\\src\\Hl7.Fhir.R6\\Model -p hl7.fhir.r6.core#6.0.0-ballot2 -p hl7.fhir.r6.expansions#6.0.0-ballot2 --subset satellite", + "commandLineArgs": "generate CSharpFirely2 --output-path ../../../firely-net-sdk/src/Hl7.Fhir.R6/Model -p hl7.fhir.r6.core#6.0.0-ballot3 -p hl7.fhir.r6.expansions#6.0.0-ballot3 --subset satellite", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely IG Backport": { "commandName": "Project", - "commandLineArgs": "generate FirelyNetIg --output-path ..\\..\\temp\\firely-ig -p hl7.fhir.uv.subscriptions-backport#1.1.0 --fhir-version 4.0.1 --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate FirelyNetIg --output-path ../../temp/firely-ig -p hl7.fhir.uv.subscriptions-backport#1.1.0 --fhir-version 4.0.1 --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely IG US Core": { "commandName": "Project", - "commandLineArgs": "generate FirelyNetIg --output-path ..\\..\\temp\\firely-ig -p hl7.fhir.us.core#6.1.0 --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate FirelyNetIg --output-path ../../temp/firely-ig -p hl7.fhir.us.core#6.1.0 --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely IG Extensions Pack": { "commandName": "Project", - "commandLineArgs": "generate FirelyNetIg --output-path ..\\..\\temp\\firely-ig -p hl7.fhir.uv.extensions.r4#latest --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate FirelyNetIg --output-path ../../temp/firely-ig -p hl7.fhir.uv.extensions.r4#latest --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely IG NDH": { "commandName": "Project", - "commandLineArgs": "generate FirelyNetIg --output-path ..\\..\\temp\\firely-ig -p hl7.fhir.us.ndh#1.0.0-ballot --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate FirelyNetIg --output-path ../../temp/firely-ig -p hl7.fhir.us.ndh#1.0.0-ballot --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely IG SDC": { "commandName": "Project", - "commandLineArgs": "generate FirelyNetIg --output-path ..\\..\\temp\\firely-ig -p hl7.fhir.uv.sdc#3.0.0 --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate FirelyNetIg --output-path ../../temp/firely-ig -p hl7.fhir.uv.sdc#3.0.0 --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Firely OpenApi": { "commandName": "Project", - "commandLineArgs": "generate OpenApi --fhir-server-url https://secure.server.fire.ly/ -p hl7.fhir.r4.core#4.0.1 --output-path ..\\..\\generated\\FS-OpenApi-R4 --include-experimental --schema-level names --metadata true --multi-file true --single-responses false --resolve-server-canonicals false --resolve-external-canonicals false --basic-scopes-only true", + "commandLineArgs": "generate OpenApi --fhir-server-url https://secure.server.fire.ly/ -p hl7.fhir.r4.core#4.0.1 --output-path ../../generated/FS-OpenApi-R4 --include-experimental --schema-level names --metadata true --multi-file true --single-responses false --resolve-server-canonicals false --resolve-external-canonicals false --basic-scopes-only true", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Info R2": { "commandName": "Project", - "commandLineArgs": "generate Info --output-path ..\\..\\generated --output-filename Info_R2.txt -p hl7.fhir.r2.core@1.0.2 -p hl7.fhir.r2.expansions#1.0.2", + "commandLineArgs": "generate Info --output-path ../../generated --output-filename Info_R2.txt -p hl7.fhir.r2.core@1.0.2 -p hl7.fhir.r2.expansions#1.0.2", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Info R3": { "commandName": "Project", - "commandLineArgs": "generate Info --output-path ..\\..\\generated --output-filename Info_R3.txt -p hl7.fhir.r3.core@3.0.2 -p hl7.fhir.r3.expansions#3.0.2", + "commandLineArgs": "generate Info --output-path ../../generated --output-filename Info_R3.txt -p hl7.fhir.r3.core@3.0.2 -p hl7.fhir.r3.expansions#3.0.2", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Info R4": { "commandName": "Project", - "commandLineArgs": "generate Info --output-path ..\\..\\generated --output-filename Info_R4.txt -p hl7.fhir.r4.core@latest -p hl7.fhir.r4.expansions#4.0.1", + "commandLineArgs": "generate Info --output-path ../../generated --output-filename Info_R4.txt -p hl7.fhir.r4.core@latest -p hl7.fhir.r4.expansions#4.0.1", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Info R4B": { "commandName": "Project", - "commandLineArgs": "generate Info --output-path ..\\..\\generated --output-filename Info_R4B.txt -p hl7.fhir.r4b.core@4.3.0 -p hl7.fhir.r4b.expansions#4.3.0", + "commandLineArgs": "generate Info --output-path ../../generated --output-filename Info_R4B.txt -p hl7.fhir.r4b.core@4.3.0 -p hl7.fhir.r4b.expansions#4.3.0", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Info R5": { "commandName": "Project", - "commandLineArgs": "generate Info --output-path ..\\..\\generated --output-filename Info_R5.txt -p hl7.fhir.r5.core@5.0.0 -p hl7.fhir.r5.expansions#5.0.0", + "commandLineArgs": "generate Info --output-path ../../generated --output-filename Info_R5.txt -p hl7.fhir.r5.core@5.0.0 -p hl7.fhir.r5.expansions#5.0.0", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Shorthand IG US Core": { "commandName": "Project", - "commandLineArgs": "generate ShorthandIg --output-path ..\\..\\temp\\fsh-ig -p hl7.fhir.us.core#6.1.0 --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate ShorthandIg --output-path ../../temp/fsh-ig -p hl7.fhir.us.core#6.1.0 --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Shorthand IG Extensions Pack": { "commandName": "Project", - "commandLineArgs": "generate ShorthandIg --output-path ..\\..\\temp\\fsh-ig -p hl7.fhir.uv.extensions.r4#latest --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate ShorthandIg --output-path ../../temp/fsh-ig -p hl7.fhir.uv.extensions.r4#latest --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Shorthand IG NDH": { "commandName": "Project", - "commandLineArgs": "generate ShorthandIg --output-path ..\\..\\temp\\fsh-ig -p hl7.fhir.us.ndh#1.0.0-ballot --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate ShorthandIg --output-path ../../temp/fsh-ig -p hl7.fhir.us.ndh#1.0.0-ballot --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Shorthand IG SDC": { "commandName": "Project", - "commandLineArgs": "generate ShorthandIg --output-path ..\\..\\temp\\fsh-ig -p hl7.fhir.uv.sdc#3.0.0 --auto-load-expansions --resolve-dependencies true --include-experimental", + "commandLineArgs": "generate ShorthandIg --output-path ../../temp/fsh-ig -p hl7.fhir.uv.sdc#3.0.0 --auto-load-expansions --resolve-dependencies true --include-experimental", "workingDirectory": "$(MSBuildProjectDirectory)" }, "TypeScript R2": { "commandName": "Project", - "commandLineArgs": "generate TypeScript --output-path ..\\..\\generated --output-filename TypeScript_R2.ts -p hl7.fhir.r2.core#1.0.2 -p hl7.fhir.r2.expansions#1.0.2", + "commandLineArgs": "generate TypeScript --output-path ../../generated --output-filename TypeScript_R2.ts -p hl7.fhir.r2.core#1.0.2 -p hl7.fhir.r2.expansions#1.0.2", "workingDirectory": "$(MSBuildProjectDirectory)" }, "TypeScript R3": { "commandName": "Project", - "commandLineArgs": "generate TypeScript --output-path ..\\..\\generated --output-filename TypeScript_R3.ts -p hl7.fhir.r3.core#3.0.2 -p hl7.fhir.r3.expansions#3.0.2", + "commandLineArgs": "generate TypeScript --output-path ../../generated --output-filename TypeScript_R3.ts -p hl7.fhir.r3.core#3.0.2 -p hl7.fhir.r3.expansions#3.0.2", "workingDirectory": "$(MSBuildProjectDirectory)" }, "TypeScript R4": { "commandName": "Project", - "commandLineArgs": "generate TypeScript --output-path ..\\..\\generated --output-filename TypeScript_R4.ts -p hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r4.expansions#4.0.1", + "commandLineArgs": "generate TypeScript --output-path ../../generated --output-filename TypeScript_R4.ts -p hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r4.expansions#4.0.1", "workingDirectory": "$(MSBuildProjectDirectory)" }, "TypeScript R4B": { "commandName": "Project", - "commandLineArgs": "generate TypeScript --output-path ..\\..\\generated --output-filename TypeScript_R4B.ts -p hl7.fhir.r4b.core#4.3.0 -p hl7.fhir.r4b.expansions#4.3.0", + "commandLineArgs": "generate TypeScript --output-path ../../generated --output-filename TypeScript_R4B.ts -p hl7.fhir.r4b.core#4.3.0 -p hl7.fhir.r4b.expansions#4.3.0", "workingDirectory": "$(MSBuildProjectDirectory)" }, "TypeScript R5": { "commandName": "Project", - "commandLineArgs": "generate TypeScript --output-path ..\\..\\generated --output-filename TypeScript_R5.ts -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0", + "commandLineArgs": "generate TypeScript --output-path ../../generated --output-filename TypeScript_R5.ts -p hl7.fhir.r5.core#5.0.0 -p hl7.fhir.r5.expansions#5.0.0", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Ruby R4": { "commandName": "Project", - "commandLineArgs": "generate Ruby --output-path ..\\..\\generated\\Ruby_r4 -p hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r4.expansions#4.0.1", + "commandLineArgs": "generate Ruby --output-path ../../generated/Ruby_r4 -p hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r4.expansions#4.0.1", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Compare x-y": { "commandName": "Project", - "commandLineArgs": "compare -c hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r5.core#5.0.0 --auto-load-expansions --resolve-dependencies true --map-source-path ..\\..\\..\\fhir-cross-version --map-destination-path ..\\..\\..\\fhir-cross-version-source --map-save-style Source", + "commandLineArgs": "compare -c hl7.fhir.r4.core#4.0.1 -p hl7.fhir.r5.core#5.0.0 --auto-load-expansions --resolve-dependencies true --map-source-path ../../../fhir-cross-version --map-destination-path ../../../fhir-cross-version-source --map-save-style Source", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Compare 43-50": { "commandName": "Project", - "commandLineArgs": "compare -p hl7.fhir.r4b.core#4.3.0 -c hl7.fhir.r5.core#5.0.0 --auto-load-expansions --resolve-dependencies true --map-source-path ..\\..\\..\\fhir-cross-version --map-destination-path ..\\..\\..\\fhir-cross-version-source --map-save-style Source", + "commandLineArgs": "compare -p hl7.fhir.r4b.core#4.3.0 -c hl7.fhir.r5.core#5.0.0 --auto-load-expansions --resolve-dependencies true --map-source-path ../../../fhir-cross-version --map-destination-path ../../../fhir-cross-version-source --map-save-style Source", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Compare 50-43": { "commandName": "Project", - "commandLineArgs": "compare -p hl7.fhir.r5.core#5.0.0 -c hl7.fhir.r4b.core#4.3.0 --auto-load-expansions --resolve-dependencies true --map-source-path ..\\..\\..\\fhir-cross-version --map-destination-path ..\\..\\..\\fhir-cross-version-source --map-save-style Source", + "commandLineArgs": "compare -p hl7.fhir.r5.core#5.0.0 -c hl7.fhir.r4b.core#4.3.0 --auto-load-expansions --resolve-dependencies true --map-source-path ../../../fhir-cross-version --map-destination-path ../../../fhir-cross-version-source --map-save-style Source", "workingDirectory": "$(MSBuildProjectDirectory)" }, "Gui": { @@ -167,7 +167,7 @@ }, "Cross Version Review": { "commandName": "Project", - "commandLineArgs": "cross-version --output-path ..\\..\\temp\\cross-version", + "commandLineArgs": "cross-version --output-path ../../temp/cross-version", "workingDirectory": "$(MSBuildProjectDirectory)" }, "http": { diff --git a/src/fhir-codegen/fhir-codegen.csproj b/src/fhir-codegen/fhir-codegen.csproj index 270a5021f..574351448 100644 --- a/src/fhir-codegen/fhir-codegen.csproj +++ b/src/fhir-codegen/fhir-codegen.csproj @@ -37,7 +37,7 @@ - +