Repository: FHIR/sushi (ts)

Issue:
Using local CS in code definition doesn't always work
For example, this produces the error `Resolved value "MyCS" is not a valid URI.`:

```
CodeSystem: MyCS
Id: my-cs
* #mycode "My Display" "My Definition"

Profile: MyProfile
Parent: Patient
* active ^code[0] = MyCS#mycode
```

but if we change it to use an alias instead, it works:

```
CodeSystem: MyCS
Id: my-cs
* #mycode "My Display" "My Definition"

Alias: MyCSAlias = http://hl7.org/fhir/sushi-test/CodeSystem/my-cs

Profile: MyProfile
Parent: Patient
* active ^code[0] = MyCSAlias#mycode
```

Note that these _also_ work (using the CS name, _not_ the alias):

```
Profile: MyOtherProfile
Parent: Observation
* code = MyCS#mycode

Instance: MyInstance
InstanceOf: Observation
* code = MyCS#mycode
```

So this would suggest that this bug may be restricted to using local code systems in codes in caret rules.

Pull request description:
Local CodeSystem in CaretRules
This fixes #516.

CodeSystems defined in FSH were not being replaced with their canonical URLs when they were used in a CaretValueRule. It would work on a FixedValueRule, but not a CaretValueRule. So if the user did:
```
CodeSystem: MyCS
Id: my-cs
* #mycode "My Display" "My Definition"

Profile: MyProfile
Parent: Patient
* active ^code[0] = MyCS#mycode
```
The `MyCS` would not be converted to the URL for `MyCS`, resulting in an error. This was also actually an issue for References too. References to locally defined resources would not be resolved into the form `<resource.resourceType>/<resource.id>`. The code for replacing References and CodeSystems is in the same spot, so this PR fixes resolution for both of them.

To test this, you can use the example given above. On `master`, that will result in a `Resolved value "MyCS" is not a valid URI.` error. On this branch, it should build without error, and the value will be correctly replaced. Additionally, the tests on https://github.com/FHIR/sushi/commit/ffbe61805345e1ce141b2da4e88afb0cfc55fe3d fail on that commit, but then the next commit fixes them by adding the bug fix.

