Repository: FHIR/sushi
Pull request #368 (2020-04-17 00:45:36)

Override inline instances
This addresses https://standardhealthrecord.atlassian.net/browse/CIMPL-342.

This PR enables the user to do something like this on an instance:
```
Instance: MyBundle
InstanceOf: Bundle
* entry[0].resource = MyPatient
* entry[0].resource.name.given = "Bob"
```
An instance `MyPatient` is placed on another instance, `MyBundle` in this case, and then values within `MyPatient` are modified within the context of `MyBundle`. The original values of the `MyPatient` instance should remain unchanged. 

This also made it clear that users need to be able to set `resourceType` on an element of type resource. For example, the following alternative to what is shown above could be done to get the same results:
```
Instance: MyBundle
InstanceOf: Bundle
* entry[0].resource.resourceType = "Patient"
* entry[0].resource.id = "SomePatientId"
* entry[0].resource.name.given = "Bob"
```
The `resourceType` property is a special field that has to be added because of the serialization to JSON, https://www.hl7.org/fhir/json.html#xml, so this needed special handling. 

The user should now be able to inline assign valid elements a `resourceType` or a predefined inline instance, and then modify the fields on that instance in the same way as if that instance was being defined directly and not inline.

cmoesel: I assume the `,` is a typo below and should be `.` -- right?
```
* entry[0].resource,resourceType = "Patient"
```
ngfreiter: > I assume the `,` is a typo below and should be `.` -- right?
> 
> ```
> * entry[0].resource,resourceType = "Patient"
> ```

Typo indeed, I fixed it in the description now.
cmoesel: This approach doesn't seem to mix well w/ the optional `[0]` indexer.  It's probably rare, but something like this does not work:
```
Instance: MyBundle
InstanceOf: Bundle
* entry.resource = MyPatient
* entry[0].resource.name.given = "Bob"
```
nor the reverse:
```
Instance: MyBundle
InstanceOf: Bundle
* entry[0].resource = MyPatient
* entry.resource.name.given = "Bob"
```

Is that something that could be easily fixed in this PR or is it better to address it separately?
ngfreiter: Ah yep that would make sense. The way I would think to fix it is just to remove all `[0]` from any path on an instance, so that all paths are the using the optional syntax and we should therefore not hit this problem. That doesn't strike me as too hard to do within this PR, unless you think that's a bad approach for some reason.
cmoesel: Yeah, I think that sounds like a good approach.  At first I was going to say to normalize the syntax to always use `[0]`, but that requires you to look at every part of the path to see if it is an array.  Normalizing _out_ the `[0]` makes a lot more sense since you don't even have to look at the types to do that.  Good idea.
ngfreiter: Okey doke, I will add/test that on this PR then.
cmoesel: Other than what I already noted and @ngfreiter is working on, it all looks good to me.  I took it through a few paces and it worked as expected.  I also verified that mCODE comes out the same with or without this branch (as expected).  So... looks good once the `[0]` bit is fixed.

Patch:
diff --git a/src/errors/InvalidResourceTypeError.ts b/src/errors/InvalidResourceTypeError.ts
new file mode 100644
index 0000000..b8f035d
--- /dev/null
+++ b/src/errors/InvalidResourceTypeError.ts
@@ -0,0 +1,8 @@
+import { Annotated } from './Annotated';
+
+export class InvalidResourceTypeError extends Error implements Annotated {
+  specReferences = ['https://www.hl7.org/fhir/resourcelist.html'];
+  constructor(public resourceType: string, public elementType: string) {
+    super(`A resourceType of ${resourceType} cannot be set on an element of type ${elementType}.`);
+  }
+}
diff --git a/src/errors/index.ts b/src/errors/index.ts
index 87fe170..8561321 100644
--- a/src/errors/index.ts
+++ b/src/errors/index.ts
@@ -39,3 +39,4 @@ export * from './MultipleStandardsStatusError';
 export * from './InvalidMappingError';
 export * from './InvalidFHIRIdError';
 export * from './ParentDeclaredAsProfileNameError';
