Repository: FHIR/sushi (ts)

Issue:
SUSHI should handle missing slicing information more gracefully
If a profile defines a sliced element without any corresponding slicing info, SUSHI may throw a de-referencing error when attempting to create an instance of that profile. This was reported on zulip here: https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Cannot.20read.20properties.20of.20undefined.20.28reading.20'length'.29

SUSHI should handle this better. I expect we can (or should) just ignore the error in most cases -- as there isn't much we can do, and it is often (usually? always?) harmless.

I've created a slimmed down example that reproduces it (note: it declares a dependency on hl7.fhir.us.davinci-hrex#1.0.0):
https://fshschool.org/FSHOnline/#/share/3RWLQRG

```
Alias: $consentscope = http://terminology.hl7.org/CodeSystem/consentscope
Alias: $v3-ActCode = http://terminology.hl7.org/CodeSystem/v3-ActCode
Alias: $provenance-participant-type = http://terminology.hl7.org/CodeSystem/provenance-participant-type
Alias: $v3-ParticipationType = http://terminology.hl7.org/CodeSystem/v3-ParticipationType
Alias: $consentaction = http://terminology.hl7.org/CodeSystem/consentaction

Profile: PDexMultiMemberMatchRequestParameterBundle
Parent: Parameters
Id: pdex-parameters-multi-member-match-bundle-in
* parameter
  * name = "memberMatchBundle" (exactly)
  * name MS
  * part 1..* MS
    * ^slicing.discriminator.type = #value
    * ^slicing.discriminator.path = "name"
    * ^slicing.rules = #closed
  * part contains
      Consent 1..1 MS
  * part[Consent]
    * name = "Consent" (exactly)
    * name MS
    * resource 1.. MS
    * resource only HRexConsent
    
Instance: multimembermatchpexample
InstanceOf: pdex-parameters-multi-member-match-bundle-in
* id = "payer-multi-member-match-in"
* parameter
* parameter[0].name = "memberMatchBundle"
* parameter[=].part[Consent]
  * name = "Consent"
  * resource
    * resourceType = "Consent"
    * id = "consent-1"
    * status = #active
    * scope = $consentscope#patient-privacy
    * category = $v3-ActCode#IDSCL
    * patient.reference = "Patient/1"
    * performer.reference = "Patient/1"
    * sourceReference.reference = "http://example.org/DocumentReference/someconsent-1"
    * policy.uri = "http://hl7.org/fhir/us/davinci-hrex/StructureDefinition-hrex-consent.html#regular"
    * provision
      * type = #permit
      * period
        * start = "2023-12-01"
        * end = "2024-03-31"
      * actor[source]
        * role = $provenance-participant-type#performer
        * reference 
          * identifier
            * system = "http://hl7.org/fhir/sid/us-npi"
            * value = "9876543210"
          * display = "Old Health Plan"
      * actor[recipient]
        * role = $v3-ParticipationType#IRCP
        * reference
          * identifier
            * system = "http://hl7.org/fhir/sid/us-npi"
            * value = "0123456789"
          * display = "New Health Plan"
      * action = $consentaction#disclose
```

Pull request description:
Handle when sliced element is not available when unfolding
Fixes #1408 and completes task [CIMPL-1228](https://standardhealthrecord.atlassian.net/browse/CIMPL-1228).

When unfolding a slice, the sliced element can be used as the source of the elements to add. The sliced element should always be available, but it may not be if the slice's structure definition is incomplete. In this case, the slice's type information will be used.



Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index 28a0330..6b8b2b8 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -2580,16 +2580,22 @@ export class ElementDefinition {
         // if this has a different profile than slicedElement, prefer to use that profile.
         // if this doesn't have a profile, or it has the same profile as slicedElement, then copy from slicedElement.
         const slicedElement = this.slicedElement();
-        if (!profileToUse) {
-          newElements = this.cloneChildren(slicedElement, false);
-        } else {
-          if (
+        if (slicedElement != null) {
+          if (!profileToUse) {
+            newElements = this.cloneChildren(slicedElement, false);
+          } else if (
             slicedElement.type.length === 1 &&
             slicedElement.type[0].profile?.length === 1 &&
             slicedElement.type[0].profile[0] === profileToUse
           ) {
             newElements = this.cloneChildren(slicedElement, false);
           }
+        } else {
+          // This can happen in the case of incomplete StructureDefinitions.
+          // When this happens, all we can do is continue to the next step to try and add the child elements.
+          logger.debug(
+            `Could not get sliced element for ${this.id}: StructureDefinition ${this.structDef.id} may be missing slicing information`
+          );
         }
       }
       if (newElements.length === 0) {


Test patch:
diff --git a/test/fhirtypes/ElementDefinition.test.ts b/test/fhirtypes/ElementDefinition.test.ts
index 4cb60c9..556fd0a 100644
--- a/test/fhirtypes/ElementDefinition.test.ts
+++ b/test/fhirtypes/ElementDefinition.test.ts
@@ -979,6 +979,27 @@ describe('ElementDefinition', () => {
       );
       expect(localSlice).toBeDefined();
     });
+
+    it('should not throw an error when unfolding a slice and the sliced element is not found', () => {
+      const telecom = practitioner.elements.find(e => e.path === 'Practitioner.telecom');
+      telecom.slicing = {
+        ordered: false,
+        rules: 'open',
+        discriminator: [{ type: 'value', path: 'system' }]
+      };
+      const telecomSlice = telecom.addSlice('FooSlice');
+      const telecomIndex = practitioner.elements.findIndex(e => e.path === 'Practitioner.telecom');
+      practitioner.elements.splice(telecomIndex, 1);
+      const newElements = telecomSlice.unfold(fisher);
+      expect(newElements).toHaveLength(7);
+      expect(newElements[0].id).toBe('Practitioner.telecom:FooSlice.id');
+      expect(newElements[1].id).toBe('Practitioner.telecom:FooSlice.extension');
+      expect(newElements[2].id).toBe('Practitioner.telecom:FooSlice.system');
+      expect(newElements[3].id).toBe('Practitioner.telecom:FooSlice.value');
+      expect(newElements[4].id).toBe('Practitioner.telecom:FooSlice.use');
+      expect(newElements[5].id).toBe('Practitioner.telecom:FooSlice.rank');
+      expect(newElements[6].id).toBe('Practitioner.telecom:FooSlice.period');
+    });
   });
 
   describe('#clone', () => {