Repository: FHIR/sushi (ts)

Issue:
SUSHI should support `Canonical` for relevant Instances too
SUSHI should support the `Canonical(...)` lookup for instances w/ a canonical URL too.  This is not just _Conformance_ and _Terminology_ resources, but also things like `Questionnaire`.

See: https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Failed.20canonical.20lookup

Pull request description:
Support Canonical for Instances
Allow the Canonical(...) construct to target an instance.  This required updating the tank fisher to return instance url values when fishing for metadata.

If you want to test manually, you can use this modified example from the original bug reported on Zulip:
```
Instance: WhoCrQuestionnaireCovid19Surveillance
InstanceOf: Questionnaire
Description: "TODO"
Title: "Revised case report form for Confirmed Novel Coronavirus COVID-19"
Usage: #definition
* url = "http://openhie.github.io/covid-19/Questionnaire/WhoCrQuestionnaireCovid19Surveillance"
* status = #active

Profile:    WhoCrQuestionnaireResponseCovid19Surveillance
Parent:     QuestionnaireResponse
Description: "Who Case Reporting QuestionnaireResponse"
Title:      "Who Case Reporting QuestionnaireResponse"
* questionnaire = Canonical(WhoCrQuestionnaireCovid19Surveillance)
```
It will have an error on `master`:
> error Cannot use canonical URL of WhoCrQuestionnaireCovid19Surveillance because it does not exist. Be sure that WhoCrQuestionnaireCovid19Surveillance exists and it has a URL.

But it will succeed on this branch.

Fixes #743

Patch:
diff --git a/src/import/FSHTank.ts b/src/import/FSHTank.ts
index a7482b4..64bc27c 100644
--- a/src/import/FSHTank.ts
+++ b/src/import/FSHTank.ts
@@ -10,9 +10,10 @@ import {
   Mapping,
   Configuration
 } from '../fshtypes';
-import flatMap from 'lodash/flatMap';
+import { AssignmentRule } from '../fshtypes/rules';
 import { Type, Metadata, Fishable } from '../utils/Fishable';
 import { getUrlFromFshDefinition } from '../fhirtypes/common';
+import flatMap from 'lodash/flatMap';
 
 export class FSHTank implements Fishable {
   constructor(public readonly docs: FSHDocument[], public readonly config: Configuration) {}
@@ -215,6 +216,12 @@ export class FSHTank implements Fishable {
       } else if (result instanceof FshValueSet || result instanceof FshCodeSystem) {
         meta.url = getUrlFromFshDefinition(result, this.config.canonical);
       } else if (result instanceof Instance) {
+        result.rules?.forEach(r => {
+          if (r.path === 'url' && r instanceof AssignmentRule && typeof r.value === 'string') {
+            meta.url = r.value;
+            // don't break; keep looping in case there is a later rule that re-assigns url
+          }
+        });
         meta.instanceUsage = result.usage;
       }
       return meta;


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index 766fe23..0854431 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -1321,6 +1321,27 @@ describe('InstanceExporter', () => {
       });
     });
 
+    it('should apply an Assignment rule with Canonical of a Questionnaire instance', () => {
+      const questionnaireInstance = new Instance('MyQuestionnaire');
+      questionnaireInstance.usage = 'Definition';
+      const urlRule = new AssignmentRule('url');
+      urlRule.value = 'http://my.awesome.questions.org/Questionnaire/MyQuestionnaire';
+      questionnaireInstance.rules.push(urlRule);
+      doc.instances.set(questionnaireInstance.name, questionnaireInstance);
+
+      const responseInstance = new Instance('MyQuestionnaireResponse');
+      responseInstance.instanceOf = 'QuestionnaireResponse';
+      const assignedValueRule = new AssignmentRule('questionnaire');
+      assignedValueRule.value = new FshCanonical('MyQuestionnaire');
+      responseInstance.rules.push(assignedValueRule);
+      doc.instances.set(responseInstance.name, responseInstance);
+
+      const exported = exportInstance(responseInstance);
+      expect(exported.questionnaire).toEqual(
+        'http://my.awesome.questions.org/Questionnaire/MyQuestionnaire'
+      );
+    });
+
     it('should apply an Assignment rule with Canonical of an inline instance', () => {
       const observationInstance = new Instance('MyObservation');
       observationInstance.instanceOf = 'Observation';
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index 1466ca2..86e03cc 100644
--- a/test/export/StructureDefinitionExporter.test.ts
+++ b/test/export/StructureDefinitionExporter.test.ts
@@ -1691,6 +1691,30 @@ describe('StructureDefinitionExporter', () => {
     expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
   });
 
+  it('should apply an Assignment rule with Canonical of a Questionnaire instance', () => {
+    const questionnaireInstance = new Instance('MyQuestionnaire');
+    questionnaireInstance.usage = 'Definition';
+    const urlRule = new AssignmentRule('url');
+    urlRule.value = 'http://my.awesome.questions.org/Questionnaire/MyQuestionnaire';
+    questionnaireInstance.rules.push(urlRule);
+    doc.instances.set(questionnaireInstance.name, questionnaireInstance);
+
+    const profile = new Profile('MyQuestionnaireResponse');
+    profile.parent = 'QuestionnaireResponse';
+    const assignedValueRule = new AssignmentRule('questionnaire');
+    assignedValueRule.value = new FshCanonical('MyQuestionnaire');
+    profile.rules.push(assignedValueRule);
+    doc.profiles.set(profile.name, profile);
+
+    exporter.exportStructDef(profile);
+    const sd = pkg.profiles[0];
+    const assignedQ = sd.findElement('QuestionnaireResponse.questionnaire');
+    expect(assignedQ.patternCanonical).toEqual(
+      'http://my.awesome.questions.org/Questionnaire/MyQuestionnaire'
+    );
+    expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+  });
+
   it('should apply an Assignment rule with Canonical of an inline instance', () => {
     const profile = new Profile('MyObservation');
     profile.parent = 'Observation';