Repository: FHIR/sushi (ts)

Issue:
Log error for ValueSet self-reference
Transferred from internal JIRA issue originally created 2024-03-06.

A component rule on a ValueSet should not reference itself. If this happens, log an error and skip the rule.

```
Alias: SCT = http://snomed.info/sct

Profile: MyServiceReqeust
Parent: ServiceRequest
Title: "Radiotherapy Plan Prescription"
Description: "A Radiotherapy Plan Prescription is a request for radiotherapy treatment with a single treatment plan."
* category from MaxVS (extensible)

ValueSet: MaxVS
Title: "Category Code Max Value Set"
Description: "Category value set required to support backward compatibility by including an inactive SNOMED code"
* ^experimental = false
* include codes from valueset MaxVS
* SCT#108290001 "Radiation oncology AND/OR radiotherapy (procedure)" // this code is inactive  
```
In the above FSH, the rule `* include codes from valueset MaxVS` should cause an error and be skipped.

Pull request description:
ValueSet self reference
**Description:** This PR addresses when a component rule on a ValueSet attempts to self-reference by id, ValueSet name, or url. For this case, it logs an error and removes the self reference.

**Testing Instructions:** Verify with unit tests.

**Related Issue:** Fixes #1487


Patch:
diff --git a/src/export/ValueSetExporter.ts b/src/export/ValueSetExporter.ts
index e27ca9e..19c1b77 100644
--- a/src/export/ValueSetExporter.ts
+++ b/src/export/ValueSetExporter.ts
@@ -86,6 +86,14 @@ export class ValueSetExporter {
           composeElement.valueSet = component.from.valueSets.map(vs => {
             return this.fisher.fishForMetadata(vs, Type.ValueSet)?.url ?? vs;
           });
+          composeElement.valueSet = composeElement.valueSet.filter(vs => {
+            if (vs == valueSet.url) {
+              logger.error(
+                `Value set with id ${valueSet.id} has component rule with self-referencing value set (by id, value set name, or url). Removing self-reference.`
+              );
+            }
+            return vs != valueSet.url;
+          });
           composeElement.valueSet.forEach(vs => {
             // Canonical URI may include | to specify version: https://www.hl7.org/fhir/references.html#canonical
             if (!isUri(vs.split('|')[0])) {
@@ -176,7 +184,9 @@ export class ValueSetExporter {
               this.addConceptComposeElement(composeElement, valueSet.compose.include);
             }
           } else {
-            valueSet.compose.include.push(composeElement);
+            if (composeElement.valueSet?.length !== 0 || composeElement.system != undefined) {
+              valueSet.compose.include.push(composeElement);
+            }
           }
         } else {
           this.addConceptComposeElement(composeElement, valueSet.compose.exclude);


Test patch:
diff --git a/test/export/ValueSetExporter.test.ts b/test/export/ValueSetExporter.test.ts
index b6f9a35..78baa7b 100644
--- a/test/export/ValueSetExporter.test.ts
+++ b/test/export/ValueSetExporter.test.ts
@@ -473,6 +473,57 @@ describe('ValueSetExporter', () => {
     });
   });
 
+  it('should remove and log error when exporting a value set that includes a component from a self referencing value set', () => {
+    const valueSet = new FshValueSet('DinnerVS');
+    valueSet.id = 'dinner-vs';
+    const component = new ValueSetConceptComponentRule(true);
+    component.from = {
+      system: 'http://food.org/food1',
+      valueSets: [
+        'http://food.org/food/ValueSet/hot-food',
+        'http://food.org/food/ValueSet/cold-food',
+        'DinnerVS',
+        'http://hl7.org/fhir/us/minimal/ValueSet/dinner-vs',
+        'dinner-vs'
+      ]
+    };
+    const component2 = new ValueSetConceptComponentRule(true);
+    component2.from = {
+      system: 'http://food.org/food2',
+      valueSets: ['DinnerVS', 'http://hl7.org/fhir/us/minimal/ValueSet/dinner-vs', 'dinner-vs']
+    };
+    valueSet.rules.push(component);
+    valueSet.rules.push(component2);
+    doc.valueSets.set(valueSet.name, valueSet);
+    const exported = exporter.export().valueSets;
+    expect(exported.length).toBe(1);
+    expect(exported[0]).toEqual({
+      resourceType: 'ValueSet',
+      id: 'dinner-vs',
+      name: 'DinnerVS',
+      url: 'http://hl7.org/fhir/us/minimal/ValueSet/dinner-vs',
+      status: 'draft',
+      compose: {
+        include: [
+          {
+            system: 'http://food.org/food1',
+            valueSet: [
+              'http://food.org/food/ValueSet/hot-food',
+              'http://food.org/food/ValueSet/cold-food'
+            ]
+          },
+          {
+            system: 'http://food.org/food2'
+          }
+        ]
+      }
+    });
+    expect(loggerSpy.getAllMessages('error')).toHaveLength(6);
+    expect(loggerSpy.getLastMessage('error')).toBe(
+      'Value set with id dinner-vs has component rule with self-referencing value set (by id, value set name, or url). Removing self-reference.'
+    );
+  });
+
   it('should export a value set that includes a concept component with at least one concept', () => {
     const valueSet = new FshValueSet('DinnerVS');
     const component = new ValueSetConceptComponentRule(true);