Repository: FHIR/sushi (ts)

Issue:
Cannot set fixed[x] values on Instances
Details of this issue are here https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/fixedString.20in.20a.20profile.20causing.20errors.20in.20instance. As an example, consider this rule on an instance of this profile http://build.fhir.org/ig/hl7au/au-fhir-base/StructureDefinition-au-immunization.html.
```
* performer[administeredBy].function.text = "Administering Provider" // a fixed string in the profile
```
The user gets the following error:
>Cannot fix this element using a pattern; as it is already fixed in the StructureDefinition using fixedString. Since fixed[x] requires exact matches, while pattern[x] allows for variation in unspecified properties, fixed[x] cannot be replaced by pattern[x] since it would loosen the constraint.

This seems to be because when we test if a value on an instance can be fixed, we test if fixing it by `pattern[x]` on the profile would succeed. My suspicion is that we need to either always test if fixing by `fixed[x]` rather than `pattern[x]` would succeed. Or we need to determine which field is set on the element on the profile, and test using the corresponding strategy.

Pull request description:
Allow setting fixed by fixed[x] values on an Instance
Fixes #398.

The issue here can be most clearly illustrated with the following example:
```
Profile: MyPatient
Parent: Patient
* name.family = "Smith" (exactly)

Instance: PatientInstance
InstanceOf: MyPatient
* name.family = "Smith"
```
SUSHI on master will allow you to do this, but an error will be emitted like:
>Cannot fix this element using a pattern; as it is already fixed in the StructureDefinition using fixedString. Since fixed[x] requires exact matches, while pattern[x] allows for variation in unspecified properties, fixed[x] cannot be replaced by pattern[x] since it would loosen the constraint.

With the error referencing the line setting `name.family` on the instance. This is incorrect, because when setting a value on an instance, fixed[x] and pattern[x] do not matter, you are just setting the value. This error happens because to test if we can fix a value on an instance, we try to fix the same value on the corresponding profile. But by mistake, we by default would always test fixing the value on the profile using pattern. This PR fixes that by now always testing if we can fix a value using fixed. On this branch, the above error should go away when testing the snippet above. There are also several other examples you can test on, in the linked issue and zulip chats. Finally, you can also run the tests on https://github.com/FHIR/sushi/commit/1cde85da1455ea332b521b727e44d76b95ea8591 and see that they fail, then run the tests on https://github.com/FHIR/sushi/commit/6bdd93eec1765dc82db3a9661cb520b63ef994a6 and see that they pass.

