Repository: FHIR/sushi
Pull request #787 (2021-03-26 02:34:17)

Fix Extensions on ElementDefs
This fixes https://github.com/FHIR/sushi/issues/759.

The issue here was not actually related to child profiles, as is pointed out in the most recent comment. The issue was just that when we call the `cleanResource` function to clean a StructureDefinition of all of the extra properties that SUSHI puts on it, we ignored the `elements` array, because we didn't think those extra properties would end up there. But, in this example:
```
Profile: MyProfile
Parent: Patient
// This does not serialize correctly
* name ^extension[MyBooleanExtension].valueBoolean = true


Extension: MyBooleanExtension
* value[x] only boolean
```
Those elements do end up in the `elements` array, and are not correctly cleaned. This PR fixes that by now cleaning the `elements` array. We do still have to avoid the `structDef` element that exists on an ElementDefinition, because otherwise we will infinitely recurse.

You can test this using the example above, or by running the test on https://github.com/FHIR/sushi/commit/52609f626d8a8c8dbad9ec927cd79e10c94d9da5, where it should fail.


Patch:
diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts
index 6b0b754..3f6aec9 100644
--- a/src/export/StructureDefinitionExporter.ts
+++ b/src/export/StructureDefinitionExporter.ts
@@ -564,10 +564,10 @@ export class StructureDefinitionExporter implements Fishable {
     this.preprocessStructureDefinition(fshDefinition, structDef.type === 'Extension');
 
     this.setRules(structDef, fshDefinition);
-    // The elements list does not need to be cleaned up.
+    // The recursive structDef fields on elements should be ignored to avoid infinite looping
     // And, the _sliceName and _primitive properties added by SUSHI should be skipped.
     cleanResource(structDef, (prop: string) =>
-      ['elements', '_sliceName', '_primitive'].includes(prop)
+      ['structDef', '_sliceName', '_primitive'].includes(prop)
     );
     structDef.inProgress = false;
 


Test patch:
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index ddfd381..6baf356 100644
--- a/test/export/StructureDefinitionExporter.test.ts
+++ b/test/export/StructureDefinitionExporter.test.ts
@@ -3170,6 +3170,35 @@ describe('StructureDefinitionExporter', () => {
     });
   });
 
+  it('should apply a CaretValueRule on an extension on ElementDefinition', () => {
+    // Extension: MyBooleanExtension
+    // * value[x] only boolean
+    const extension = new Extension('MyBooleanExtension');
+    const onlyBoolean = new OnlyRule('value[x]');
+    onlyBoolean.types.push({ type: 'boolean' });
+    extension.rules.push(onlyBoolean);
+    doc.extensions.set(extension.name, extension);
+    // Profile: ExtensionOnName
+    // Parent: Patient
+    // * name ^extension[MyBooleanExtension].valueBoolean = true
+    const profile = new Profile('ExtensionOnName');
+    profile.parent = 'Patient';
+    const rule = new CaretValueRule('name');
+    rule.caretPath = 'extension[MyBooleanExtension].valueBoolean';
+    rule.value = true;
+    profile.rules.push(rule);
+
+    exporter.exportStructDef(profile);
+    const sd = pkg.profiles[0];
+    const name = sd.findElement('Patient.name');
+    expect(name.extension).toEqual([
+      {
+        url: 'http://hl7.org/fhir/us/minimal/StructureDefinition/MyBooleanExtension',
+        valueBoolean: true
+      }
+    ]);
+  });
+
   it('should not apply an invalid CaretValueRule on an element without a path', () => {
     const profile = new Profile('Foo');
     profile.parent = 'Observation';