Repository: FHIR/sushi
Pull request #1468 (2024-06-05 19:39:22)

Warn when reference values do not resolve and have incorrect format
This PR includes:
- Issues a warning when the user provides reference values to entities that do not resolve and have incorrect FHIR Reference format
- Tests affected were modified

jafeltra: I also noticed we still have #968 open. I think this PR addresses that issue, and we should be able to close it when this gets merged.

Patch:
diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts
index fa32648..84c6c90 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -979,6 +979,14 @@ export function replaceReferences<T extends AssignmentRule | CaretValueRule>(
         clone = cloneDeep(rule);
         (clone.value as FshReference).reference = value.reference.slice(0, firstPipe);
         clone = replaceReferences(clone, tank, fisher);
+      } else if (type == null && id == null && value.reference) {
+        // if the reference to an entity is provided but is unable to be resolved
+        if (!(value.reference.startsWith('urn:') || value.reference.includes('/'))) {
+          logger.warn(
+            `Cannot find the entity referenced at ${value.reference}. The provided reference value will be used, but this reference does not conform to the FHIR Reference format.`,
+            rule.sourceInfo
+          );
+        }
       }
     }
   } else if (value instanceof FshCode) {


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index 3002d29..67f2b0a 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -5486,6 +5486,7 @@ describe('InstanceExporter', () => {
       expect(exported.managingOrganization).toEqual({
         reference: 'Organization/org-id'
       });
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
     });
 
     it('should assign a reference while resolving the Instance of a profile being referred to', () => {
@@ -5533,6 +5534,82 @@ describe('InstanceExporter', () => {
       ]);
     });
 
+    it('should log warning when reference values do not resolve and is not an absolute or relative URL', () => {
+      // * target = Reference(exampleReferenceUnableToBeResolved)
+      const assignedRefRule = new AssignmentRule('target');
+      assignedRefRule.value = new FshReference('exampleReferenceUnableToBeResolved');
+      provenanceInstance.rules.push(assignedRefRule);
+      const exported = exportInstance(provenanceInstance);
+      expect(exported.target).toEqual([
+        {
+          reference: 'exampleReferenceUnableToBeResolved'
+        }
+      ]);
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(1);
+      expect(loggerSpy.getLastMessage('warn')).toMatch(
+        'Cannot find the entity referenced at exampleReferenceUnableToBeResolved. The provided reference value will be used, but this reference does not conform to the FHIR Reference format.'
+      );
+    });
+    it('should not log warning when reference values do not resolve and is a UUID or OID', () => {
+      // * target = Reference(urn:uuid:exampleReference)
+      const assignedRefRule = new AssignmentRule('target');
+      assignedRefRule.value = new FshReference('urn:uuid:exampleReference');
+      provenanceInstance.rules.push(assignedRefRule);
+      const exported = exportInstance(provenanceInstance);
+      expect(exported.target).toEqual([
+        {
+          reference: 'urn:uuid:exampleReference'
+        }
+      ]);
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
+    });
+
+    it('should not log warning when reference values do not resolve and is a relative URL with correct number of parts', () => {
+      // * target = Reference(UnresolvableType/exampleReferenceUnableToBeResolved)
+      const assignedRefRule = new AssignmentRule('target');
+      assignedRefRule.value = new FshReference(
+        'UnresolvableType/exampleReferenceUnableToBeResolved'
+      );
+      provenanceInstance.rules.push(assignedRefRule);
+      const exported = exportInstance(provenanceInstance);
+      expect(exported.target).toEqual([
+        {
+          reference: 'UnresolvableType/exampleReferenceUnableToBeResolved'
+        }
+      ]);
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
+    });
+
+    it('should not log warning when reference values do not resolve and is a relative URL but has more than two parts', () => {
+      // * target = Reference(More/Than/Two/Parts/exampleReferenceUnableToBeResolved)
+      const assignedRefRule = new AssignmentRule('target');
+      assignedRefRule.value = new FshReference(
+        'More/Than/Two/Parts/exampleReferenceUnableToBeResolved'
+      );
+      provenanceInstance.rules.push(assignedRefRule);
+      const exported = exportInstance(provenanceInstance);
+      expect(exported.target).toEqual([
+        {
+          reference: 'More/Than/Two/Parts/exampleReferenceUnableToBeResolved'
+        }
+      ]);
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
+    });
+
+    it('should not log warning when reference values are an absolute URL', () => {
+      // * target = Reference(http://foo.org/fhir/Patient/abc)
+      const assignedRefRule = new AssignmentRule('target');
+      assignedRefRule.value = new FshReference('http://foo.org/fhir/Patient/abc');
+      provenanceInstance.rules.push(assignedRefRule);
+      const exported = exportInstance(provenanceInstance);
+      expect(exported.target).toEqual([
+        {
+          reference: 'http://foo.org/fhir/Patient/abc'
+        }
+      ]);
+      expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
+    });
+
     it('should assign a reference leaving the full profile URL when it is specified', () => {
       const assignedRefRule = new AssignmentRule('target');
       assignedRefRule.value = new FshReference(
@@ -10190,7 +10267,7 @@ describe('InstanceExporter', () => {
             }
           ]
         });
-        expect(loggerSpy.getAllLogs('warn')).toBeEmpty();
+        expect(loggerSpy.getAllMessages('warn')).toHaveLength(0);
         expect(loggerSpy.getAllLogs('error')).toBeEmpty();
       });