Repository: FHIR/sushi
Pull request #955 (2021-11-15 21:33:30)

Include inherited constraints on slices
See https://github.com/FHIR/sushi/issues/932 for an explanation.

See https://fshschool.org/FSHOnline/#/share/3wQHuPR for an example to recreate this. 

On `master`, when you run this through SUSHI and the IG Publisher, you will see an error like the following:

![image](https://user-images.githubusercontent.com/52747882/141856741-aeac4054-7303-431c-ace6-c9a7ff6220d5.png)

On this branch you should see no error.

I've noticed that this PR does cause some regressions, but I looked through them and did not see any issues that were not expected from this change. But I would appreciate if reviewers also take a look.

cmoesel: @ngfreiter - I think you’ve convinced me. This is a LOT of churn and redundant differentials just to address a limitation in the IG Publisher. I think that maybe we should only address the immediate issue at hand, which is that *extensions* need to be copied from the slice base to the slice. If you agree with me, then I say go for it (and sorry for making you jump through all these hoops). If you disagree, we can continue to discuss when I get back.
ngfreiter: Ok @cmoesel, I think https://github.com/FHIR/sushi/pull/955/commits/fe022920631786229fa5eeceea88d03599df57ca should work as you described only on extenionsions.
cmoesel: OK.  Cool.  I'm going to run regression on this again and see where we land!

Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index 2239bc4..4624658 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -2136,7 +2136,7 @@ export class ElementDefinition {
       } else if (this.sliceName) {
         // If the element is sliced, we first try to unfold from the SD itself
         const slicedElement = this.slicedElement();
-        newElements = this.cloneChildren(slicedElement);
+        newElements = this.cloneChildren(slicedElement, false);
       }
       if (newElements.length === 0) {
         // If we have exactly one profile to use, use that, otherwise use the code
@@ -2176,15 +2176,25 @@ export class ElementDefinition {
   /**
    * Returns an array of an ElementDefinition's unfolded children.
    * @param {ElementDefinition} targetElement - The ElementDefinition being unfolded
+   * @param {boolean} recaptureSliceExtensions - Indicates whether or not slice extensions should be recaptured
    * @returns {ElementDefinition[]} An array of the targetElement's children, with the IDs altered and
    * the original property re-captured.
    */
-  private cloneChildren(targetElement: ElementDefinition): ElementDefinition[] {
+  private cloneChildren(
+    targetElement: ElementDefinition,
+    recaptureSliceExtensions = true
+  ): ElementDefinition[] {
     return targetElement?.children().map(e => {
-      const eClone = e.clone();
+      // Sometimes we want to avoid recapturing extensions, but if an element is not a slice
+      // extension, we always capture it
+      const shouldCaptureOriginal =
+        recaptureSliceExtensions || e.sliceName == null || !e.path.endsWith('.extension');
+      const eClone = e.clone(shouldCaptureOriginal);
       eClone.id = eClone.id.replace(targetElement.id, this.id);
       eClone.structDef = this.structDef;
-      eClone.captureOriginal();
+      if (shouldCaptureOriginal) {
+        eClone.captureOriginal();
+      }
       return eClone;
     });
   }


Test patch:
diff --git a/test/fhirtypes/ElementDefinition.test.ts b/test/fhirtypes/ElementDefinition.test.ts
index c1511c7..cd0ec13 100644
--- a/test/fhirtypes/ElementDefinition.test.ts
+++ b/test/fhirtypes/ElementDefinition.test.ts
@@ -701,6 +701,38 @@ describe('ElementDefinition', () => {
       expect(observation.elements).toHaveLength(numOriginalElements);
       expect(observation.elements[valueIdx + 1].id).toBe('Observation.dataAbsentReason');
     });
+
+    it('should add a slice from the structure definition if it exists, while preserving all constraints', () => {
+      const extension = observation.elements.find(
+        e => e.path === 'Observation.component.extension'
+      );
+      extension.slicing = {
+        discriminator: [
+          {
+            type: 'value',
+            path: 'url'
+          }
+        ],
+        ordered: false,
+        rules: 'open'
+      };
+      const extensionSlice = extension.addSlice('Test');
+      extensionSlice.type[0].profile = [
+        'http://example.org/fhir/StructureDefinition/ExampleExtension'
+      ];
+      const component = observation.elements.find(e => e.path === 'Observation.component');
+      component.slicing = {
+        ordered: false,
+        rules: 'open',
+        discriminator: [{ type: 'value', path: 'code' }]
+      };
+      const componentSlice = component.addSlice('FooSlice');
+      const newElements = componentSlice.unfold(fisher);
+      const fooSliceExtensionTest = newElements.find(
+        e => e.id === 'Observation.component:FooSlice.extension:Test'
+      );
+      expect(fooSliceExtensionTest.calculateDiff().type).toEqual(extensionSlice.type);
+    });
   });
 
   describe('#clone', () => {