Repository: FHIR/sushi
Pull request #326 (2020-04-01 15:49:08)

Find slices of root element on slice children
This addresses https://standardhealthrecord.atlassian.net/browse/CIMPL-305.

This fixes the issue described in this comment: https://github.com/FHIR/sushi/pull/296#pullrequestreview-381986468. The `findElementByPath` function was unable to find an element when that element was a slice of an element that is itself a child of a slice. This is the FSH that I used to test this out:
```
Profile: FunWithSlicing
Parent: resprate
* category.coding ^slicing.discriminator.path = "$this"
* category.coding ^slicing.discriminator.type = #pattern
* category.coding ^slicing.rules = #open 
* category.coding contains SomeSlice 1..5
* category[VSCat].coding[SomeSlice] ^short = "DO I EXIST"
```
Be aware that to truly test this, you will want to wait until the fixes in https://github.com/FHIR/sushi/pull/325 have been merged. Although this PR does not explicitly depend on that one, the bugs in the IG Publisher that #325 fix will make it difficult to test this PR in the IG Publisher.

ngfreiter: Ok @mint-thompson , both your comments should be fixed in the latest commit.
mint-thompson: I'm still sort of wondering what kind of situation would lead to a case where the existing slicing exists, and is different from `connectedSliceElement.slicing`, but I think it's fine for now.

Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index bf860c1..cf577af 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -547,6 +547,17 @@ export class ElementDefinition {
     }
   }
 
+  findConnectedSliceElement(postPath = ''): ElementDefinition {
+    const slicingRoot = this.slicedElement();
+    if (slicingRoot) {
+      return this.structDef.findElement(`${slicingRoot.id}${postPath}`);
+    } else if (this.parent()) {
+      return this.parent().findConnectedSliceElement(
+        `.${this.path.split('.').slice(-1)[0]}${postPath}`
+      );
+    }
+  }
+
   /**
    * Checks if the sum of slice mins exceeds the max of sliced element, and returns
    * the sum if so.
diff --git a/src/fhirtypes/StructureDefinition.ts b/src/fhirtypes/StructureDefinition.ts
index a6bd9da..8b556da 100644
--- a/src/fhirtypes/StructureDefinition.ts
+++ b/src/fhirtypes/StructureDefinition.ts
@@ -605,6 +605,24 @@ export class StructureDefinition {
   ): ElementDefinition {
     let matchingSlice = elements.find(e => e.sliceName === pathPart.brackets.join('/'));
     if (!matchingSlice && pathPart.brackets?.length === 1) {
+      // If the current element is a child of a slice, the match may exist on the original
+      // sliced element, search for that here
+      for (const e of elements) {
+        const connectedSliceElement = e.findConnectedSliceElement();
+        const matchingConnectedSlice = connectedSliceElement
+          ?.getSlices()
+          .find(e => e.sliceName === pathPart.brackets.join('/'));
+
+        if (matchingConnectedSlice) {
+          const newSlice = matchingConnectedSlice.clone(false);
+          newSlice.id = `${e.id}:${matchingConnectedSlice.sliceName}`;
+          newSlice.structDef = this;
+          if (!e.slicing) e.slicing = connectedSliceElement.slicing;
+          this.addElement(newSlice);
+          return newSlice;
+        }
+      }
+
       // If we don't find a match, search predefined extensions for a match
       const sliceDefinition = fisher.fishForFHIR(pathPart.brackets[0], Type.Extension);
       if (sliceDefinition?.url) {


Test patch:
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index 0727eca..604e788 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -553,6 +553,53 @@ describe('StructureDefinition', () => {
       expect(foundRoot.id).toBe('Observation.category:VSCat.coding');
     });
 
+    it('should find a slice that only exists on a sliced element child', () => {
+      const coding = respRate.findElementByPath('category.coding', fisher);
+      coding.slicing = {
+        discriminator: [{ type: 'pattern', path: '$this' }],
+        rules: 'open'
+      };
+      coding.addSlice('RegularSlice');
+      const originalLength = respRate.elements.length;
+      const root = respRate.findElement('Observation.category:VSCat.coding');
+      const slice = respRate.findElementByPath('category[VSCat].coding[RegularSlice]', fisher);
+      expect(slice).toBeDefined();
+      expect(slice.id).toBe('Observation.category:VSCat.coding:RegularSlice');
+      expect(root.slicing).toEqual(coding.slicing);
+      expect(respRate.elements.length).toBe(originalLength + 1);
+    });
+
+    it('should find a slice that only exists on a sliced element grandchild', () => {
+      const id = respRate.findElementByPath('category.coding.id', fisher);
+      id.slicing = {
+        discriminator: [{ type: 'pattern', path: '$this' }],
+        rules: 'open'
+      };
+      id.addSlice('RegularSlice');
+      const originalLength = respRate.elements.length;
+      const root = respRate.findElement('Observation.category:VSCat.coding.id');
+      const slice = respRate.findElementByPath('category[VSCat].coding.id[RegularSlice]', fisher);
+      expect(root.slicing).toEqual(id.slicing);
+      expect(slice).toBeDefined();
+      expect(slice.id).toBe('Observation.category:VSCat.coding.id:RegularSlice');
+      expect(respRate.elements.length).toBe(originalLength + 1);
+    });
+
+    it('should not find a slice that does not exist on a sliced root child', () => {
+      const regularCoding = respRate.findElementByPath('category.coding', fisher);
+      regularCoding.slicing = {
+        discriminator: [{ type: 'pattern', path: '$this' }],
+        rules: 'open'
+      };
+      regularCoding.addSlice('RegularSlice');
+      const originalLength = respRate.elements.length;
+      const root = respRate.findElement('Observation.category:VSCat.coding');
+      const slice = respRate.findElementByPath('category[VSCat].coding[IrregularSlice]', fisher);
+      expect(root.slicing).toBeUndefined();
+      expect(slice).toBeUndefined();
+      expect(respRate.elements.length).toBe(originalLength);
+    });
+
     it('should find a re-sliced element by path', () => {
       const jsonReslice = JSON.parse(
         fs.readFileSync(