Patch:
diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts
index 683e07b..8602f11 100644
--- a/src/export/StructureDefinitionExporter.ts
+++ b/src/export/StructureDefinitionExporter.ts
@@ -189,17 +189,22 @@ export class StructureDefinitionExporter implements Fishable {
               });
             }
           } else if (rule instanceof CaretValueRule) {
-            if (rule.path !== '') {
-              element.setInstancePropertyByPath(rule.caretPath, rule.value, this);
+            const replacedRule = replaceReferences(rule, this.tank, this);
+            if (replacedRule.path !== '') {
+              element.setInstancePropertyByPath(replacedRule.caretPath, replacedRule.value, this);
             } else {
-              if (rule.isInstance) {
+              if (replacedRule.isInstance) {
                 if (this.deferredRules.has(structDef)) {
-                  this.deferredRules.get(structDef).push(rule);
+                  this.deferredRules.get(structDef).push(replacedRule);
                 } else {
-                  this.deferredRules.set(structDef, [rule]);
+                  this.deferredRules.set(structDef, [replacedRule]);
                 }
               } else {
-                structDef.setInstancePropertyByPath(rule.caretPath, rule.value, this);
+                structDef.setInstancePropertyByPath(
+                  replacedRule.caretPath,
+                  replacedRule.value,
+                  this
+                );
               }
             }
           } else if (rule instanceof ObeysRule) {
diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts
index 9e5164d..31814da 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -12,7 +12,9 @@ import {
   InsertRule,
   ConceptRule,
   ValueSetConceptComponentRule,
-  SdRule
+  SdRule,
+  CaretValueRule,
+  FixedValueType
 } from '../fshtypes/rules';
 import {
   FshReference,
@@ -224,14 +226,15 @@ export function getArrayIndex(pathPart: PathPart): number {
  * @param {Fishable} fisher - A fishable implementation for finding definitions and metadata
  * @returns {FixedValueRule} a clone of the rule if replacing is done, otherwise the original rule
  */
-export function replaceReferences(
-  rule: FixedValueRule,
+export function replaceReferences<T extends FixedValueRule | CaretValueRule>(
+  rule: T,
   tank: FSHTank,
   fisher: Fishable
-): FixedValueRule {
-  let clone: FixedValueRule;
-  if (rule.fixedValue instanceof FshReference) {
-    const instance = tank.fish(rule.fixedValue.reference, Type.Instance) as Instance;
+): T {
+  let clone: T;
+  const value = getRuleValue(rule);
+  if (value instanceof FshReference) {
+    const instance = tank.fish(value.reference, Type.Instance) as Instance;
     const instanceMeta = fisher.fishForMetadata(
       instance?.instanceOf,
       Type.Resource,
@@ -247,22 +250,35 @@ export function replaceReferences(
       ) as FixedValueRule;
       const id = idRule?.fixedValue ?? instance.id;
       clone = cloneDeep(rule);
-      const fv = clone.fixedValue as FshReference;
+      const fv = getRuleValue(clone) as FshReference;
       fv.reference = `${instanceMeta.sdType}/${id}`;
       fv.sdType = instanceMeta.sdType;
     }
-  } else if (rule.fixedValue instanceof FshCode) {
-    const codeSystem = tank.fish(rule.fixedValue.system, Type.CodeSystem);
+  } else if (value instanceof FshCode) {
+    const codeSystem = tank.fish(value.system, Type.CodeSystem);
     const codeSystemMeta = fisher.fishForMetadata(codeSystem?.name, Type.CodeSystem);
     if (codeSystem && codeSystemMeta) {
       clone = cloneDeep(rule);
-      const fv = clone.fixedValue as FshCode;
+      const fv = getRuleValue(clone) as FshCode;
       fv.system = codeSystemMeta.url;
     }
   }
   return clone ?? rule;
 }
 
+/**
+ * Function to get a value from a rule that has a value (FixedValue or CaretValue)
+ * @param rule - The rule to get a value from
+ * @returns - The value on the rule
+ */
+function getRuleValue(rule: FixedValueRule | CaretValueRule): FixedValueType {
+  if (rule instanceof FixedValueRule) {
+    return rule.fixedValue;
+  } else if (rule instanceof CaretValueRule) {
+    return rule.value;
+  }
+}
+
 /**
  * Returns the sliceName for a set of pathParts
  * @param {PathPart} pathPart - The part of the path to get a sliceName for


Test patch:
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index 0cbc018..6fbbb57 100644
--- a/test/export/StructureDefinitionExporter.test.ts
+++ b/test/export/StructureDefinitionExporter.test.ts
@@ -2449,6 +2449,98 @@ describe('StructureDefinitionExporter', () => {
     });
   });
 
+  it('should apply a Reference CaretValueRule on an SD and replace the Reference', () => {
+    const profile = new Profile('Foo');
+    profile.parent = 'Observation';
+
+    const instance = new Instance('Bar');
+    instance.id = 'bar-id';
+    instance.instanceOf = 'Organization';
+    doc.instances.set(instance.name, instance);
+
+    const rule = new CaretValueRule('');
+    rule.caretPath = 'identifier[0].assigner';
+    rule.value = new FshReference('Bar');
+    profile.rules.push(rule);
+
+    exporter.exportStructDef(profile);
+    const sd = pkg.profiles[0];
+
+    expect(sd.identifier[0].assigner).toEqual({
+      reference: 'Organization/bar-id'
+    });
+  });
+
+  it('should apply a Reference CaretValueRule on an ED and replace the Reference', () => {
+    const profile = new Profile('Foo');
+    profile.parent = 'Observation';
+
+    const instance = new Instance('Bar');
+    instance.id = 'bar-id';
+    instance.instanceOf = 'Organization';
+    doc.instances.set(instance.name, instance);
+
+    const rule = new CaretValueRule('subject');
+    rule.caretPath = 'patternReference';
+    rule.value = new FshReference('Bar');
+    profile.rules.push(rule);
+
+    exporter.exportStructDef(profile);
+    const sd = pkg.profiles[0];
+
+    const ed = sd.elements.find(e => e.id === 'Observation.subject');
+
+    expect(ed.patternReference).toEqual({
+      reference: 'Organization/bar-id'
+    });
+  });
+
+  it('should apply a CodeSystem CaretValueRule on an SD and replace the CodeSystem', () => {
+    const profile = new Profile('Foo');
+    profile.parent = 'Observation';
+
+    const cs = new FshCodeSystem('Bar');
+    cs.id = 'bar-id';
+    doc.codeSystems.set(cs.name, cs);
+
+    const rule = new CaretValueRule('');
+    rule.caretPath = 'jurisdiction';
+    rule.value = new FshCode('foo', 'Bar');
+    profile.rules.push(rule);
+
+    exporter.exportStructDef(profile);
+    const sd = pkg.profiles[0];
+
+    expect(sd.jurisdiction[0].coding[0]).toEqual({
+      code: 'foo',
+      system: 'http://hl7.org/fhir/us/minimal/CodeSystem/bar-id'
+    });
+  });
+
+  it('should apply a CodeSystem CaretValueRule on an ED and replace the Reference', () => {
+    const profile = new Profile('Foo');
+    profile.parent = 'Observation';
+
+    const cs = new FshCodeSystem('Bar');
+    cs.id = 'bar-id';
+    doc.codeSystems.set(cs.name, cs);
+
+    const rule = new CaretValueRule('subject');
+    rule.caretPath = 'code';
+    rule.value = new FshCode('foo', 'Bar');
+    profile.rules.push(rule);
+
+    exporter.exportStructDef(profile);
+    const sd = pkg.profiles[0];
+
+    const ed = sd.elements.find(e => e.id === 'Observation.subject');
+
+    expect(ed.code[0]).toEqual({
+      code: 'foo',
+      system: 'http://hl7.org/fhir/us/minimal/CodeSystem/bar-id'
+    });
+  });
+
   // ObeysRule
   it('should apply an ObeysRule at the specified path', () => {
     const profile = new Profile('Foo');