+export * from './InvalidResourceTypeError';
diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts
index 52bf9cc..56a57e9 100644
--- a/src/export/InstanceExporter.ts
+++ b/src/export/InstanceExporter.ts
@@ -27,6 +27,10 @@ export class InstanceExporter implements Fishable {
     instanceOfStructureDefinition: StructureDefinition
   ): InstanceDefinition {
     let rules = fshInstanceDef.rules.map(r => cloneDeep(r));
+    // Normalize all rules to not use the optional [0] index
+    rules.forEach(r => {
+      r.path = r.path.replace(/\[0+\]/g, '');
+    });
     rules = rules.map(r => replaceReferences(r, this.tank, this.fisher));
     // Convert strings in fixedValueRules to instances
     rules = rules.filter(r => {
@@ -45,6 +49,28 @@ export class InstanceExporter implements Fishable {
       }
       return true;
     });
+    // Collect all paths that indicate the sub-paths of that path should be of a given resourceType
+    // for example, if a.b = SomePatientInstance, then any subpath (a.b.x) must ensure that when validating
+    // Patient is used for the type of a.b
+    const inlineResourcePaths: { path: string; instanceOf: string }[] = [];
+    rules.forEach(r => {
+      if (r.isResource && r.fixedValue instanceof InstanceDefinition) {
+        inlineResourcePaths.push({
+          path: r.path,
+          // We only use the first element of the meta.profile array, if a need arises for a more
+          // comprehensive approach, we can come back to this later
+          instanceOf: r.fixedValue.meta?.profile[0] ?? r.fixedValue.resourceType
+        });
+      }
+      if (r.path.endsWith('.resourceType') && typeof r.fixedValue === 'string') {
+        inlineResourcePaths.push({
+          // Only get the part of the path before resourceType, aka if path is a.b.resourceType
+          // the relevant element is a.b, since it is the actual Resource element
+          path: splitOnPathPeriods(r.path).slice(0, -1).join('.'),
+          instanceOf: r.fixedValue
+        });
+      }
+    });
 
     // When fixing values, things happen in the order:
     // 1 - Validate values for rules that are on the instance
@@ -57,14 +83,31 @@ export class InstanceExporter implements Fishable {
     const ruleMap: Map<string, { pathParts: PathPart[]; fixedValue: any }> = new Map();
     rules.forEach(rule => {
       try {
-        const { fixedValue, pathParts } = instanceOfStructureDefinition.validateValueAtPath(
+        const matchingInlineResourcePaths = inlineResourcePaths.filter(
+          i =>
+            rule.path.startsWith(i.path) &&
+            rule.path !== i.path &&
+            rule.path != `${i.path}.resourceType`
+        );
+        // Generate an array of resourceTypes that matches the path, so if path is
+        // a.b.c.d.e, and b is a Bundle and D is a Patient,
+        // inlineResourceTypes = [undefined, "Bundle", undefined, "Patient", undefined]
+        const inlineResourceTypes: string[] = [];
+        matchingInlineResourcePaths.forEach(match => {
+          inlineResourceTypes[splitOnPathPeriods(match.path).length - 1] = match.instanceOf;
+        });
+        const validatedRule = instanceOfStructureDefinition.validateValueAtPath(
           rule.path,
           rule.fixedValue,
           this.fisher,
-          rule.units
+          rule.units,
+          inlineResourceTypes
         );
         // Record each valid rule in a map
-        ruleMap.set(rule.path, { pathParts, fixedValue });
+        ruleMap.set(rule.path, {
+          pathParts: validatedRule.pathParts,
+          fixedValue: validatedRule.fixedValue
+        });
       } catch (e) {
         logger.error(e.message, rule.sourceInfo);
       }
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index 7675fcf..bf2af44 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -29,7 +29,7 @@ import {
   InvalidMappingError,
   InvalidFHIRIdError
 } from '../errors';
-import { setPropertyOnDefinitionInstance, splitOnPathPeriods } from './common';
+import { setPropertyOnDefinitionInstance, splitOnPathPeriods, isInheritedResource } from './common';
 import { Fishable, Type, Metadata, logger } from '../utils';
 import { InstanceDefinition } from './InstanceDefinition';
 import { idRegex } from './primitiveTypes';
@@ -1364,23 +1364,17 @@ export class ElementDefinition {
   /**
    * Checks if a resource can be fixed to this element
    * @param {InstanceDefinition} value - The resource to fix
+   * @param {Fishable} fisher - A fishable implementation for finding definitions and metadata
    * @throws {NoSingleTypeError} when the ElementDefinition does not have a single type
    * @throws {MismatchedTypeError} when the ElementDefinition is not of type Resource
    * @returns {InstanceDefinition} the input value when it can be fixed
    */
-  checkFixResource(value: InstanceDefinition): InstanceDefinition {
+  checkFixResource(value: InstanceDefinition, fisher: Fishable): InstanceDefinition {
     if (!this.hasSingleType()) {
       throw new NoSingleTypeError('Resource');
     }
     const type = this.type[0].code;
-    if (
-      type === 'Resource' ||
-      (type === 'DomainResource' &&
-        // These are the only 3 resources not inherited from DomainResource
-        // https://www.hl7.org/fhir/domainresource.html#bnr
-        !['Bundle', 'Parameters', 'Binary'].includes(value.resourceType)) ||
-      type === value.resourceType
-    ) {
+    if (isInheritedResource(value.resourceType, type, fisher)) {
       return value;
     } else {
       throw new MismatchedTypeError(value.resourceType, value.id, type);
diff --git a/src/fhirtypes/StructureDefinition.ts b/src/fhirtypes/StructureDefinition.ts
index fe34241..5a3f74b 100644
--- a/src/fhirtypes/StructureDefinition.ts
+++ b/src/fhirtypes/StructureDefinition.ts
@@ -8,13 +8,19 @@ import {
 import { Meta } from './specialTypes';
 import { Identifier, CodeableConcept, Coding, Narrative, Resource, Extension } from './dataTypes';
 import { ContactDetail, UsageContext } from './metaDataTypes';
-import { CannotResolvePathError, InvalidElementAccessError, MissingSnapshotError } from '../errors';
+import {
+  CannotResolvePathError,
+  InvalidElementAccessError,
+  MissingSnapshotError,
+  InvalidResourceTypeError
+} from '../errors';
 import {
   getArrayIndex,
   setPropertyOnDefinitionInstance,
   HasName,
   HasId,
-  splitOnPathPeriods
+  splitOnPathPeriods,
+  isInheritedResource
 } from './common';
 import { Fishable, Type } from '../utils/Fishable';
 import { applyMixins } from '../utils';
@@ -418,20 +424,24 @@ export class StructureDefinition {
    * @param {any} value - The value to fix; use null to validate just the path when you know the value is valid
    * @param {Fishable} fisher - A fishable implementation for finding definitions and metadata
    * @param {boolean} units - If the value uses the units keyword
+   * @param {inlineResourceTypes} - Types that will be used to replace Resource elements
    * @throws {CannotResolvePathError} when the path cannot be resolved to an element
+   * @throws {InvalidResourceTypeError} when setting resourceType to an invalid value
    * @returns {any} - The object or value to fix
    */
   validateValueAtPath(
     path: string,
     value: any,
     fisher: Fishable,
-    units = false
+    units = false,
+    inlineResourceTypes: string[] = []
   ): { fixedValue: any; pathParts: PathPart[] } {
     const pathParts = this.parseFSHPath(path);
     let currentPath = '';
     let previousPath = '';
     let currentElement: ElementDefinition;
-    for (const pathPart of pathParts) {
+    let previousElement: ElementDefinition;
+    for (const [i, pathPart] of pathParts.entries()) {
       // Construct the path up to this point
       previousPath = currentPath;
       currentPath += `${currentPath ? '.' : ''}${pathPart.base}`;
@@ -468,6 +478,18 @@ export class StructureDefinition {
         }
       }
 
+      if (
+        !currentElement &&
+        pathPart.base === 'resourceType' &&
+        previousElement?.type?.length === 1
+      ) {
+        if (isInheritedResource(value, previousElement.type[0].code, fisher)) {
+          return { fixedValue: value, pathParts: pathParts };
+        } else {
+          throw new InvalidResourceTypeError(value, previousElement.type[0].code);
+        }
+      }
+
       // If the element has a base.max that is greater than 1, but the element has been constrained, still set properties in an array
       const nonArrayElementIsBasedOnArray =
         currentElement?.base?.max !== '0' &&
@@ -502,12 +524,53 @@ export class StructureDefinition {
       ) {
         pathPart.primitive = true;
       }
+
+      // If we have inlineResourceTypes at this pathPart, we need to validate recursively using that
+      // inline resource type, not the original type of the currentElement
+      if (
+        inlineResourceTypes.length > 0 &&
+        inlineResourceTypes[i] &&
+        currentElement.type?.length === 1 &&
+        isInheritedResource(inlineResourceTypes[i], currentElement.type[0].code, fisher, true)
+      ) {
+        const inlineResourceJSON = fisher.fishForFHIR(
+          inlineResourceTypes[i],
+          Type.Resource,
+          Type.Profile
+        );
+        if (inlineResourceJSON) {
+          const inlineResourceStructDef = StructureDefinition.fromJSON(inlineResourceJSON);
+          const inlinePath = this.assembleFSHPath(pathParts.slice(i + 1));
+          try {
+            const validatedInlineResource = inlineResourceStructDef.validateValueAtPath(
+              inlinePath,
+              value,
+              fisher,
+              units,
+              inlineResourceTypes.slice(i + 1)
+            );
+            return {
+              fixedValue: validatedInlineResource.fixedValue,
+              pathParts: pathParts.slice(0, i + 1).concat(validatedInlineResource.pathParts)
+            };
+          } catch (e) {
+            // Catch the error from the recursive call so the original path can be logged
+            if (e instanceof CannotResolvePathError) {
+              throw new CannotResolvePathError(path);
+            } else {
+              throw e;
+            }
+          }
+        }
+      }
+
+      previousElement = currentElement;
     }
     const clone = currentElement.clone();
     let fixedValue;
     // Fixed resources cannot be fixed by pattern[x]/fixed[x], so we must set fixedValue directly
     if (value instanceof InstanceDefinition) {
-      fixedValue = clone.checkFixResource(value);
+      fixedValue = clone.checkFixResource(value, fisher);
     } else {
       // fixValue will throw if it fails, but skip the check if value is null
       if (value != null) {
@@ -551,6 +614,23 @@ export class StructureDefinition {
     return pathParts;
   }
 
+  /**
+   * Assembles a PathPart array back to its original form
+   * @param {PathPart[]} pathParts - An array of pathParts
+   * @returns {string} path - The path corresponding to those pathParts
+   */
+  private assembleFSHPath(pathParts: PathPart[]): string {
+    let path = '';
+    pathParts.forEach((pathPart, i) => {
+      path += pathPart.base;
+      pathPart.brackets?.forEach(bracket => (path += `[${bracket}]`));
+      if (i < pathParts.length - 1) {
+        path += '.';
+      }
+    });
+    return path;
+  }
+
   /**
    * Looks for a matching choice element. If the choice has no existing slice, we slice it and
    * add to the existing StructureDefinition. If the choice has an existing slice, we return that.
diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts
index cce52cb..d74b1e8 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -304,6 +304,39 @@ export function getAllImpliedPaths(element: ElementDefinition, path: string): st
   return finalPaths;
 }
 
+/**
+ * Tests if resourceType is a valid FHIR resource that is a subtype of type. This is the case
+ * if type is Resource, or if type is DomainResource and resourceType is one of the resources
+ * that inherits from DomainResource, or if type is equal to resourceType.
+ * @param {string} resourceType - The resourceType to test inheritance of
+ * @param {string} type - The original type being inherited from
+ * @param {Fishable} fisher - A fisher for finding FHIR definitions
+ * @param {boolean} allowProfile - True if profiles of inherited resource should be allowed
+ * @returns {boolean} true if resourceType is a valid sub-type of type, false otherwise
+ */
+export function isInheritedResource(
+  resourceType: string,
+  type: string,
+  fisher: Fishable,
+  allowProfile = false
+): boolean {
+  const types = allowProfile ? [Type.Resource, Type.Profile] : [Type.Resource];
+  const resource = fisher.fishForFHIR(resourceType, ...types);
+  if (resource) {
+    if (allowProfile) {
+      resourceType = resource.resourceType;
+    }
+    return (
+      type === 'Resource' ||
+      (type === 'DomainResource' &&
+        // These are the only 3 resources not inherited from DomainResource
+        // https://www.hl7.org/fhir/domainresource.html#bnr
+        !['Bundle', 'Parameters', 'Binary'].includes(resourceType)) ||
+      type === resourceType
+    );
+  }
+}
+
 const nameRegex = /^[A-Z]([A-Za-z0-9_]){0,254}$/;
 
 export class HasName {


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index 16a77e2..38b03bc 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -1388,61 +1388,6 @@ describe('InstanceExporter', () => {
       );
     });
 
-    it('should fix an inline resource to an instance', () => {
-      const inlineInstance = new Instance('MyInlinePatient');
-      inlineInstance.instanceOf = 'Patient';
-      const fixedValRule = new FixedValueRule('active');
-      fixedValRule.fixedValue = true;
-      inlineInstance.rules.push(fixedValRule); // * active = true
-      doc.instances.set(inlineInstance.name, inlineInstance);
-
-      const inlineRule = new FixedValueRule('contained[0]');
-      inlineRule.fixedValue = 'MyInlinePatient';
-      inlineRule.isResource = true;
-      patientInstance.rules.push(inlineRule); // * contained[0] = MyInlinePatient
-
-      const exported = exportInstance(patientInstance);
-      expect(exported.contained).toEqual([
-        { resourceType: 'Patient', id: 'MyInlinePatient', active: true }
-      ]);
-    });
-
-    it('should fix an inline resource to an instance element with a specific type', () => {
-      const inlineInstance = new Instance('MyInlinePatient');
-      inlineInstance.instanceOf = 'Patient';
-      const fixedValRule = new FixedValueRule('active');
-      fixedValRule.fixedValue = true;
-      inlineInstance.rules.push(fixedValRule);
-      // * active = true
-      doc.instances.set(inlineInstance.name, inlineInstance);
-
-      const caretRule = new CaretValueRule('entry');
-      caretRule.caretPath = 'slicing.discriminator.type';
-      caretRule.value = new FshCode('value');
-      const containsRule = new ContainsRule('entry');
-      containsRule.items = [{ name: 'PatientsOnly' }];
-      const cardRule = new CardRule('entry[PatientsOnly]');
-      cardRule.min = 0;
-      cardRule.max = '1';
-      const typeRule = new OnlyRule('entry[PatientsOnly].resource');
-      typeRule.types = [{ type: 'Patient' }];
-      // * entry ^slicing.discriminator.type = #value
-      // * entry contains Patient 0..1
-      // * entry[PatientsOnly].resource only Patient
-      bundle.rules.push(caretRule, containsRule, cardRule, typeRule);
-
-      const bundleValRule = new FixedValueRule('entry[PatientsOnly].resource');
-      bundleValRule.fixedValue = 'MyInlinePatient';
-      bundleValRule.isResource = true;
-      // * entry[PatientsOnly].resource = MyInlinePatient
-      bundleInstance.rules.push(bundleValRule);
-
-      const exported = exportInstance(bundleInstance);
-      expect(exported.entry[0]).toEqual({
-        resource: { resourceType: 'Patient', id: 'MyInlinePatient', active: true }
-      });
-    });
-
     it('should only export an instance once', () => {
       const bundleInstance = new Instance('MyBundle');
       bundleInstance.instanceOf = 'Bundle';
@@ -1468,19 +1413,187 @@ describe('InstanceExporter', () => {
       expect(exportedBundledPatient).toHaveLength(1);
     });
 
-    it('should log an error when fixing an inline resource that does not exist to an instance', () => {
-      const inlineRule = new FixedValueRule('contained[0]')
-        .withFile('FakeInstance.fsh')
-        .withLocation([1, 2, 3, 4]);
-      inlineRule.fixedValue = 'MyFakePatient';
-      inlineRule.isResource = true;
-      patientInstance.rules.push(inlineRule); // * contained[0] = MyFakePatient
+    describe('#Inline Instances', () => {
+      beforeEach(() => {
+        const inlineInstance = new Instance('MyInlinePatient');
+        inlineInstance.instanceOf = 'Patient';
+        const fixedValRule = new FixedValueRule('active');
+        fixedValRule.fixedValue = true;
+        inlineInstance.rules.push(fixedValRule);
+        // * active = true
+        doc.instances.set(inlineInstance.name, inlineInstance);
+
+        const caretRule = new CaretValueRule('entry');
+        caretRule.caretPath = 'slicing.discriminator.type';
+        caretRule.value = new FshCode('value');
+        const containsRule = new ContainsRule('entry');
+        containsRule.items = [{ name: 'PatientsOnly' }];
+        const cardRule = new CardRule('entry[PatientsOnly]');
+        cardRule.min = 0;
+        cardRule.max = '1';
+        const typeRule = new OnlyRule('entry[PatientsOnly].resource');
+        typeRule.types = [{ type: 'Patient' }];
+        // * entry ^slicing.discriminator.type = #value
+        // * entry contains Patient 0..1
+        // * entry[PatientsOnly].resource only Patient
+        bundle.rules.push(caretRule, containsRule, cardRule, typeRule);
+      });
 
-      const exported = exportInstance(patientInstance);
-      expect(exported.contained).toBeUndefined();
-      expect(loggerSpy.getLastMessage('error')).toMatch(
-        /Cannot find definition for Instance: MyFakePatient. Skipping rule.*File: FakeInstance.fsh.*Line: 1 - 3\D*/s
-      );
+      it('should fix an inline resource to an instance', () => {
+        const inlineRule = new FixedValueRule('contained[0]');
+        inlineRule.fixedValue = 'MyInlinePatient';
+        inlineRule.isResource = true;
+        patientInstance.rules.push(inlineRule); // * contained[0] = MyInlinePatient
+
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toEqual([
+          { resourceType: 'Patient', id: 'MyInlinePatient', active: true }
+        ]);
+      });
+
+      it('should fix an inline resource to an instance element with a specific type', () => {
+        const bundleValRule = new FixedValueRule('entry[PatientsOnly].resource');
+        bundleValRule.fixedValue = 'MyInlinePatient';
+        bundleValRule.isResource = true;
+        // * entry[PatientsOnly].resource = MyInlinePatient
+        bundleInstance.rules.push(bundleValRule);
+
+        const exported = exportInstance(bundleInstance);
+        expect(exported.entry[0]).toEqual({
+          resource: { resourceType: 'Patient', id: 'MyInlinePatient', active: true }
+        });
+      });
+
+      it('should log an error when fixing an inline resource that does not exist to an instance', () => {
+        const inlineRule = new FixedValueRule('contained[0]')
+          .withFile('FakeInstance.fsh')
+          .withLocation([1, 2, 3, 4]);
+        inlineRule.fixedValue = 'MyFakePatient';
+        inlineRule.isResource = true;
+        patientInstance.rules.push(inlineRule); // * contained[0] = MyFakePatient
+
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toBeUndefined();
+        expect(loggerSpy.getLastMessage('error')).toMatch(
+          /Cannot find definition for Instance: MyFakePatient. Skipping rule.*File: FakeInstance.fsh.*Line: 1 - 3\D*/s
+        );
+      });
+
+      it('should override a fixed inline resource on an instance', () => {
+        const inlineRule = new FixedValueRule('contained[0]');
+        inlineRule.fixedValue = 'MyInlinePatient';
+        inlineRule.isResource = true;
+        const overrideRule = new FixedValueRule('contained[0].birthDate');
+        overrideRule.fixedValue = '2000-02-24';
+        // * contained[0] = MyInlinePatient
+        // * contained[0].birthDate = 2000-02-24
+        patientInstance.rules.push(inlineRule, overrideRule);
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toEqual([
+          { resourceType: 'Patient', id: 'MyInlinePatient', active: true, birthDate: '2000-02-24' }
+        ]);
+      });
+
+      it('should override a fixed via resourceType inline resource on an instance', () => {
+        const inlineRule = new FixedValueRule('contained[0].resourceType');
+        inlineRule.fixedValue = 'Patient';
+        const overrideRule = new FixedValueRule('contained[0].birthDate');
+        overrideRule.fixedValue = '2000-02-24';
+        // * contained[0].resourceType = "Patient"
+        // * contained[0].birthDate = 2000-02-24
+        patientInstance.rules.push(inlineRule, overrideRule);
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toEqual([{ resourceType: 'Patient', birthDate: '2000-02-24' }]);
+      });
+
+      it('should override a fixed inline resource on an instance with paths that mix usage of [0] indexing', () => {
+        const inlineRule = new FixedValueRule('contained[00]'); // [00] index used
+        inlineRule.fixedValue = 'MyInlinePatient';
+        inlineRule.isResource = true;
+        const overrideRule = new FixedValueRule('contained.birthDate'); // no [0] index used
+        overrideRule.fixedValue = '2000-02-24';
+        // * contained[0] = MyInlinePatient
+        // * contained.birthDate = 2000-02-24
+        patientInstance.rules.push(inlineRule, overrideRule);
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toEqual([
+          { resourceType: 'Patient', id: 'MyInlinePatient', active: true, birthDate: '2000-02-24' }
+        ]);
+      });
+
+      it('should override a fixed via resourceType inline resource on an instance with paths that mix usage of [0] indexing', () => {
+        const inlineRule = new FixedValueRule('contained[0].resourceType'); // [0] index used
+        inlineRule.fixedValue = 'Patient';
+        const overrideRule = new FixedValueRule('contained.birthDate'); // no [0] index used
+        overrideRule.fixedValue = '2000-02-24';
+        // * contained.birthDate = 2000-02-24
+        // * contained[0].resourceType = "Patient"
+        patientInstance.rules.push(overrideRule, inlineRule);
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toEqual([{ resourceType: 'Patient', birthDate: '2000-02-24' }]);
+      });
+
+      it('should override a nested fixed inline resource on an instance', () => {
+        const bundleRule = new FixedValueRule('contained[0].resourceType');
+        bundleRule.fixedValue = 'Bundle';
+        const patientRule = new FixedValueRule('contained[0].entry[0].resource');
+        patientRule.fixedValue = 'MyInlinePatient';
+        patientRule.isResource = true;
+        const birthDateRule = new FixedValueRule('contained[0].entry[0].resource.birthDate');
+        birthDateRule.fixedValue = '2000-02-24';
+        // * contained[0].resourceType = "Bundle"
+        // * contained[0].entry[0].resource = MyInlinePatient
+        // * contained[0].entry[0].resource.birthDate = "2000-02-24"
+        patientInstance.rules.push(bundleRule, patientRule, birthDateRule);
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toEqual([
+          {
+            resourceType: 'Bundle',
+            entry: [
+              {
+                resource: {
+                  resourceType: 'Patient',
+                  id: 'MyInlinePatient',
+                  active: true,
+                  birthDate: '2000-02-24'
+                }
+              }
+            ]
+          }
+        ]);
+      });
+
+      it('should override an inline profile on an instance', () => {
+        const inlineBundle = new Instance('MyBundle');
+        inlineBundle.instanceOf = 'TestBundle';
+        doc.instances.set(inlineBundle.name, inlineBundle);
+
+        const bundleRule = new FixedValueRule('contained[0]');
+        bundleRule.fixedValue = 'MyBundle';
+        bundleRule.isResource = true;
+        const birthDateRule = new FixedValueRule(
+          'contained[0].entry[PatientsOnly].resource.birthDate'
+        );
+        birthDateRule.fixedValue = '2000-02-24';
+        // contained[0] = MyBundle
+        // contained[0].entry[PatientsOnly].resource.birthDate = "2000-02-24"
+        patientInstance.rules.push(bundleRule, birthDateRule);
+        const exported = exportInstance(patientInstance);
+        expect(exported.contained).toEqual([
+          {
+            id: 'MyBundle',
+            meta: { profile: ['http://example.com/StructureDefinition/TestBundle'] },
+            resourceType: 'Bundle',
+            entry: [
+              {
+                resource: {
+                  birthDate: '2000-02-24'
+                }
+              }
+            ]
+          }
+        ]);
+      });
     });
   });
 
diff --git a/test/fhirtypes/ElementDefinition.checkFixResource.test.ts b/test/fhirtypes/ElementDefinition.checkFixResource.test.ts
index 72705cf..0433805 100644
--- a/test/fhirtypes/ElementDefinition.checkFixResource.test.ts
+++ b/test/fhirtypes/ElementDefinition.checkFixResource.test.ts
@@ -30,14 +30,14 @@ describe('ElementDefinition', () => {
   describe('#checkFixResource', () => {
     it('should return a resource when it can be fixed', () => {
       const contained = observation.elements.find(e => e.id === 'Observation.contained');
-      const value = contained.checkFixResource(inlineInstance);
+      const value = contained.checkFixResource(inlineInstance, fisher);
       expect(value.resourceType).toBe('Patient');
     });
 
     it('should throw NoSingleTypeError when element has multiple types', () => {
       const valueX = observation.elements.find(e => e.id === 'Observation.value[x]');
       expect(() => {
-        valueX.checkFixResource(inlineInstance);
+        valueX.checkFixResource(inlineInstance, fisher);
       }).toThrow(
         'Cannot fix Resource value on this element since this element does not have a single type'
       );
@@ -46,7 +46,7 @@ describe('ElementDefinition', () => {
     it('should throw MismatchedTypeError when the value is fixed to a non-Resource', () => {
       const status = observation.elements.find(e => e.id === 'Observation.status');
       expect(() => {
-        status.checkFixResource(inlineInstance);
+        status.checkFixResource(inlineInstance, fisher);
       }).toThrow(
         'Cannot fix Patient value: MyInlineInstance. Value does not match element type: code'
       );
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index 67d55d5..783f33b 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -1154,6 +1154,225 @@ describe('StructureDefinition', () => {
           respRate.validateValueAtPath('contained[PatientsOnly][0]', instanceDef, fisher)
         ).toThrow(/Bundle.*OfJoy.*Patient/);
       });
+
+      // Overriding elements
+      it('should allow replacing parts of a Resource element', () => {
+        const language = new FshCode('French');
+        const { fixedValue } = respRate.validateValueAtPath(
+          'contained[0].language',
+          language,
+          fisher
+        );
+        expect(fixedValue).toBe('French');
+      });
+
+      it('should allow replacing parts of a Patient element', () => {
+        const gender = new FshCode('F');
+        const { fixedValue } = respRate.validateValueAtPath(
+          'contained[PatientsOnly][0].gender',
+          gender,
+          fisher
+        );
+        expect(fixedValue).toBe('F');
+      });
+
+      it('should allow overriding a Resource with a Patient', () => {
+        const gender = new FshCode('F');
+        const { fixedValue, pathParts } = respRate.validateValueAtPath(
+          'contained[0].gender',
+          gender,
+          fisher,
+          false,
+          ['Patient']
+        );
+        expect(fixedValue).toBe('F');
+        expect(pathParts).toEqual([
+          { base: 'contained', brackets: ['0'] },
+          { base: 'gender', primitive: true }
+        ]);
+      });
+
+      it('should allow overriding a Resource with a Patient within a Resource overriden by a Bundle', () => {
+        const gender = new FshCode('F');
+        const {
+          fixedValue,
+          pathParts
+        } = respRate.validateValueAtPath(
+          'contained[0].entry[0].resource.gender',
+          gender,
+          fisher,
+          false,
+          ['Bundle', null, 'Patient', null]
+        );
+        expect(fixedValue).toBe('F');
+        expect(pathParts).toEqual([
+          { base: 'contained', brackets: ['0'] },
+          { base: 'entry', brackets: ['0'] },
+          { base: 'resource' },
+          { base: 'gender', primitive: true }
+        ]);
+      });
+
+      it('should allow overriding a Resource with a Patient within a Resource overriden by a Bundle within a Bundle', () => {
+        const gender = new FshCode('F');
+        const {
+          fixedValue,
+          pathParts
+        } = respRate.validateValueAtPath(
+          'contained[0].entry[0].resource.entry[0].resource.gender',
+          gender,
+          fisher,
+          false,
+          ['Bundle', null, 'Bundle', null, 'Patient', null]
+        );
+        expect(fixedValue).toBe('F');
+        expect(pathParts).toEqual([
+          { base: 'contained', brackets: ['0'] },
+          { base: 'entry', brackets: ['0'] },
+          { base: 'resource' },
+          { base: 'entry', brackets: ['0'] },
+          { base: 'resource' },
+          { base: 'gender', primitive: true }
+        ]);
+      });
+
+      it('should allow overriding a Resource with a Profile', () => {
+        const unit = 'slugs';
+        const {
+          fixedValue,
+          pathParts
+        } = respRate.validateValueAtPath('contained[0].valueQuantity.unit', unit, fisher, false, [
+          'http://hl7.org/fhir/StructureDefinition/resprate',
+          null,
+          null
+        ]);
+        expect(fixedValue).toBe('slugs');
+        expect(pathParts).toEqual([
+          { base: 'contained', brackets: ['0'] },
+          { base: 'valueQuantity' },
+          { base: 'unit', primitive: true }
+        ]);
+      });
+
+      it('should not allow overriding a Resource constrained to Patient with a non-Patient path', () => {
+        const system = 'http://hello.com';
+        expect(() =>
+          respRate.validateValueAtPath('contained[0].system', system, fisher, false, ['Patient'])
+        ).toThrow('Cannot resolve element from path: contained[0].system');
+      });
+
+      it('should not allow overriding a Resource constrained to Patient with a non-Patient path inside a Resource', () => {
+        const system = 'http://hello.com';
+        expect(() =>
+          respRate.validateValueAtPath(
+            'contained[0].entry[0].resource.system',
+            system,
+            fisher,
+            false,
+            ['Bundle', null, 'Patient', null]
+          )
+        ).toThrow('Cannot resolve element from path: contained[0].entry[0].resource.system');
+      });
+
+      it('should not allow overriding a Patient with an Observation', () => {
+        const method = new FshCode('man', 'http://method.com');
+        expect(() =>
+          respRate.validateValueAtPath('contained[PatientsOnly].method', method, fisher, false, [
+            'Observation'
+          ])
+        ).toThrow('Cannot resolve element from path: contained[PatientsOnly].method');
+      });
+
+      it('should not allow overriding a Resource constrained to a non-FHIR Resource', () => {
+        const system = 'http://hello.com';
+        expect(() =>
+          respRate.validateValueAtPath('contained[0].system', system, fisher, false, [
+            'CodeableConcept'
+          ])
+        ).toThrow('Cannot resolve element from path: contained[0].system');
+      });
+
+      // resourceType
+      it('should allow a valid FHIR resourceType to be set on a Resource element', () => {
+        const { fixedValue, pathParts } = respRate.validateValueAtPath(
+          'contained[0].resourceType',
+          'Patient',
+          fisher
+        );
+        expect(fixedValue).toBe('Patient');
+        expect(pathParts).toEqual([
+          { base: 'contained', brackets: ['0'] },
+          { base: 'resourceType' }
+        ]);
+      });
+
+      it('should allow a valid FHIR resourceType to be set on a DomainResource element', () => {
+        const { fixedValue, pathParts } = respRate.validateValueAtPath(
+          'contained[DomainsOnly][0].resourceType',
+          'Patient',
+          fisher
+        );
+        expect(fixedValue).toBe('Patient');
+        expect(pathParts).toEqual([
+          { base: 'contained', brackets: ['DomainsOnly', '0'] },
+          { base: 'resourceType' }
+        ]);
+      });
+
+      it('should allow a valid FHIR resourceType to be set on a Patient element', () => {
+        const { fixedValue, pathParts } = respRate.validateValueAtPath(
+          'contained[PatientsOnly][0].resourceType',
+          'Patient',
+          fisher
+        );
+        expect(fixedValue).toBe('Patient');
+        expect(pathParts).toEqual([
+          { base: 'contained', brackets: ['PatientsOnly', '0'] },
+          { base: 'resourceType' }
+        ]);
+      });
+
+      it('should not allow a resourceType to be set on a non-Resource element', () => {
+        expect(() => {
+          respRate.validateValueAtPath('code.resourceType', 'CodeableConcept', fisher);
+        }).toThrow(
+          'A resourceType of CodeableConcept cannot be set on an element of type CodeableConcept.'
+        );
+      });
+
+      it('should not allow an invalid FHIR resourceType to be set on a DomainResource element', () => {
+        expect(() =>
+          respRate.validateValueAtPath('contained[DomainsOnly][0].resourceType', 'Bundle', fisher)
+        ).toThrow('A resourceType of Bundle cannot be set on an element of type DomainResource.');
+      });
+
+      it('should not allow an invalid FHIR resourceType to be set on a Patient element', () => {
+        expect(() =>
+          respRate.validateValueAtPath(
+            'contained[PatientsOnly][0].resourceType',
+            'Resource',
+            fisher
+          )
+        ).toThrow('A resourceType of Resource cannot be set on an element of type Patient.');
+      });
+
+      it('should not allow a profile resourceType to be set on a Resource element', () => {
+        expect(() =>
+          respRate.validateValueAtPath('contained[0].resourceType', 'resprate', fisher)
+        ).toThrow('A resourceType of resprate cannot be set on an element of type Resource');
+      });
+
+      it('should not allow an invalid FHIR resourceType to be set on a choice element', () => {
+        expect(() =>
+          respRate.validateValueAtPath('component.value[x].resourceType', 'Patient', fisher)
+        ).toThrow('Cannot resolve element from path: component.value[x].resourceType');
+      });
+
+      it('should not allow a resourceType to be set at the top level of the instance', () => {
+        expect(() => respRate.validateValueAtPath('resourceType', 'Patient', fisher)).toThrow(
+          'Cannot resolve element from path: resourceType'
+        );
+      });
     });
   });