Patch:
diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts
index 5a543dd..c452856 100644
--- a/src/export/InstanceExporter.ts
+++ b/src/export/InstanceExporter.ts
@@ -121,7 +121,7 @@ export class InstanceExporter implements Fishable {
         // fixable descendents, and fixable descenendents of its parents. A fixable descendent is a 1..n direct child
         // or a fixable descendent of such a child
         const parents = element.getAllParents();
-        const associatedElements = [element, ...element.getFixableDescendents(), ...parents];
+        const associatedElements = [...element.getFixableDescendents(), ...parents];
         parents.map(p => p.getFixableDescendents()).forEach(pd => associatedElements.push(...pd));
 
         for (const associatedEl of associatedElements) {
diff --git a/src/fhirtypes/StructureDefinition.ts b/src/fhirtypes/StructureDefinition.ts
index 5a3f74b..2b4f84c 100644
--- a/src/fhirtypes/StructureDefinition.ts
+++ b/src/fhirtypes/StructureDefinition.ts
@@ -574,8 +574,8 @@ export class StructureDefinition {
     } else {
       // fixValue will throw if it fails, but skip the check if value is null
       if (value != null) {
-        // since we don't actually keep the clone, argument for exactly should not matter
-        clone.fixValue(value, false, units);
+        // exactly must be true so that we always test fixing with the more strict fixed[x] approach
+        clone.fixValue(value, true, units);
       }
       // If there is a fixedValue or patternValue, find it and return it
       const key = Object.keys(clone).find(k => k.startsWith('pattern') || k.startsWith('fixed'));


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index 1fefbbd..52d18a1 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -233,7 +233,7 @@ describe('InstanceExporter', () => {
     });
 
     // Fixing top level elements
-    it('should fix top level elements that are fixed on the Structure Definition', () => {
+    it('should fix top level elements that are fixed by pattern[x] on the Structure Definition', () => {
       const cardRule = new CardRule('active');
       cardRule.min = 1;
       patient.rules.push(cardRule);
@@ -244,6 +244,18 @@ describe('InstanceExporter', () => {
       expect(exported.active).toEqual(true);
     });
 
+    it('should fix top level elements that are fixed by fixed[x] on the Structure Definition', () => {
+      const cardRule = new CardRule('active');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
+      const fixedValRule = new FixedValueRule('active');
+      fixedValRule.fixedValue = true;
+      fixedValRule.exactly = true;
+      patient.rules.push(fixedValRule);
+      const exported = exportInstance(patientInstance);
+      expect(exported.active).toEqual(true);
+    });
+
     it('should fix top level codes that are fixed on the Structure Definition', () => {
       const cardRule = new CardRule('gender');
       cardRule.min = 1;
@@ -369,17 +381,21 @@ describe('InstanceExporter', () => {
     });
 
     it('should fix an element to a value the same as the fixed value on the Structure Definition', () => {
+      loggerSpy.reset();
       const fixedValRule = new FixedValueRule('active');
       fixedValRule.fixedValue = true;
+      fixedValRule.exactly = true;
       patient.rules.push(fixedValRule);
       const instanceFixedValRule = new FixedValueRule('active');
       instanceFixedValRule.fixedValue = true;
       patientInstance.rules.push(instanceFixedValRule);
       const exported = exportInstance(patientInstance);
       expect(exported.active).toEqual(true);
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
     });
 
     it('should fix an element to a value the same as the fixed pattern on the Structure Definition', () => {
+      loggerSpy.reset();
       const fixedValRule = new FixedValueRule('maritalStatus');
       const fixedFshCode = new FshCode('foo', 'http://foo.com');
       fixedValRule.fixedValue = fixedFshCode;
@@ -392,6 +408,7 @@ describe('InstanceExporter', () => {
       expect(exported.maritalStatus).toEqual({
         coding: [{ code: 'foo', system: 'http://foo.com' }]
       });
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
     });
 
     it('should fix an element to a value that is a superset of the fixed pattern on the Structure Definition', () => {
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index 51e5743..235e8f0 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -936,6 +936,32 @@ describe('StructureDefinition', () => {
       expect(pathParts[0]).toEqual({ primitive: true, base: 'version' });
     });
 
+    it('should allow fixing the same instance value over an existing pattern[x]', () => {
+      const method = respRate.findElement('Observation.method');
+      method.patternCodeableConcept = { coding: [{ system: 'http://system.com', code: 'foo' }] };
+      const { fixedValue, pathParts } = respRate.validateValueAtPath(
+        'method',
+        new FshCode('foo', 'http://system.com'),
+        fisher
+      );
+      expect(fixedValue).toEqual({ coding: [{ system: 'http://system.com', code: 'foo' }] });
+      expect(pathParts.length).toBe(1);
+      expect(pathParts[0]).toEqual({ base: 'method' });
+    });
+
+    it('should allow fixing the same instance value over an existing fixed[x]', () => {
+      const method = respRate.findElement('Observation.method');
+      method.fixedCodeableConcept = { coding: [{ system: 'http://system.com', code: 'foo' }] };
+      const { fixedValue, pathParts } = respRate.validateValueAtPath(
+        'method',
+        new FshCode('foo', 'http://system.com'),
+        fisher
+      );
+      expect(fixedValue).toEqual({ coding: [{ system: 'http://system.com', code: 'foo' }] });
+      expect(pathParts.length).toBe(1);
+      expect(pathParts[0]).toEqual({ base: 'method' });
+    });
+
     // Invalid paths
     it('should not allow fixing an instance value with an incorrect path', () => {
       expect(() => {