Repository: FHIR/sushi
Pull request #452 (2020-05-20 13:56:39)

Fix contains rule cardinality
Fixes https://github.com/FHIR/sushi/issues/450.

On the following example:
```
Profile: MyMessageHeader
Parent: MessageHeader
* focus ^slicing.discriminator.type = #pattern
* focus ^slicing.discriminator.path = "reference"
* focus ^slicing.rules = #open
* focus ^slicing.ordered = false
* focus ^slicing.description = "Focus Resource types"
* focus contains BDRPatient 1..1
* focus contains BDRReportableDiagnosis 1..*
```
A user noticed that the cardinality of the slice defined on the last line was not being included in the differential for the generated structure definition. The guess for why this happens that was laid out in the issue comments is correct. The first slice is added with cardinality `1..1`, which forces the cardinality of the base element from `0..*` to `1..*`. The second element is then added with cardinality `1..*`, but at this point, the original element has cardinality `1..*`. So when `captureOriginal` is called, the original cardinality is recorded as `1..*`. Therefore no cardinality shows up in the differential for the slice.

My solution to this was to always remove the cardinality from the original element before running `captureOriginal`. This way, the cardinality of a slice is *always* included in a differential. I am not sure there is strict guidance from FHIR on if this is correct. To me it seems correct. When we create a slice, the cardinality of that slice is describing some new info that is not in the original element. It seems like even if the cardinality of that slice happens to match the cardinality of the original, that is just a coincidence, and it still belongs in the diff. Be aware this will cause a diff in mcode though. 


Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index 65e96f5..995e7e3 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -1742,6 +1742,12 @@ export class ElementDefinition {
       throw new DuplicateSliceError(this.structDef.name, this.id, name);
     }
 
+    // On a new slice, delete slice.min and slice.max and then reset them
+    // so that they are always captured in diff
+    const originalMax = slice.max;
+    delete slice.min;
+    delete slice.max;
+
     // Capture the original so that the differential only contains changes from this point on.
     slice.captureOriginal();
 
@@ -1751,6 +1757,7 @@ export class ElementDefinition {
     // Cardinality can be later narrowed by card constraints, which check validity of narrowing
     // According to https://chat.fhir.org/#narrow/stream/179239-tooling/topic/Slicing.201.2E.2E.3F.20element
     slice.min = 0;
+    slice.max = originalMax;
     if (type) {
       slice.type = [type];
     }


Test patch:
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index c550ce9..d125509 100644
--- a/test/export/StructureDefinitionExporter.test.ts
+++ b/test/export/StructureDefinitionExporter.test.ts
@@ -1608,6 +1608,9 @@ describe('StructureDefinitionExporter', () => {
 
     expect(sd.elements.length).toBe(baseStructDef.elements.length + 1);
     expect(barSlice).toBeDefined();
+    const diff = barSlice.calculateDiff();
+    expect(diff.min).toBe(0);
+    expect(diff.max).toBe('*');
   });
 
   it('should apply a ContainsRule of a defined extension on an extension element', () => {
@@ -3658,6 +3661,7 @@ describe('StructureDefinitionExporter', () => {
         id: 'Observation.component:SystolicBP',
         path: 'Observation.component',
         sliceName: 'SystolicBP',
+        min: 0,
         max: '1'
       },
       {
@@ -3676,6 +3680,7 @@ describe('StructureDefinitionExporter', () => {
         id: 'Observation.component:DiastolicBP',
         path: 'Observation.component',
         sliceName: 'DiastolicBP',
+        min: 0,
         max: '1'
       },
       {
diff --git a/test/fhirtypes/ElementDefinition.slicing.test.ts b/test/fhirtypes/ElementDefinition.slicing.test.ts
index 1b7c783..d83e4ce 100644
--- a/test/fhirtypes/ElementDefinition.slicing.test.ts
+++ b/test/fhirtypes/ElementDefinition.slicing.test.ts
@@ -262,6 +262,9 @@ describe('ElementDefinition', () => {
       expect(systolicBP.max).toBe('*');
       expect(systolicBP.type).toHaveLength(1);
       expect(systolicBP.type[0]).toEqual(new ElementDefinitionType('BackboneElement'));
+      const systolicDiff = systolicBP.calculateDiff();
+      expect(systolicDiff.min).toBe(0);
+      expect(systolicDiff.max).toBe('*');
       expect(diastolicBP.id).toBe('Observation.component:DiastolicBP');
       expect(diastolicBP.path).toBe('Observation.component');
       expect(diastolicBP.slicing).toBeUndefined();
@@ -272,6 +275,9 @@ describe('ElementDefinition', () => {
       expect(diastolicBP.type[0]).toEqual(new ElementDefinitionType('BackboneElement'));
       expect(observation.elements[lastComponentIndex + 1]).toEqual(systolicBP);
       expect(observation.elements[lastComponentIndex + 2]).toEqual(diastolicBP);
+      const diastolicDiff = diastolicBP.calculateDiff();
+      expect(diastolicDiff.min).toBe(0);
+      expect(diastolicDiff.max).toBe('*');
     });
 
     it('should add slices for specific types in a choice', () => {
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index 4040fd3..35cb9a4 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -243,7 +243,9 @@ describe('StructureDefinition', () => {
         id: 'Observation.valueQuantity',
         path: 'Observation.valueQuantity',
         short: 'the quantity choice',
-        type: [{ code: 'Quantity' }]
+        type: [{ code: 'Quantity' }],
+        min: 0,
+        max: '1'
       });
     });