Repository: FHIR/sushi (ts)

Issue:
SUSHI fails to find element slices when other elements have the same slice name
_This issue is based on my findings while investigation a bug reported on Zulip [here](https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Bug.3A.20No.20element.20found.20at.20path)._

If the same slice name is used on multiple elements, and some of these elements have paths that are sub-paths of others, SUSHI may fail to find some slice elements.  For example, given elements w/ these ids:
```
Observation.category:Foo
Observation.category:Foo.coding:Bar
Observation.category:Bar
```
if you have a rule like this in SUSHI:
```
* category[Bar] MS
```
then SUSHI will fail to find the element, reporting an error like:
> No element found at path category[Bar] for FlagRule in ObsProfile, skipping rule errors.

This is because the logic to find the path  mistakenly focuses in on the first path that has slicename `Bar` - even if the path isn't a complete match, but then later notices that the paths don't match and returns `undefined`.  SUSHI needs to be updated to consider the path throughout the element matching algorithm.

Pull request description:
Fix findElementByPath w/ duplicated slicenames
Fixes #956

When multiple elements have the same slicename (but different paths), sometimes SUSHI fails to find slice elements. This PR fixes it by ensuring that SUSHI takes the full path into account when trying to find slice elements.

There is a unit test that shows this. If you copy it to the test suite in master, it will fail. But in this PR branch, it passes.  (Sorry I forgot to make a separate commit for just the test).

If you want to test manually, you'll have to follow a few steps:
* Download the test project here: https://github.com/patrick-werner/testDerivedAddress
* Run SUSHI on it.  There will be errors about missing snapshots.  This is expected.
* Unzip the de.basisprofil.r4-1.1.0.tgz file at the root of the project and copy it's contents to your FHIR cache in the appropriate place.  E.g., the `package` folder in the zip should replace `~/.fhir/packages/de.basisprofil.r4#1.1.0/package` folder in your FHIR cache.
* Run SUSHI again.

On master, you get errors about not being able to find `address[Postfach]`.  On this branch, you do not get those errors.

Patch:
diff --git a/src/fhirtypes/StructureDefinition.ts b/src/fhirtypes/StructureDefinition.ts
index c479d4b..29224ec 100644
--- a/src/fhirtypes/StructureDefinition.ts
+++ b/src/fhirtypes/StructureDefinition.ts
@@ -270,7 +270,12 @@ export class StructureDefinition {
 
       // After getting matches based on the 'base' part, we now filter according to 'brackets'
       if (pathPart.brackets) {
-        const sliceElement = this.findMatchingSlice(pathPart, matchingElements, fisher);
+        const sliceElement = this.findMatchingSlice(
+          fhirPathString,
+          pathPart,
+          matchingElements,
+          fisher
+        );
         if (sliceElement) {
           matchingElements = [sliceElement, ...sliceElement.children()];
         } else {
@@ -697,18 +702,22 @@ export class StructureDefinition {
 
   /**
    * Looks for a slice within the set of elements that matches the fhirPath
-   * @param {PathPart} pathPart - The path to match sliceName against
+   * @param {string} fhirPathString - the current FHIR path to match against
+   * @param {PathPart} pathPart - The path part to match sliceName against
    * @param {ElementDefinition[]} elements - The set of elements to search through
    * @param {Fishable} fisher - A fishable implementation for finding definitions and metadata
    * @returns {ElementDefinition} - The sliceElement if found, else undefined
    */
   private findMatchingSlice(
+    fhirPathString: string,
     pathPart: PathPart,
     elements: ElementDefinition[],
     fisher: Fishable
   ): ElementDefinition {
     let matchingSlice: ElementDefinition;
-    matchingSlice = elements.find(e => e.sliceName === pathPart.brackets.join('/'));
+    matchingSlice = elements.find(
+      e => e.path === fhirPathString && 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
@@ -716,7 +725,7 @@ export class StructureDefinition {
         const connectedSliceElement = e.findConnectedSliceElement();
         const matchingConnectedSlice = connectedSliceElement
           ?.getSlices()
-          .find(e => e.sliceName === pathPart.brackets.join('/'));
+          .find(e => e.path === fhirPathString && e.sliceName === pathPart.brackets.join('/'));
 
         if (matchingConnectedSlice) {
           const newSlice = matchingConnectedSlice.clone(false);


Test patch:
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index 9399778..b032c57 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -885,6 +885,20 @@ describe('StructureDefinition', () => {
       expect(VSCat.id).toBe('Observation.category:VSCat');
     });
 
+    it('should find a sliced element by path when a previous element in a different slice has a child w/ the same slice name', () => {
+      // This test is intended to reproduce the problematic edge case in this issue: https://github.com/FHIR/sushi/issues/956
+      const Cat = respRate.findElement('Observation.category');
+      Cat.addSlice('CommonName');
+
+      const VSCatCoding = respRate.findElement('Observation.category:VSCat.coding');
+      VSCatCoding.slicing = { discriminator: [{ type: 'pattern', path: '$this' }], rules: 'open' };
+      VSCatCoding.addSlice('CommonName');
+
+      const VSCat = respRate.findElementByPath('category[CommonName]', fisher);
+      expect(VSCat).toBeDefined();
+      expect(VSCat.id).toBe('Observation.category:CommonName');
+    });
+
     it('should find a child of a sliced element by path', () => {
       const VSCatID = respRate.findElementByPath('category[VSCat].id', fisher);
       expect(VSCatID).toBeDefined();